diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 7a036d9752..7103c47882 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -27,6 +27,7 @@ body: What version of the package are you using? You can check the Unity version in Package Manager Window. See [manual](https://docs.unity3d.com/Manual/upm-ui.html). options: + - 3.0.0-pre.8 - 3.0.0-pre.7 - 3.0.0-pre.6 - 3.0.0-pre.5 diff --git a/.gitignore b/.gitignore index 5f015ee8cb..5956a9e5c4 100644 --- a/.gitignore +++ b/.gitignore @@ -218,6 +218,7 @@ upm-ci~/ Plugin~/webrtc/* WebRTC~/CodeCoverage WebRTC~/TestResults-* +WebRTC~/Logs *.dbmdl *.dbproj.schemaview *.jfm @@ -359,6 +360,7 @@ Plugin~/WebRTCPluginTest/webrtc-test.xcodeproj/* [Tt]emp/ [Oo]bj/ [Bb]uild/ +[Uu]serSettings/ # Exclude Documentation folder !Documentation~ diff --git a/.yamato/upm-ci-webrtc-packages.yml b/.yamato/upm-ci-webrtc-packages.yml index dca7b2ae25..eae176a889 100644 --- a/.yamato/upm-ci-webrtc-packages.yml +++ b/.yamato/upm-ci-webrtc-packages.yml @@ -1,4 +1,59 @@ {% metadata_file .yamato/package.metafile %} +{% metadata_file .yamato/meta/environments.yml %} + +editors: + - version: 2019.4 + - version: 2020.3 + - version: 2021.2 + +platforms: + - name: win + type: Unity::VM + gpu_type: Unity::VM::GPU + image: renderstreaming/win10:v0.3.7-728388 + gpu_image: renderstreaming/win10:v0.3.7-728285 + flavor: b1.large + model: rtx2080 + build_command: BuildScripts~/build_plugin_win.cmd + test_command: BuildScripts~/test_plugin_win.cmd + plugin_path: Runtime/Plugins/x86_64/webrtc.dll + - name: linux + type: Unity::VM + gpu_type: Unity::VM::GPU + image: renderstreaming/ubuntu-18.04:latest + gpu_image: renderstreaming/ubuntu-18.04:latest + flavor: b1.large + model: rtx2080 + build_command: BuildScripts~/build_plugin_linux.sh + test_command: BuildScripts~/test_plugin_linux.sh + plugin_path: Runtime/Plugins/x86_64/libwebrtc.so + - name: macos + type: Unity::metal::macmini + gpu_type: Unity::metal::macmini + image: package-ci/mac:latest + gpu_image: package-ci/mac:latest + flavor: m1.mac + build_command: BuildScripts~/build_plugin_mac.sh + test_command: BuildScripts~/test_plugin_mac.sh + plugin_path: Runtime/Plugins/macOS/webrtc.bundle/** + - name: ios + type: Unity::metal::macmini + gpu_type: Unity::metal::macmini + image: package-ci/mac:latest + gpu_image: package-ci/mac:latest + flavor: m1.mac + build_command: BuildScripts~/build_plugin_ios.sh + test_command: BuildScripts~/test_plugin_ios.sh + plugin_path: Runtime/Plugins/iOS/webrtc.framework/** + - name: android + type: Unity::VM + gpu_type: Unity::VM + image: renderstreaming/android-linux-build:latest + gpu_image: renderstreaming/android-linux-build:latest + flavor: b1.large + build_command: BuildScripts~/build_plugin_android.sh + test_command: BuildScripts~/test_plugin_android.sh + plugin_path: Runtime/Plugins/Android/libwebrtc.aar test_targets: - name: win @@ -316,6 +371,37 @@ test_{{ package.name }}_{{ editor.version }}_android_{{ target.name }}: paths: - "build/test-results/**" {% endfor %} + +test_{{ package.name }}_{{ editor.version }}_webgl_win: + name: Test {{ package.packagename }} with {{ editor.version }} on WebGL Windows + agent: + type: Unity::VM::GPU + image: package-ci/win10:stable + flavor: b1.large + dependencies: + - .yamato/upm-ci-{{ package.name }}-packages.yml#pack_{{ package.name }} + commands: + - pip install unity-downloader-cli --index-url https://artifactory.prd.it.unity3d.com/artifactory/api/pypi/pypi/simple + - unity-downloader-cli -u {{ editor.version }} -c WebGL -c Editor -c StandaloneSupport-IL2CPP --fast -w + - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools/utr-standalone/utr.bat --output utr.bat + - ./utr --suite=playmode --platform=WebGL --editor-location=.Editor --testproject=WebRTC~ --artifacts_path=build/test-results --timeout=5400 + +test_{{ package.name }}_{{ editor.version }}_webgl_macos: + name: Test {{ package.packagename }} with {{ editor.version }} on WebGL macOS + agent: + type: Unity::VM::osx + image: package-ci/mac:latest + flavor: m1.mac + dependencies: + - .yamato/upm-ci-{{ package.name }}-packages.yml#pack_{{ package.name }} + commands: + - find upm-ci~/packages/ -name "*.tgz" | xargs -I file tar xvf file -C upm-ci~ + - cp -rf upm-ci~/package/Runtime/Plugins Runtime/ + - pip install unity-downloader-cli --index-url https://artifactory.prd.it.unity3d.com/artifactory/api/pypi/pypi/simple + - unity-downloader-cli -u {{ editor.version }} -c WebGL -c Editor -c StandaloneSupport-IL2CPP --fast -w + - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools/utr-standalone/utr --output utr + - chmod +x ./utr + - ./utr --suite=playmode --platform=WebGL --editor-location=.Editor --testproject=WebRTC~ --artifacts_path=build/test-results --timeout=5400 {% endfor %} {% for target in test_targets %} diff --git a/CHANGELOG.md b/CHANGELOG.md index a6032abbf9..bd240d2712 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,25 @@ All notable changes to the webrtc package will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [3.0.0-pre.8] - 2024-12-12 + +### Changed +- doc: improve the API doc of CameraExtension +- doc: improve the API doc of RTCSessionDescription +- doc: improve the API doc of RTCDataChannel +- doc: Improve the API doc of RTCConfiguration +- doc: Improve the API doc of RTCRtpEncodingParameters +- doc: Improve the API doc of RTCRtpSender +- doc: Improve the API doc of RTCRtpTransceiver +- doc: Improve the API doc of WebRTC +- doc: Improve the API doc of AudioStreamTrack +- doc: Improve the API doc of VideoStreamTrack +- doc: Improve the API doc of MediaStreamTrack +- doc: Improve the API doc of MediaStream +- doc: Improve the API doc of RTCPeerConnection +- doc: Improve the API doc of RTCIceServer + + ## [3.0.0-pre.7] - 2023-10-20 ### Added diff --git a/Documentation~/install.md b/Documentation~/install.md index 15f0b5cf17..58e2506f1f 100644 --- a/Documentation~/install.md +++ b/Documentation~/install.md @@ -11,7 +11,7 @@ Check Package Manager window, Click `+` button and select `Add package from git Input the string below to the input field. ``` -com.unity.webrtc@3.0.0-pre.7 +com.unity.webrtc@3.0.0-pre.8 ``` The list of version string is [here](https://github.com/Unity-Technologies/com.unity.webrtc/tags). In most cases, the latest version is recommended to use. diff --git a/Plugin~/WebRTCPlugin/Codec/CreateVideoCodecFactory.cpp b/Plugin~/WebRTCPlugin/Codec/CreateVideoCodecFactory.cpp index ff6fe21841..a1598d449c 100644 --- a/Plugin~/WebRTCPlugin/Codec/CreateVideoCodecFactory.cpp +++ b/Plugin~/WebRTCPlugin/Codec/CreateVideoCodecFactory.cpp @@ -54,13 +54,16 @@ namespace webrtc if (impl == kNvCodecImpl) { #if CUDA_PLATFORM - if (gfxDevice && gfxDevice->IsCudaSupport() && NvEncoder::IsSupported()) + if (gfxDevice && gfxDevice->IsCudaSupport()) { CUcontext context = gfxDevice->GetCUcontext(); - NV_ENC_BUFFER_FORMAT format = gfxDevice->GetEncodeBufferFormat(); - std::unique_ptr factory = - std::make_unique(context, format, profiler); - return CreateSimulcastEncoderFactory(std::move(factory)); + if (NvEncoder::IsSupported(context)) + { + NV_ENC_BUFFER_FORMAT format = gfxDevice->GetEncodeBufferFormat(); + std::unique_ptr factory = + std::make_unique(context, format, profiler); + return CreateSimulcastEncoderFactory(std::move(factory)); + } } #endif } diff --git a/Plugin~/WebRTCPlugin/Codec/NvCodec/NvCodec.cpp b/Plugin~/WebRTCPlugin/Codec/NvCodec/NvCodec.cpp index f9449d00e1..3a321be535 100644 --- a/Plugin~/WebRTCPlugin/Codec/NvCodec/NvCodec.cpp +++ b/Plugin~/WebRTCPlugin/Codec/NvCodec/NvCodec.cpp @@ -146,15 +146,40 @@ namespace webrtc return std::make_unique(codec, context, memoryType, format, profiler); } - bool NvEncoder::IsSupported() + bool NvEncoder::IsSupported(CUcontext context) { uint32_t version = 0; uint32_t currentVersion = (NVENCAPI_MAJOR_VERSION << 4) | NVENCAPI_MINOR_VERSION; - NVENC_API_CALL(NvEncodeAPIGetMaxSupportedVersion(&version)); - if (currentVersion > version) + NVENCSTATUS result = NvEncodeAPIGetMaxSupportedVersion(&version); + if (result != NV_ENC_SUCCESS || currentVersion > version) { return false; } + +// Check if this device can get the function list of nvencoder API +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" + NV_ENCODE_API_FUNCTION_LIST funclist = { NV_ENCODE_API_FUNCTION_LIST_VER }; + result = NvEncodeAPICreateInstance(&funclist); + if (result != NV_ENC_SUCCESS || funclist.nvEncOpenEncodeSession == nullptr) + { + return false; + } + +// Check if this device can open encode session +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" + NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS encodeSessionExParams = { NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER }; + encodeSessionExParams.device = context; + encodeSessionExParams.deviceType = NV_ENC_DEVICE_TYPE_CUDA; + encodeSessionExParams.apiVersion = NVENCAPI_VERSION; + void* hEncoder = nullptr; + result = funclist.nvEncOpenEncodeSessionEx(&encodeSessionExParams, &hEncoder); + if (result != NV_ENC_SUCCESS || hEncoder == nullptr) + { + return false; + } + + funclist.nvEncDestroyEncoder(hEncoder); + hEncoder = nullptr; return true; } diff --git a/Plugin~/WebRTCPlugin/Codec/NvCodec/NvCodec.h b/Plugin~/WebRTCPlugin/Codec/NvCodec/NvCodec.h index 665be39424..617158d3d9 100644 --- a/Plugin~/WebRTCPlugin/Codec/NvCodec/NvCodec.h +++ b/Plugin~/WebRTCPlugin/Codec/NvCodec/NvCodec.h @@ -35,7 +35,7 @@ namespace webrtc CUmemorytype memoryType, NV_ENC_BUFFER_FORMAT format, ProfilerMarkerFactory* profiler); - static bool IsSupported(); + static bool IsSupported(CUcontext context); ~NvEncoder() override { } }; diff --git a/Plugin~/WebRTCPluginTest/NvCodec/NvCodecTest.cpp b/Plugin~/WebRTCPluginTest/NvCodec/NvCodecTest.cpp index 3accfb8d89..b9f779ee87 100644 --- a/Plugin~/WebRTCPluginTest/NvCodec/NvCodecTest.cpp +++ b/Plugin~/WebRTCPluginTest/NvCodec/NvCodecTest.cpp @@ -36,11 +36,12 @@ namespace webrtc GTEST_SKIP() << "The graphics driver is not installed on the device."; if (!device_->IsCudaSupport()) GTEST_SKIP() << "CUDA is not supported on this device."; - if (!NvEncoder::IsSupported()) - GTEST_SKIP() << "Current Driver Version does not support this NvEncodeAPI version."; context_ = device_->GetCUcontext(); + if (!NvEncoder::IsSupported(context_)) + GTEST_SKIP() << "Current Driver Version does not support this NvEncodeAPI version."; + VideoCodecTest::SetUp(); } diff --git a/README.md b/README.md index e9862981ac..cec035486a 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ unity 2020.3 unity 2021.3 -unity 2022.3 -unity 2023.1 +unity 2022.3 +unity 2023.1 **WebRTC for Unity** is a package that allows [WebRTC](https://webrtc.org) to be used in Unity. @@ -32,32 +32,33 @@ Please read this if you have an interest to customize native code in this projec ## Roadmap -| Version | libwebrtc version | Focus | When | -| ------- | ----------------- | ----- | ---- | -| `1.0.0-preview` | [M72](https://groups.google.com/d/msg/discuss-webrtc/3h4y0fimHwg) | - First release | Sep 2019 | -| `1.1.0-preview` | [M72](https://groups.google.com/d/msg/discuss-webrtc/3h4y0fimHwg) | - IL2CPP Support
- Linux platform Support
- Add software encoder | Feb 2020 | -| `2.0.0-preview` | [M79](https://groups.google.com/d/msg/discuss-webrtc/Ozvbd0p7Q1Y) | - Multi camera
- DirectX12 (DXR) Support | Apr 2020 | -| `2.1.0-preview` | [M84](https://groups.google.com/g/discuss-webrtc/c/MRAV4jgHYV0) | - Profiler tool
- Bitrate control | Aug 2020 | -| `2.2.0-preview` | [M85](https://groups.google.com/g/discuss-webrtc/c/Qq3nsR2w2HU) | - Video decoder (VP8, VP9 only)
- Vulkan HW encoder support
- MacOS HW encoder support | Oct 2020 | -| `2.3.0-preview` | [M85](https://groups.google.com/g/discuss-webrtc/c/Qq3nsR2w2HU) | - iOS platform support | Dec 2020 | -| `2.4.0-exp.1` | [M89](https://groups.google.com/g/discuss-webrtc/c/Zrsn2hi8FV0) | - Android platform support | Apr 2021 | -| `2.4.0-exp.2` | [M89](https://groups.google.com/g/discuss-webrtc/c/Zrsn2hi8FV0) | - Fix bugs | May 2021 | -| `2.4.0-exp.3` | [M89](https://groups.google.com/g/discuss-webrtc/c/Zrsn2hi8FV0) | - Fix bugs | Jun 2021 | -| `2.4.0-exp.4` | [M89](https://groups.google.com/g/discuss-webrtc/c/Zrsn2hi8FV0) | - Audio renderer support
- Apple Silicon support | Aug 2021 | -| `2.4.0-exp.5` | [M92](https://groups.google.com/g/discuss-webrtc/c/hks5zneZJbo) | - Fix audio streaming issues | Feb 2022 | -| `2.4.0-exp.6` | [M92](https://groups.google.com/g/discuss-webrtc/c/hks5zneZJbo) | - Hotfix | Feb 2022 | -| `2.4.0-exp.7` | [M92](https://groups.google.com/g/discuss-webrtc/c/hks5zneZJbo) | - Fix video streaming issues | May 2022 | -| `2.4.0-exp.8` | [M92](https://groups.google.com/g/discuss-webrtc/c/hks5zneZJbo) | - Fix bugs | Jul 2022 | -| `2.4.0-exp.9` | [M92](https://groups.google.com/g/discuss-webrtc/c/hks5zneZJbo) | - Hotfix | Aug 2022 | -| `2.4.0-exp.10` | [M92](https://groups.google.com/g/discuss-webrtc/c/hks5zneZJbo) | - Hotfix | Aug 2022 | -| `2.4.0-exp.11` | [M92](https://groups.google.com/g/discuss-webrtc/c/hks5zneZJbo) | - Hotfix | Sep 2022 | -| `3.0.0-pre.1` | [M92](https://groups.google.com/g/discuss-webrtc/c/hks5zneZJbo) | - Fix bugs | Nov 2022 | -| `3.0.0-pre.2` | [M107](https://groups.google.com/g/discuss-webrtc/c/StVFkKuSRc8) | - Update libwebrtc M107 | Dec 2022 | -| `3.0.0-pre.3` | [M107](https://groups.google.com/g/discuss-webrtc/c/StVFkKuSRc8) | - Fix bugs | Dec 2022 | -| `3.0.0-pre.4` | [M107](https://groups.google.com/g/discuss-webrtc/c/StVFkKuSRc8) | - Fix bugs | Jan 2023 | -| `3.0.0-pre.5` | [M107](https://groups.google.com/g/discuss-webrtc/c/StVFkKuSRc8) | - Encoded Transform API | Apr 2023 | -| `3.0.0-pre.6` | [M112](https://groups.google.com/g/discuss-webrtc/c/V-XFau9W9gY) | - Fix bugs | Jul 2023 | -| `3.0.0-pre.7` | [M116](https://groups.google.com/g/discuss-webrtc/c/bEsO8Lz7psE) | - Update libwebrtc M116
- Simulcast for NVIDIA H.264 | Oct 2023 | +| Version | libwebrtc version | Focus | When | +|------------------| ----------------- | ----- | ---- | +| `1.0.0-preview` | [M72](https://groups.google.com/d/msg/discuss-webrtc/3h4y0fimHwg) | - First release | Sep 2019 | +| `1.1.0-preview` | [M72](https://groups.google.com/d/msg/discuss-webrtc/3h4y0fimHwg) | - IL2CPP Support
- Linux platform Support
- Add software encoder | Feb 2020 | +| `2.0.0-preview` | [M79](https://groups.google.com/d/msg/discuss-webrtc/Ozvbd0p7Q1Y) | - Multi camera
- DirectX12 (DXR) Support | Apr 2020 | +| `2.1.0-preview` | [M84](https://groups.google.com/g/discuss-webrtc/c/MRAV4jgHYV0) | - Profiler tool
- Bitrate control | Aug 2020 | +| `2.2.0-preview` | [M85](https://groups.google.com/g/discuss-webrtc/c/Qq3nsR2w2HU) | - Video decoder (VP8, VP9 only)
- Vulkan HW encoder support
- MacOS HW encoder support | Oct 2020 | +| `2.3.0-preview` | [M85](https://groups.google.com/g/discuss-webrtc/c/Qq3nsR2w2HU) | - iOS platform support | Dec 2020 | +| `2.4.0-exp.1` | [M89](https://groups.google.com/g/discuss-webrtc/c/Zrsn2hi8FV0) | - Android platform support | Apr 2021 | +| `2.4.0-exp.2` | [M89](https://groups.google.com/g/discuss-webrtc/c/Zrsn2hi8FV0) | - Fix bugs | May 2021 | +| `2.4.0-exp.3` | [M89](https://groups.google.com/g/discuss-webrtc/c/Zrsn2hi8FV0) | - Fix bugs | Jun 2021 | +| `2.4.0-exp.4` | [M89](https://groups.google.com/g/discuss-webrtc/c/Zrsn2hi8FV0) | - Audio renderer support
- Apple Silicon support | Aug 2021 | +| `2.4.0-exp.5` | [M92](https://groups.google.com/g/discuss-webrtc/c/hks5zneZJbo) | - Fix audio streaming issues | Feb 2022 | +| `2.4.0-exp.6` | [M92](https://groups.google.com/g/discuss-webrtc/c/hks5zneZJbo) | - Hotfix | Feb 2022 | +| `2.4.0-exp.7` | [M92](https://groups.google.com/g/discuss-webrtc/c/hks5zneZJbo) | - Fix video streaming issues | May 2022 | +| `2.4.0-exp.8` | [M92](https://groups.google.com/g/discuss-webrtc/c/hks5zneZJbo) | - Fix bugs | Jul 2022 | +| `2.4.0-exp.9` | [M92](https://groups.google.com/g/discuss-webrtc/c/hks5zneZJbo) | - Hotfix | Aug 2022 | +| `2.4.0-exp.10` | [M92](https://groups.google.com/g/discuss-webrtc/c/hks5zneZJbo) | - Hotfix | Aug 2022 | +| `2.4.0-exp.11` | [M92](https://groups.google.com/g/discuss-webrtc/c/hks5zneZJbo) | - Hotfix | Sep 2022 | +| `3.0.0-pre.1` | [M92](https://groups.google.com/g/discuss-webrtc/c/hks5zneZJbo) | - Fix bugs | Nov 2022 | +| `3.0.0-pre.2` | [M107](https://groups.google.com/g/discuss-webrtc/c/StVFkKuSRc8) | - Update libwebrtc M107 | Dec 2022 | +| `3.0.0-pre.3` | [M107](https://groups.google.com/g/discuss-webrtc/c/StVFkKuSRc8) | - Fix bugs | Dec 2022 | +| `3.0.0-pre.4` | [M107](https://groups.google.com/g/discuss-webrtc/c/StVFkKuSRc8) | - Fix bugs | Jan 2023 | +| `3.0.0-pre.5` | [M107](https://groups.google.com/g/discuss-webrtc/c/StVFkKuSRc8) | - Encoded Transform API | Apr 2023 | +| `3.0.0-pre.6` | [M112](https://groups.google.com/g/discuss-webrtc/c/V-XFau9W9gY) | - Fix bugs | Jul 2023 | +| `3.0.0-pre.7` | [M116](https://groups.google.com/g/discuss-webrtc/c/bEsO8Lz7psE) | - Update libwebrtc M116
- Simulcast for NVIDIA H.264 | Oct 2023 | +| `3.0.0-pre.8` | [M116](https://groups.google.com/g/discuss-webrtc/c/bEsO8Lz7psE) | | | ## Licenses diff --git a/Runtime/Plugins/WebGL.meta b/Runtime/Plugins/WebGL.meta new file mode 100644 index 0000000000..ff2accf012 --- /dev/null +++ b/Runtime/Plugins/WebGL.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2f07c78bad0e2744cb2802a3f74e4f63 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Plugins/WebGL/AudioStreamTrack.jslib b/Runtime/Plugins/WebGL/AudioStreamTrack.jslib new file mode 100644 index 0000000000..039b840099 --- /dev/null +++ b/Runtime/Plugins/WebGL/AudioStreamTrack.jslib @@ -0,0 +1,19 @@ +var UnityWebRTCAudioStreamTrack = { + + // To be implemented + CreateAudioTrack: function (labelPtr, sourcePtr) { + if (!uwcom_audioContext) { + uwcom_audioContext = new AudioContext; + } + var dest = uwcom_audioContext.createMediaStreamDestination(); + var audioTrack = dest.stream.getAudioTracks()[0]; + uwcom_addManageObj(audioTrack); + audioTrack.guid = UTF8ToString(labelPtr); + return audioTrack.managePtr; + }, + + ProcessAudio: function (data, size) { + // TODO + } +}; +mergeInto(LibraryManager.library, UnityWebRTCAudioStreamTrack); diff --git a/Runtime/Plugins/WebGL/AudioStreamTrack.jslib.meta b/Runtime/Plugins/WebGL/AudioStreamTrack.jslib.meta new file mode 100644 index 0000000000..0849e3ddd5 --- /dev/null +++ b/Runtime/Plugins/WebGL/AudioStreamTrack.jslib.meta @@ -0,0 +1,44 @@ +fileFormatVersion: 2 +guid: 5594afffd631a0149986ea6854cb8379 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 1 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 0 + Exclude Win: 1 + Exclude Win64: 1 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + WebGL: WebGL + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Plugins/WebGL/Common.jslib b/Runtime/Plugins/WebGL/Common.jslib new file mode 100644 index 0000000000..348c1ff70e --- /dev/null +++ b/Runtime/Plugins/WebGL/Common.jslib @@ -0,0 +1,211 @@ +var UnityWebRTCCommon = { + $uwcom_logLevel: 0, + $uwcom_managePtr: 0, + $uwcom_localAudioTracks: {}, + $uwcom_localVideoTracks: {}, + $uwcom_remoteAudioTracks: {}, + $uwcom_remoteVideoTracks: {}, + $uwcom_audioContext: null, + + $uwcom_addManageObj: function (obj) { + if (!obj.managePtr) { + uwcom_managePtr++; + obj.managePtr = uwcom_managePtr; + UWManaged[obj.managePtr] = obj; + } + else if(!UWManaged[obj.managePtr]){ + UWManaged[obj.managePtr] = obj; + } + }, + $uwcom_strToPtr: function (str) { + var len = lengthBytesUTF8(str) + 1; + var ptr = _malloc(len); + stringToUTF8(str, ptr, len); + return ptr; + }, + $uwcom_arrayToReturnPtr: function (arr, type) { + var buf = (new type(arr)).buffer; + var ui8a = new Uint8Array(buf); + var ptr = _malloc(ui8a.byteLength + 4); + HEAP32.set([arr.length], ptr >> 2); + HEAPU8.set(ui8a, ptr + 4); + setTimeout(function () { + _free(ptr); + }, 0); + return ptr; + }, + $uwcom_errorNo: function (err) { + var errNo = UWRTCErrorType.indexOf(err.name); + if (errNo === -1) + errNo = 0; + return errNo; + }, + $uwcom_fixStatEnumValue: function (stat) { + if (stat.type === 'codec') { + if (stat.codecType) { + stat.codecType = UWRTCCodecType.indexOf(stat.codecType); + if (stat.codecType === -1) return false; + } + } + if (stat.type === 'outbound-rtp') { + if (stat.qualityLimitationReason) { + stat.qualityLimitationReason = UWRTCQualityLimitationReason.indexOf(stat.qualityLimitationReason); + if (stat.qualityLimitationReason === -1) return false; + } + if (stat.priority) { + stat.priority = UWRTCPriorityType.indexOf(stat.priority); + if (stat.priority === -1) return false; + } + } + if (stat.type === 'media-source') { + if (stat.kind) { + stat.kind = UWMediaStreamTrackKind.indexOf(stat.kind); + if (stat.kind === -1) return false; + } + } + if (stat.type === 'data-channel') { + if (stat.state) { + stat.state = UWRTCDataChannelState.indexOf(stat.state); + if (stat.state === -1) return false; + } + } + if (stat.type === 'transport') { + if (stat.iceRole) { + stat.iceRole = UWRTCIceRole.indexOf(stat.iceRole); + if (stat.iceRole === -1) return false; + } + if (stat.dtlsState) { + stat.dtlsState = UWRTCDtlsTransportState.indexOf(stat.dtlsState); + if (stat.dtlsState === -1) return false; + } + if (stat.iceState) { + stat.iceState = UWRTCIceTransportState.indexOf(stat.iceState); + if (stat.iceState === -1) return false; + } + } + if (stat.type === 'local-candidate' + || stat.type === 'remote-candidate') { + if (stat.candidateType) { + stat.candidateType = UWRTCIceCandidateType.indexOf(stat.candidateType); + if (stat.candidateType === -1) return false; + } + } + if (stat.type === 'candidate-pair') { + if (stat.state) { + stat.state = UWRTCStatsIceCandidatePairState.indexOf(stat.state); + if (stat.state === -1) return false; + } + } + stat.type = UWRTCStatsType.indexOf(stat.type); + return true; + }, + $uwcom_statsSerialize: function (stats) { + var statsJsons = []; + stats.forEach((function(stat) { + if (uwcom_fixStatEnumValue(stat)) statsJsons.push(stat); + })); + var statsDataJson = JSON.stringify(statsJsons); + var statsDataJsonPtr = uwcom_strToPtr(statsDataJson); + return statsDataJsonPtr; + }, + $uwcom_existsCheck: function (ptr, funcName, typeName) { + var obj = UWManaged[ptr]; + if (obj) return true; + console.error("[jslib] " + funcName + ": Unmanaged " + typeName + ". Ptr: " + ptr); + return false; + }, + $uwcom_getIdx: function (enum_, val) { + enum_.indexOf() + }, + $uwcom_debugLog: function (level, fileName, member, msg) { + if (!level) return; + var logLevels = ['', '', '', '', '', '', '', 'error', 'warning', 'log', 'verbose']; + var levelNo = logLevels.indexOf(level); + if (levelNo === -1) return; + if ((uwcom_logLevel > 0 && uwcom_logLevel <= 3 && levelNo > 0 && (levelNo - 6) <= uwcom_logLevel) || + (uwcom_logLevel > 6 && uwcom_logLevel <= 9 && levelNo > 6 && levelNo <= uwcom_logLevel)) { + msg = '[JSLIB] ' + fileName + ' : ' + member + ' : ' + msg; + var msgPtr = uwcom_strToPtr(msg); + // shift level number so 9(log) => 1(NativeLoggingSeverity.Info) + Module.dynCall_vii(uwevt_DebugLog, msgPtr, Math.min(logLevels.indexOf('log') - levelNo + 1,4)); + _free(msgPtr); + } + }, + + $UWManaged: {}, + + $uwevt_DebugLog: null, + $uwevt_PCOnIceCandidate: null, + $uwevt_PCOnIceConnectionChange: null, + $uwevt_PCOnConnectionStateChange: null, + $uwevt_PCOnIceGatheringChange: null, + $uwevt_PCOnNegotiationNeeded: null, + $uwevt_PCOnDataChannel: null, + $uwevt_PCOnTrack: null, + $uwevt_PCOnRemoveTrack: null, + $uwevt_MSOnAddTrack: null, + $uwevt_MSOnRemoveTrack: null, + $uwevt_DCOnTextMessage: null, + $uwevt_DCOnBinaryMessage: null, + $uwevt_DCOnOpen: null, + $uwevt_DCOnClose: null, + $uwevt_DCOnError: null, + $uwevt_OnSetSessionDescSuccess: null, + $uwevt_OnSetSessionDescFailure: null, + $uwevt_OnSuccessCreateSessionDesc: null, + $uwevt_OnFailureCreateSessionDesc: null, + $uwevt_OnStatsDeliveredCallback: null, + + RegisterDebugLog: function (debugLogPtr,enableNativeLog,nativeLoggingSeverity) { + var logLevels = ['', '', '', '', '', '', '', 'error', 'warning', 'log', 'verbose']; + // shift level number so 1(NativeLoggingSeverity.Info) => 9(log) + uwcom_logLevel = logLevels.indexOf('log') - nativeLoggingSeverity + 1; + uwevt_DebugLog = debugLogPtr; + }, + + StatsGetJson: function (statsPtr) { + throw new Error("Not implemented"); + }, + +}; +autoAddDeps(UnityWebRTCCommon, '$uwcom_logLevel'); +autoAddDeps(UnityWebRTCCommon, '$uwcom_debugLog'); +autoAddDeps(UnityWebRTCCommon, '$uwcom_managePtr'); +autoAddDeps(UnityWebRTCCommon, '$uwcom_localAudioTracks'); +autoAddDeps(UnityWebRTCCommon, '$uwcom_localVideoTracks'); +autoAddDeps(UnityWebRTCCommon, '$uwcom_remoteAudioTracks'); +autoAddDeps(UnityWebRTCCommon, '$uwcom_remoteVideoTracks'); +autoAddDeps(UnityWebRTCCommon, '$uwcom_audioContext'); +autoAddDeps(UnityWebRTCCommon, '$UWManaged'); + +autoAddDeps(UnityWebRTCCommon, '$uwevt_DebugLog'); +autoAddDeps(UnityWebRTCCommon, '$uwevt_PCOnIceCandidate'); +autoAddDeps(UnityWebRTCCommon, '$uwevt_PCOnIceConnectionChange'); +autoAddDeps(UnityWebRTCCommon, '$uwevt_PCOnConnectionStateChange'); +autoAddDeps(UnityWebRTCCommon, '$uwevt_PCOnIceGatheringChange'); +autoAddDeps(UnityWebRTCCommon, '$uwevt_PCOnNegotiationNeeded'); +autoAddDeps(UnityWebRTCCommon, '$uwevt_PCOnDataChannel'); +autoAddDeps(UnityWebRTCCommon, '$uwevt_PCOnTrack'); +autoAddDeps(UnityWebRTCCommon, '$uwevt_PCOnRemoveTrack'); +autoAddDeps(UnityWebRTCCommon, '$uwevt_MSOnAddTrack'); +autoAddDeps(UnityWebRTCCommon, '$uwevt_MSOnRemoveTrack'); +autoAddDeps(UnityWebRTCCommon, '$uwevt_DCOnTextMessage'); +autoAddDeps(UnityWebRTCCommon, '$uwevt_DCOnBinaryMessage'); +autoAddDeps(UnityWebRTCCommon, '$uwevt_DCOnOpen'); +autoAddDeps(UnityWebRTCCommon, '$uwevt_DCOnClose'); +autoAddDeps(UnityWebRTCCommon, '$uwevt_DCOnError'); +autoAddDeps(UnityWebRTCCommon, '$uwevt_OnSetSessionDescSuccess'); +autoAddDeps(UnityWebRTCCommon, '$uwevt_OnSetSessionDescFailure'); +autoAddDeps(UnityWebRTCCommon, '$uwevt_OnSuccessCreateSessionDesc'); +autoAddDeps(UnityWebRTCCommon, '$uwevt_OnFailureCreateSessionDesc'); +autoAddDeps(UnityWebRTCCommon, '$uwevt_OnStatsDeliveredCallback'); + +autoAddDeps(UnityWebRTCCommon, '$uwcom_addManageObj'); +autoAddDeps(UnityWebRTCCommon, '$uwcom_strToPtr'); +autoAddDeps(UnityWebRTCCommon, '$uwcom_arrayToReturnPtr'); +autoAddDeps(UnityWebRTCCommon, '$uwcom_errorNo'); +autoAddDeps(UnityWebRTCCommon, '$uwcom_fixStatEnumValue'); +autoAddDeps(UnityWebRTCCommon, '$uwcom_statsSerialize'); +autoAddDeps(UnityWebRTCCommon, '$uwcom_existsCheck'); + +mergeInto(LibraryManager.library, UnityWebRTCCommon); diff --git a/Runtime/Plugins/WebGL/Common.jslib.meta b/Runtime/Plugins/WebGL/Common.jslib.meta new file mode 100644 index 0000000000..9ab0de82d9 --- /dev/null +++ b/Runtime/Plugins/WebGL/Common.jslib.meta @@ -0,0 +1,44 @@ +fileFormatVersion: 2 +guid: 03acbb047b5b38f498dd4b9435f8a9b2 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 1 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 0 + Exclude Win: 1 + Exclude Win64: 1 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + WebGL: WebGL + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Plugins/WebGL/Context.jslib b/Runtime/Plugins/WebGL/Context.jslib new file mode 100644 index 0000000000..d704505bf9 --- /dev/null +++ b/Runtime/Plugins/WebGL/Context.jslib @@ -0,0 +1,338 @@ +var UnityWebRTCContext = { + GetHardwareEncoderSupport: function () { + return true; + }, + + ContextCreate__deps: ['$UWEncoderType'], + ContextCreate: function (uid, encodeType) { + var context = { + id: uid, + refPtr: new Set(), + encodeType: UWEncoderType[encodeType] + }; + uwcom_addManageObj(context); + return context.managePtr; + }, + + ContextDestroy: function (uid) { + var contextPtrs = Object.keys(UWManaged).filter(function (contextPtr) { + return 'id' in UWManaged[contextPtr] && UWManaged[contextPtr].id === uid; + }); + if (contextPtrs.length > 1) { + console.error('ContextDestroy: multiple Contexts with the same id'); + } else if (!contextPtrs.length) { + console.error('ContextDestroy: There is no context with id = ' + uid.toString()); + } + contextPtrs.forEach(function (contextPtr) { + delete UWManaged[contextPtr]; + }); + }, + + SetCurrentContext: function (contextPtr) { + UWManaged["__currentContext__"] = UWManaged[contextPtr]; + }, + + // TODO + ContextAddRefPtr: function (contextPtr,ptr){ + if (!uwcom_existsCheck(contextPtr, "ContextDeleteRefPtr", "context")) return; + /** @type {{ refPtr: Set }} */ + var context = UWManaged[contextPtr]; + if(context && context.refPtr) + context.refPtr.add(ptr); + }, + + // TODO + ContextDeleteRefPtr: function(contextPtr,ptr){ + if (!uwcom_existsCheck(contextPtr, "ContextDeleteRefPtr", "context")) return; + /** @type {{ refPtr: Set }} */ + var context = UWManaged[contextPtr]; + if(context.refPtr) + context.refPtr.delete(ptr); + }, + + // TODO + ContextCreateAudioTrackSource: function(contextPtr){ + if (!uwcom_existsCheck(contextPtr, "ContextCreateAudioTrackSource", "context")) return; + const audioTrackSource = {}; + uwcom_addManageObj(audioTrackSource); + return audioTrackSource.managePtr; + }, + + // TODO + ContextCreateVideoTrackSource: function(contextPtr){ + if (!uwcom_existsCheck(contextPtr, "ContextCreateVideoTrackSource", "context")) return; + const videoTrackSource = {}; + uwcom_addManageObj(videoTrackSource); + return videoTrackSource.managePtr; + }, + + ContextGetEncoderType: function (contextPtr) { + if (!uwcom_existsCheck(contextPtr, 'ContextGetEncoderType', 'context')) return; + var context = UWManaged[contextPtr]; + var encodeTypeIdx = UWEncoderType.indexOf(context.encodeType); + return encodeTypeIdx; + }, + + ContextCreatePeerConnection: function (contextPtr, conf) { + if (!uwcom_existsCheck(contextPtr, 'ContextCreatePeerConnection', 'context')) return; + return _CreatePeerConnection(conf); + }, + + ContextCreatePeerConnectionWithConfig: function (contextPtr, confPtr) { + if (!uwcom_existsCheck(contextPtr, 'ContextCreatePeerConnectionWithConfig', 'context')) return; + return _CreatePeerConnectionWithConfig(confPtr); + }, + + ContextDeletePeerConnection: function (contextPtr, peerPtr) { + //if (!uwcom_existsCheck(contextPtr, 'ContextDeletePeerConnection', 'context')) return; + if (!uwcom_existsCheck(peerPtr, 'ContextDeletePeerConnection', 'peer')) return; + var peer = UWManaged[peerPtr]; + if (peer.readyState !== 'closed' || peer.signalingState !== 'closed') + peer.close(); + delete UWManaged[peerPtr]; + }, + + PeerConnectionSetLocalDescription: function (contextPtr, peerPtr, typeIdx, sdpPtr) { + if (!uwcom_existsCheck(contextPtr, 'PeerConnectionSetLocalDescription', 'context')) return 11; // OperationErrorWithData + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionSetLocalDescription', 'peer')) return 11; // OperationErrorWithData + return _PeerConnectionSetDescription(peerPtr, typeIdx, sdpPtr, 'Local'); + }, + + PeerConnectionSetRemoteDescription: function (contextPtr, peerPtr, typeIdx, sdpPtr) { + if (!uwcom_existsCheck(contextPtr, 'PeerConnectionSetRemoteDescription', 'context')) return 11; // OperationErrorWithData + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionSetRemoteDescription', 'peer')) return 11; // OperationErrorWithData + return _PeerConnectionSetDescription(peerPtr, typeIdx, sdpPtr, 'Remote'); + }, + + PeerConnectionSetLocalDescriptionWithoutDescription: function (contextPtr, peerPtr) { + if (!uwcom_existsCheck(contextPtr, 'PeerConnectionSetLocalDescriptionWithoutDescription', 'context')) return 11; // OperationErrorWithData + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionSetLocalDescriptionWithoutDescription', 'peer')) return 11; // OperationErrorWithData + return _PeerConnectionSetDescriptionWithoutDescription(peerPtr); + }, + + PeerConnectionRegisterOnSetSessionDescSuccess: function (contextPtr, peerPtr, OnSetSessionDescSuccess) { + if (!uwcom_existsCheck(contextPtr, 'PeerConnectionRegisterOnSetSessionDescSuccess', 'context')) return; + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionRegisterOnSetSessionDescSuccess', 'peer')) return; + uwevt_OnSetSessionDescSuccess = OnSetSessionDescSuccess; + }, + + PeerConnectionRegisterOnSetSessionDescFailure: function (contextPtr, peerPtr, OnSetSessionDescFailure) { + if (!uwcom_existsCheck(contextPtr, 'PeerConnectionRegisterOnSetSessionDescFailure', 'context')) return; + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionRegisterOnSetSessionDescFailure', 'peer')) return; + uwevt_OnSetSessionDescFailure = OnSetSessionDescFailure; + }, + + ContextCreateDataChannel: function (contextPtr, peerPtr, labelPtr, optionsJsonPtr) { + if (!uwcom_existsCheck(contextPtr, 'ContextCreateDataChannel', 'context')) return; + if (!uwcom_existsCheck(peerPtr, 'ContextCreateDataChannel', 'peer')) return; + return _CreateDataChannel(peerPtr, labelPtr, optionsJsonPtr); + }, + + ContextDeleteDataChannel: function (contextPtr, dataChannelPtr) { + //if (!uwcom_existsCheck(contextPtr, 'ContextDeleteDataChannel', 'context')) return; + if (!uwcom_existsCheck(dataChannelPtr, 'ContextDeleteDataChannel', 'dataChannel')) return; + delete UWManaged[dataChannelPtr]; + }, + + ContextCreateMediaStream: function (contextPtr, labelPtr) { + if (!uwcom_existsCheck(contextPtr, 'ContextCreateMediaStream', 'context')) return; + return _CreateMediaStream(labelPtr); + }, + + ContextDeleteMediaStream: function (contextPtr, streamPtr) { + //if (!uwcom_existsCheck(contextPtr, 'ContextDeleteMediaStream', 'context')) return; + if (!uwcom_existsCheck(streamPtr, 'ContextDeleteMediaStream', 'stream')) return; + _DeleteMediaStream(streamPtr); + }, + + ContextRegisterMediaStreamObserver: function (contextPtr, streamPtr) { + if (!uwcom_existsCheck(contextPtr, 'MediaStreamRegisterOnAddTrack', 'context')) return; + if (!uwcom_existsCheck(streamPtr, 'MediaStreamRegisterOnAddTrack', 'stream')) return; + + var stream = UWManaged[streamPtr]; + stream.onaddtrack = (function(evt) { + uwcom_addManageObj(evt.track); + Module.dynCall_vii(uwevt_MSOnAddTrack, stream.managePtr, evt.track.managePtr); + }); + stream.onremovetrack = (function(evt) { + if (!uwcom_existsCheck(evt.track.managePtr, "stream.onremovetrack", "track")) return; + Module.dynCall_vii(uwevt_MSOnRemoveTrack, stream.managePtr, evt.track.managePtr); + }); + }, + + ContextUnRegisterMediaStreamObserver: function (contextPtr, streamPtr) { + if (!uwcom_existsCheck(contextPtr, 'MediaStreamRegisterOnAddTrack', 'context')) return; + if (!uwcom_existsCheck(streamPtr, 'MediaStreamRegisterOnAddTrack', 'stream')) return; + var context = UWManaged[contextPtr]; + var stream = UWManaged[streamPtr]; + throw new Error("Not implemented"); + }, + + MediaStreamRegisterOnAddTrack: function (contextPtr, streamPtr, MediaStreamOnAddTrack) { + if (!uwcom_existsCheck(contextPtr, 'MediaStreamRegisterOnAddTrack', 'context')) return; + if (!uwcom_existsCheck(streamPtr, 'MediaStreamRegisterOnAddTrack', 'stream')) return; + var context = UWManaged[contextPtr]; + var stream = UWManaged[streamPtr]; + uwevt_MSOnAddTrack = MediaStreamOnAddTrack; + }, + + MediaStreamRegisterOnRemoveTrack: function (contextPtr, streamPtr, MediaStreamOnRemoveTrack) { + if (!uwcom_existsCheck(contextPtr, 'MediaStreamRegisterOnRemoveTrack', 'context')) return; + if (!uwcom_existsCheck(streamPtr, 'MediaStreamRegisterOnRemoveTrack', 'stream')) return; + var context = UWManaged[contextPtr]; + var stream = UWManaged[streamPtr]; + uwevt_MSOnRemoveTrack = MediaStreamOnRemoveTrack; + }, + + StatsCollectorRegisterCallback: function(onCollectStatsCallback) { + throw new Error("Not implemented"); + }, + CreateSessionDescriptionObserverRegisterCallback: function(nativeCreateSessionDescCallback) { + throw new Error("Not implemented"); + }, + SetLocalDescriptionObserverRegisterCallback: function(setLocalDescriptionCallback) { + throw new Error("Not implemented"); + }, + SetRemoteDescriptionObserverRegisterCallback: function(setRemoteDescriptionCallback) { + throw new Error("Not implemented"); + }, + SetTransformedFrameRegisterCallback: function(transformedFrameCallback) { + throw new Error("Not implemented"); + }, + + GetBatchUpdateEventFunc: function (contextPtr) { + if (!uwcom_existsCheck(contextPtr, 'GetBatchUpdateEventFunc', 'context')) return; + var context = UWManaged[contextPtr]; + throw new Error("Not implemented"); + return null; + }, + + GetBatchUpdateEventID: function () { + throw new Error("Not implemented"); + return -1; + }, + + AudioTrackAddSink: function(trackPtr, sinkPtr) { + if (!uwcom_existsCheck(trackPtr, 'AudioTrackAddSink', 'track')) return; + if (!uwcom_existsCheck(sinkPtr, 'AudioTrackAddSink', 'sink')) return; + var track = UWManaged[trackPtr]; + var sink = UWManaged[sinkPtr]; + throw new Error("Not implemented"); + }, + + AudioTrackRemoveSink: function(trackPtr, sinkPtr) { + if (!uwcom_existsCheck(trackPtr, 'AudioTrackRemoveSink', 'track')) return; + if (!uwcom_existsCheck(sinkPtr, 'AudioTrackRemoveSink', 'sink')) return; + var track = UWManaged[trackPtr]; + var sink = UWManaged[sinkPtr]; + throw new Error("Not implemented"); + }, + + AudioTrackSinkProcessAudio: function(sinkPtr, data, length, channels, sampleRate) { + if (!uwcom_existsCheck(sinkPtr, 'AudioTrackSinkProcessAudio', 'sink')) return; + var sink = UWManaged[sinkPtr]; + throw new Error("Not implemented"); + }, + + GetUpdateTextureFunc: function (contextPtr) { + if (!uwcom_existsCheck(contextPtr, 'GetUpdateTextureFunc', 'context')) return; + var context = UWManaged[contextPtr]; + throw new Error("Not implemented"); + }, + + ContextCreateAudioTrack: function (contextPtr, labelPtr, sourcePtr) { + if (!uwcom_existsCheck(contextPtr, 'ContextCreateAudioTrack', 'context')) return; + return _CreateAudioTrack(labelPtr, sourcePtr); + }, + + ContextCreateVideoTrack: function (contextPtr, srcTexturePtr, dstTexturePtr, width, height) { + if (!uwcom_existsCheck(contextPtr, 'ContextCreateVideoTrack', 'context')) return; + return _CreateVideoTrack(srcTexturePtr, dstTexturePtr, width, height); + }, + + ContextStopMediaStreamTrack: function (contextPtr, trackPtr) { + if (!uwcom_existsCheck(contextPtr, 'ContextStopMediaStreamTrack', 'context')) return; + if (!uwcom_existsCheck(trackPtr, 'ContextStopMediaStreamTrack', 'track')) return; + var track = UWManaged[trackPtr]; + track.stop(); + }, + + ContextDeleteMediaStreamTrack: function (contextPtr, trackPtr) { + if (!uwcom_existsCheck(trackPtr, 'ContextDeleteMediaStreamTrack', 'track')) return; + var track = UWManaged[trackPtr]; + + // Not sure how js garbage collection works, remove/disable/stop all attributes inside the track object? + if(track.kind === "video"){ + if(uwcom_localVideoTracks[trackPtr]){ + delete uwcom_localVideoTracks[trackPtr]; + } + + if(uwcom_remoteVideoTracks[trackPtr]){ + uwcom_remoteVideoTracks[trackPtr].video.remove(); + uwcom_remoteVideoTracks[trackPtr].track.stop(); + delete uwcom_remoteVideoTracks[trackPtr]; + } + } + delete UWManaged[trackPtr]; + }, + + ContextRegisterAudioReceiveCallback: function (contextPtr, trackPtr, AudioTrackOnReceive){ + if (!uwcom_existsCheck(contextPtr, 'ContextRegisterAudioReceiveCallback', 'context')) return; + if (!uwcom_existsCheck(trackPtr, 'ContextRegisterAudioReceiveCallback', 'track')) return; + var context = UWManaged[contextPtr]; + var track = UWManaged[trackPtr]; + throw new Error("Not implemented"); + }, + + ContextUnregisterAudioReceiveCallback: function (contextPtr, trackPtr){ + if (!uwcom_existsCheck(contextPtr, 'ContextUnregisterAudioReceiveCallback', 'context')) return; + if (!uwcom_existsCheck(trackPtr, 'ContextUnregisterAudioReceiveCallback', 'track')) return; + var context = UWManaged[contextPtr]; + var track = UWManaged[trackPtr]; + throw new Error("Not implemented"); + }, + + // CreateVideoRenderer: function(contextPtr) { + + // }, + + // DeleteVideoRenderer: function(contextPtr, sinkPtr) { + + // }, + + ContextDeleteStatsReport: function (contextPtr, reportPtr) { + //if (!uwcom_existsCheck(contextPtr, 'ContextDeleteStatsReport', 'context')) return; + if (!uwcom_existsCheck(reportPtr, 'ContextDeleteStatsReport', 'report')) return; + delete UWManaged[reportPtr]; + }, + + // ContextSetVideoEncoderParameter: function(trackPtr, width, height, format, texturePtr) { + + // }, + + // GetInitializationResult: function(contextPtr, trackPtr) { + + // }, + + $UWContextGetCapabilities: function (senderReceiver, kindIdx) { + var kind = UWMediaStreamTrackKind[kindIdx]; + var capabilities = {codecs:[], headerExtensions: []}; + const supportsSetCodecPreferences = window.RTCRtpTransceiver && 'setCodecPreferences' in window.RTCRtpTransceiver.prototype; + if(supportsSetCodecPreferences) capabilities = senderReceiver.getCapabilities(kind); + var capabilitiesJson = JSON.stringify(capabilities); + var capabilitiesJsonPtr = uwcom_strToPtr(capabilitiesJson); + return capabilitiesJsonPtr; + }, + + ContextGetSenderCapabilities: function (contextPtr, kindIdx) { + if (!uwcom_existsCheck(contextPtr, 'ContextGetSenderCapabilities', 'context')) return; + return UWContextGetCapabilities(RTCRtpSender, kindIdx); + }, + + ContextGetReceiverCapabilities: function (contextPtr, kindIdx) { + if (!uwcom_existsCheck(contextPtr, 'ContextGetReceiverCapabilities', 'context')) return; + return UWContextGetCapabilities(RTCRtpReceiver, kindIdx); + } +}; +autoAddDeps(UnityWebRTCContext, '$UWContextGetCapabilities'); +mergeInto(LibraryManager.library, UnityWebRTCContext); \ No newline at end of file diff --git a/Runtime/Plugins/WebGL/Context.jslib.meta b/Runtime/Plugins/WebGL/Context.jslib.meta new file mode 100644 index 0000000000..3c1c80389d --- /dev/null +++ b/Runtime/Plugins/WebGL/Context.jslib.meta @@ -0,0 +1,44 @@ +fileFormatVersion: 2 +guid: c48860db3eea72b4ea1296b43edd93dc +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 1 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 0 + Exclude Win: 1 + Exclude Win64: 1 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + WebGL: WebGL + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Plugins/WebGL/Enum.jslib b/Runtime/Plugins/WebGL/Enum.jslib new file mode 100644 index 0000000000..d385dbab03 --- /dev/null +++ b/Runtime/Plugins/WebGL/Enum.jslib @@ -0,0 +1,261 @@ +var UnityWebRTCEnum = { + // WebRTC + $UWRTCSdpSemantics: [ // Not defined in the API + 'plan-b', + 'unified-plan' + ], + $UWRTCIceCredentialType: [ + 'password', + 'oauth' + ], + $UWRTCIceTransportPolicy: [ + '', + 'relay', + '', + 'all' + ], + $UWRTCBundlePolicy: [ + 'balanced', + 'max-compat', + 'max-bundle' + ], + $UWRTCRtcpMuxPolicy: [ + 'require' + ], + $UWRTCSignalingState: [ + 'stable', + 'have-local-offer', + 'have-local-pranswer', + 'have-remote-offer', + 'have-remote-pranswer', + 'closed' + ], + $UWRTCIceGatheringState: [ + 'new', + 'gathering', + 'complete' + ], + $UWRTCPeerConnectionState: [ + 'new', + 'connecting', + 'connected', + 'disconnected', + 'failed', + 'closed' + ], + $UWRTCIceConnectionState: [ + 'new', + 'checking', + 'connected', + 'completed', + 'failed', + 'disconnected', + 'closed', + 'max' + ], + $UWRTCSdpType: [ + 'offer', + 'pranswer', + 'answer', + 'rollback' + ], + $UWRTCIceProtocol: [ + 'udp', + 'tcp' + ], + $UWRTCIceTcpCandidateType: [ + 'active', + 'passive', + 'so' + ], + $UWRTCIceCandidateType: [ + 'host', + 'srflx', + 'prflx', + 'relay' + ], + $UWRTCRtpTransceiverDirection: [ + 'sendrecv', + 'sendonly', + 'recvonly', + 'inactive', + 'stopped' + ], + $UWRTCDtlsTransportState: [ + 'new', + 'connecting', + 'connected', + 'closed', + 'failed' + ], + $UWRTCIceGathererState: [ + 'new', + 'gathering', + 'complete' + ], + $UWRTCIceTransportState: [ + 'new', + 'checking', + 'connected', + 'completed', + 'disconnected', + 'failed', + 'closed' + ], + $UWRTCIceRole: [ + 'unknown', + 'controlling', + 'controlled' + ], + $UWRTCIceComponent: [ + 'none', + 'rtp', + 'rtcp' + ], + $UWRTCSctpTransportState: [ + 'connecting', + 'connected', + 'closed' + ], + $UWRTCDataChannelState: [ + 'connecting', + 'open', + 'closing', + 'closed' + ], + $UWRTCErrorDetailType: [ + 'data-channel-failure', + 'dtls-failure', + 'fingerprint-failure', + 'hardware-encoder-error', + 'hardware-encoder-not-available', + 'sdp-syntax-error', + 'sctp-failure' + ], + // Media Capture and Streams + $UWMediaStreamTrackState: [ + 'live', + 'ended' + ], + $UWVideoFacingModeEnum: [ + 'user', + 'environment', + 'left', + 'right' + ], + $UWVideoResizeModeEnum: [ + 'none', + 'crop-and-scale' + ], + $UWMediaStreamTrackKind: [ // Not defined in the API + 'audio', + 'video' + ], + $UWMediaDeviceKind: [ + 'audioinput', + 'audiooutput', + 'videoinput' + ], + // Identifiers for WebRTC's Statistics + $UWRTCStatsType: [ + 'codec', + 'inbound-rtp', + 'outbound-rtp', + 'remote-inbound-rtp', + 'remote-outbound-rtp', + 'media-source', + 'csrc', + 'peer-connection', + 'data-channel', + 'stream', + 'track', + 'transceiver', + 'sender', + 'receiver', + 'transport', + 'sctp-transport', + 'candidate-pair', + 'local-candidate', + 'remote-candidate', + 'certificate', + 'ice-server' + ], + $UWRTCCodecType: [ + 'encode', + 'decode' + ], + $UWRTCQualityLimitationReason: [ + 'none', + 'cpu', + 'bandwidth', + 'other' + ], + $UWRTCStatsIceCandidatePairState: [ + 'frozen', + 'waiting', + 'in-progress', + 'failed', + 'succeeded' + ], + // WebRTC Priority Control + $UWRTCPriorityType: [ + 'very-low', + 'low', + 'medium', + 'high' + ], + // Unity WebRTC + $UWEncoderType: [ + 'software', + 'hardware' + ], + $UWRTCErrorType: [ + 'None', + 'UnsupportedOperation', + 'UnsupportedParameter', + 'InvalidParameter', + 'InvalidRange', + 'SyntaxError', + 'InvalidState', + 'InvalidModification', + 'NetworkError', + 'ResourceExhausted', + 'InternalError', + 'OperationErrorWithData' + ] +} +autoAddDeps(UnityWebRTCEnum, '$UWRTCSdpSemantics'); +autoAddDeps(UnityWebRTCEnum, '$UWRTCIceCredentialType'); +autoAddDeps(UnityWebRTCEnum, '$UWRTCIceTransportPolicy'); +autoAddDeps(UnityWebRTCEnum, '$UWRTCBundlePolicy'); +autoAddDeps(UnityWebRTCEnum, '$UWRTCRtcpMuxPolicy'); +autoAddDeps(UnityWebRTCEnum, '$UWRTCSignalingState'); +autoAddDeps(UnityWebRTCEnum, '$UWRTCIceGatheringState'); +autoAddDeps(UnityWebRTCEnum, '$UWRTCPeerConnectionState'); +autoAddDeps(UnityWebRTCEnum, '$UWRTCIceConnectionState'); +autoAddDeps(UnityWebRTCEnum, '$UWRTCSdpType'); +autoAddDeps(UnityWebRTCEnum, '$UWRTCIceProtocol'); +autoAddDeps(UnityWebRTCEnum, '$UWRTCIceTcpCandidateType'); +autoAddDeps(UnityWebRTCEnum, '$UWRTCIceCandidateType'); +autoAddDeps(UnityWebRTCEnum, '$UWRTCRtpTransceiverDirection'); +autoAddDeps(UnityWebRTCEnum, '$UWRTCDtlsTransportState'); +autoAddDeps(UnityWebRTCEnum, '$UWRTCIceGathererState'); +autoAddDeps(UnityWebRTCEnum, '$UWRTCIceTransportState'); +autoAddDeps(UnityWebRTCEnum, '$UWRTCIceRole'); +autoAddDeps(UnityWebRTCEnum, '$UWRTCIceComponent'); +autoAddDeps(UnityWebRTCEnum, '$UWRTCSctpTransportState'); +autoAddDeps(UnityWebRTCEnum, '$UWRTCDataChannelState'); +autoAddDeps(UnityWebRTCEnum, '$UWRTCErrorDetailType'); +autoAddDeps(UnityWebRTCEnum, '$UWMediaStreamTrackState'); +autoAddDeps(UnityWebRTCEnum, '$UWVideoFacingModeEnum'); +autoAddDeps(UnityWebRTCEnum, '$UWVideoResizeModeEnum'); +autoAddDeps(UnityWebRTCEnum, '$UWMediaStreamTrackKind'); +autoAddDeps(UnityWebRTCEnum, '$UWMediaDeviceKind'); +autoAddDeps(UnityWebRTCEnum, '$UWRTCStatsType'); +autoAddDeps(UnityWebRTCEnum, '$UWRTCCodecType'); +autoAddDeps(UnityWebRTCEnum, '$UWRTCQualityLimitationReason'); +autoAddDeps(UnityWebRTCEnum, '$UWRTCStatsIceCandidatePairState'); +autoAddDeps(UnityWebRTCEnum, '$UWRTCPriorityType'); +autoAddDeps(UnityWebRTCEnum, '$UWEncoderType'); +autoAddDeps(UnityWebRTCEnum, '$UWRTCErrorType'); +mergeInto(LibraryManager.library, UnityWebRTCEnum); \ No newline at end of file diff --git a/Runtime/Plugins/WebGL/Enum.jslib.meta b/Runtime/Plugins/WebGL/Enum.jslib.meta new file mode 100644 index 0000000000..ac44328133 --- /dev/null +++ b/Runtime/Plugins/WebGL/Enum.jslib.meta @@ -0,0 +1,44 @@ +fileFormatVersion: 2 +guid: 07f342ccd20fa214c9e2477f5dc681c3 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 1 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 0 + Exclude Win: 1 + Exclude Win64: 1 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + WebGL: WebGL + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Plugins/WebGL/MediaStream.jslib b/Runtime/Plugins/WebGL/MediaStream.jslib new file mode 100644 index 0000000000..0b44e153a3 --- /dev/null +++ b/Runtime/Plugins/WebGL/MediaStream.jslib @@ -0,0 +1,149 @@ +var UnityWebRTCMediaStream = { + + // Note: MediaStream creates a read-only id field, so we cannot set it. + // Using custom 'guid' field instead. + CreateMediaStream: function (labelPtr) { + var label = UTF8ToString(labelPtr); + var stream = new MediaStream(); + stream.guid = label; + stream.onaddtrack = function (evt) { + uwcom_addManageObj(evt.track); + console.log('stream.ontrack' + evt.track.managePtr); + Module.dynCall_vii(uwevt_MSOnAddTrack, this.managePtr, evt.track.managePtr); + }; + stream.onremovetrack = function (evt) { + if (!evt.track.managePtr) { + console.warn('track does not own managePtr'); + return; + } + if (!uwcom_existsCheck(evt.track.managePtr, 'stream.onremovetrack', 'track')) return; + Module.dynCall_vii(uwevt_MSOnRemoveTrack, this.managePtr, evt.track.managePtr); + }; + + uwcom_addManageObj(stream); + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'CreateMediaStream', stream.managePtr); + return stream.managePtr; + }, + + MediaStreamAddUserMedia: function (streamPtr, constraints){ + if (!uwcom_existsCheck(streamPtr, 'MediaStreamAddUserMedia', 'stream')) return; + uwcom_debugLog('log', 'MediaStream.jslib', 'AddUserMedia', streamPtr); + + var stream = UWManaged[streamPtr]; + var optionsJson = UTF8ToString(constraints); + var options = JSON.parse(optionsJson); + + navigator.mediaDevices.getUserMedia(options) + .then(function(usermedia){ + usermedia.getTracks().forEach(function(track){ + uwcom_addManageObj(track); + _MediaStreamAddTrack(stream.managePtr, track.managePtr); + }) + }) + .catch(function(err) { + console.error(err); + }); + }, + + DeleteMediaStream: function(streamPtr) { + var stream = UWManaged[streamPtr]; + stream.getTracks().forEach(function(track) { + track.stop(); + stream.removeTrack(track); + track = null; + }); + stream = null; + delete UWManaged[streamPtr]; + }, + + MediaStreamGetID: function (streamPtr) { + if (!uwcom_existsCheck(streamPtr, 'MediaStreamGetID', 'stream')) return; + var stream = UWManaged[streamPtr]; + var id = stream.guid || stream.id; + var streamIdPtr = uwcom_strToPtr(id); + return streamIdPtr; + }, + + MediaStreamGetVideoTracks: function (streamPtr) { + if (!uwcom_existsCheck(streamPtr, 'MediaStreamGetVideoTracks', 'stream')) return; + var stream = UWManaged[streamPtr]; + var tracks = stream.getVideoTracks(); + var ptrs = []; + tracks.forEach(function (track) { + uwcom_addManageObj(track); + ptrs.push(track.managePtr); + }); + var ptr = uwcom_arrayToReturnPtr(ptrs, Int32Array); + return ptr; + }, + + MediaStreamGetAudioTracks: function (streamPtr) { + if (!uwcom_existsCheck(streamPtr, 'MediaStreamGetAudioTracks', 'stream')) return; + var stream = UWManaged[streamPtr]; + var tracks = stream.getAudioTracks(); + var ptrs = []; + tracks.forEach(function (track) { + uwcom_addManageObj(track); + ptrs.push(track.managePtr); + }); + var ptr = uwcom_arrayToReturnPtr(ptrs, Int32Array); + return ptr; + }, + + MediaStreamAddTrack: function (streamPtr, trackPtr) { + if (!uwcom_existsCheck(streamPtr, 'MediaStreamAddTrack', 'stream')) return; + if (!uwcom_existsCheck(trackPtr, 'MediaStreamAddTrack', 'track')) return; + var stream = UWManaged[streamPtr]; + var track = UWManaged[trackPtr]; + try { + stream.addTrack(track); + Module.dynCall_vii(uwevt_MSOnAddTrack, stream.managePtr, track.managePtr); + return true; + } catch(err){ + return false; + } + // try { + // console.log('MediaStreamAddTrack:' + streamPtr + ':' + trackPtr); + // stream.addTrack(track); + // var video = document.createElement('video'); + // video.id = 'video_' + track.managePtr.toString(); + // video.muted = true; + // //video.style.display = 'none'; + // video.srcObject = stream; + // document.body.appendChild(video); + // video.style.width = '300px'; + // video.style.height = '200px'; + // video.style.position = 'absolute'; + // video.style.left = video.style.top = 0; + // uwcom_remoteVideoTracks[track.managePtr] = { + // track: track, + // video: video, + // playing: false + // }; + // video.onplaying = function(){ + // uwcom_remoteVideoTracks[track.managePtr].playing = true; + // } + // video.play(); + // Module.dynCall_vii(uwevt_MSOnAddTrack, stream.managePtr, track.managePtr); + // return true; + // } catch (err) { + // console.log('MediaStreamAddTrack: ' + err.message); + // return false; + // } + }, + + MediaStreamRemoveTrack: function (streamPtr, trackPtr) { + if (!uwcom_existsCheck(streamPtr, 'MediaStreamRemoveTrack', 'stream')) return; + if (!uwcom_existsCheck(trackPtr, 'MediaStreamRemoveTrack', 'track')) return; + var stream = UWManaged[streamPtr]; + var track = UWManaged[trackPtr]; + try { + stream.removeTrack(track); + return true; + } catch (err) { + console.log('MediaStreamRemoveTrack: ' + err.message); + return false; + } + } +}; +mergeInto(LibraryManager.library, UnityWebRTCMediaStream); \ No newline at end of file diff --git a/Runtime/Plugins/WebGL/MediaStream.jslib.meta b/Runtime/Plugins/WebGL/MediaStream.jslib.meta new file mode 100644 index 0000000000..71357073a7 --- /dev/null +++ b/Runtime/Plugins/WebGL/MediaStream.jslib.meta @@ -0,0 +1,44 @@ +fileFormatVersion: 2 +guid: 257addfc1fbef704184dfac8b8d18ad9 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 1 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 0 + Exclude Win: 1 + Exclude Win64: 1 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + WebGL: WebGL + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Plugins/WebGL/MediaStreamTrack.jslib b/Runtime/Plugins/WebGL/MediaStreamTrack.jslib new file mode 100644 index 0000000000..112608696c --- /dev/null +++ b/Runtime/Plugins/WebGL/MediaStreamTrack.jslib @@ -0,0 +1,34 @@ +var UnityWebRTCMediaStreamTrack = { + MediaStreamTrackGetEnabled: function (trackPtr) { + if (!uwcom_existsCheck(trackPtr, 'MediaStreamTrackGetEnabled', 'track')) return; + var track = UWManaged[trackPtr]; + return track.enabled; + }, + + MediaStreamTrackSetEnabled: function (trackPtr, enabled) { + if (!uwcom_existsCheck(trackPtr, 'MediaStreamTrackSetEnabled', 'track')) return; + var track = UWManaged[trackPtr]; + track.enabled = !!enabled; + }, + + MediaStreamTrackGetReadyState: function (trackPtr) { + if (!uwcom_existsCheck(trackPtr, 'MediaStreamTrackGetReadyState', 'track')) return; + var track = UWManaged[trackPtr]; + return UWMediaStreamTrackState.indexOf(track.readyState); + }, + + MediaStreamTrackGetKind: function (trackPtr) { + if (!uwcom_existsCheck(trackPtr, 'MediaStreamTrackGetKind', 'track')) return; + var track = UWManaged[trackPtr]; + return UWMediaStreamTrackKind.indexOf(track.kind); + }, + + MediaStreamTrackGetID: function (trackPtr) { + if (!uwcom_existsCheck(trackPtr, 'MediaStreamTrackGetID', 'track')) return; + var track = UWManaged[trackPtr]; + var id = track.guid || track.id; + var idPtr = uwcom_strToPtr(id); + return idPtr; + } +}; +mergeInto(LibraryManager.library, UnityWebRTCMediaStreamTrack); \ No newline at end of file diff --git a/Runtime/Plugins/WebGL/MediaStreamTrack.jslib.meta b/Runtime/Plugins/WebGL/MediaStreamTrack.jslib.meta new file mode 100644 index 0000000000..843b040fdc --- /dev/null +++ b/Runtime/Plugins/WebGL/MediaStreamTrack.jslib.meta @@ -0,0 +1,44 @@ +fileFormatVersion: 2 +guid: 00b85dc7de0a51d44b9388fb29b6cdca +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 1 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 0 + Exclude Win: 1 + Exclude Win64: 1 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + WebGL: WebGL + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Plugins/WebGL/RTCDataChannel.jslib b/Runtime/Plugins/WebGL/RTCDataChannel.jslib new file mode 100644 index 0000000000..a61f8a6a3c --- /dev/null +++ b/Runtime/Plugins/WebGL/RTCDataChannel.jslib @@ -0,0 +1,163 @@ +var UnityWebRTCDataChannel = { + + CreateDataChannel: function(peerPtr, labelPtr, optionsJsonPtr) { + /** @type {RTCPeerConnection} */ + var peer = UWManaged[peerPtr]; + var label = UTF8ToString(labelPtr); + var optionsJson = UTF8ToString(optionsJsonPtr); + var options = JSON.parse(optionsJson); + + // Firefox doesn't like null values, so remove them. + if(options.ordered.hasValue) options.ordered = options.ordered.value; + else delete options.ordered; + if(options.maxRetransmits.hasValue) options.maxRetransmits = options.maxRetransmits.value; + else delete options.maxRetransmits; + if(options.maxRetransmitTime.hasValue) options.maxRetransmitTime = options.maxRetransmitTime.value; + else delete options.maxRetransmitTime; + if(options.negotiated.hasValue) options.negotiated = options.negotiated.value; + else delete options.negotiated; + if(options.id.hasValue) options.id = options.id.value; + else delete options.id; + + // Chrome (incorrectly?) accept maxRetransmits and maxRetransmitTime being set + if (options.maxRetransmits && options.maxRetransmitTime) return 0; + + try { + var dataChannel = peer.createDataChannel(label, options); + } + catch(err){ + console.log(err); + return 0; + } + + dataChannel.onmessage = function (evt) { + if (typeof evt.data === 'string') { + var msgPtr = uwcom_strToPtr(evt.data); + Module.dynCall_vii(uwevt_DCOnTextMessage, this.managePtr, msgPtr); + } else { + var msgPtr = uwcom_arrayToReturnPtr(evt.data, Uint8Array); + Module.dynCall_viii(uwevt_DCOnBinaryMessage, this.managePtr, msgPtr + 4, evt.data.byteLength); + } + }; + dataChannel.onopen = function (evt) { + if (!uwcom_existsCheck(this.managePtr, "onopen", "dataChannel")) return; + Module.dynCall_vi(uwevt_DCOnOpen, this.managePtr); + }; + dataChannel.onclose = function (evt) { + if (!uwcom_existsCheck(this.managePtr, "onclose", "dataChannel")) return; + Module.dynCall_vi(uwevt_DCOnClose, this.managePtr); + }; + dataChannel.onerror = function (evt) { + if (!uwcom_existsCheck(this.managePtr, "onerror", "dataChannel")) return; + Module.dynCall_vi(uwevt_DCOnError, this.managePtr); + }; + uwcom_addManageObj(dataChannel); + return dataChannel.managePtr; + }, + + DataChannelGetID: function (dataChannelPtr) { + if (!uwcom_existsCheck(dataChannelPtr, 'DataChannelGetID', 'dataChannel')) return; + var dataChannel = UWManaged[dataChannelPtr]; + if(dataChannel.id === null) return -1; + return dataChannel.id; + }, + + DataChannelGetLabel: function (dataChannelPtr) { + if (!uwcom_existsCheck(dataChannelPtr, 'DataChannelGetLabel', 'dataChannel')) return; + var dataChannel = UWManaged[dataChannelPtr]; + var labelPtr = uwcom_strToPtr(dataChannel.label); + return labelPtr; + }, + + DataChannelGetProtocol: function (dataChannelPtr) { + if (!uwcom_existsCheck(dataChannelPtr, 'DataChannelGetProtocol', 'dataChannel')) return; + var dataChannel = UWManaged[dataChannelPtr]; + var protocolPtr = uwcom_strToPtr(dataChannel.protocol); + return protocolPtr; + }, + + DataChannelGetMaxRetransmits: function (dataChannelPtr) { + if (!uwcom_existsCheck(dataChannelPtr, 'DataChannelGetMaxRetransmits', 'dataChannel')) return; + var dataChannel = UWManaged[dataChannelPtr]; + if(dataChannel.maxRetransmits === null) return -1; + return dataChannel.maxRetransmits; + }, + + DataChannelGetMaxRetransmitTime: function (dataChannelPtr) { + if (!uwcom_existsCheck(dataChannelPtr, 'DataChannelGetMaxRetransmitTime', 'dataChannel')) return; + var dataChannel = UWManaged[dataChannelPtr]; + if(dataChannel.maxPacketLifeTime === null) return -1; + return dataChannel.maxPacketLifeTime; + }, + + DataChannelGetOrdered: function (dataChannelPtr) { + if (!uwcom_existsCheck(dataChannelPtr, 'DataChannelGetOrdered', 'dataChannel')) return; + var dataChannel = UWManaged[dataChannelPtr]; + return dataChannel.ordered; + }, + + DataChannelGetBufferedAmount: function (dataChannelPtr) { + if (!uwcom_existsCheck(dataChannelPtr, 'DataChannelGetBufferedAmount', 'dataChannel')) return; + var dataChannel = UWManaged[dataChannelPtr]; + return dataChannel.bufferedAmount; + }, + + DataChannelGetNegotiated: function (dataChannelPtr) { + if (!uwcom_existsCheck(dataChannelPtr, 'DataChannelGetNegotiated', 'dataChannel')) return; + var dataChannel = UWManaged[dataChannelPtr]; + return dataChannel.negotiated; + }, + + DataChannelGetReadyState: function (dataChannelPtr) { + if (!uwcom_existsCheck(dataChannelPtr, 'DataChannelGetReadyState', 'dataChannel')) return; + var dataChannel = UWManaged[dataChannelPtr]; + var readyStateIdx = UWRTCDataChannelState.indexOf(dataChannel.readyState); + return readyStateIdx; + }, + + DataChannelRegisterOnMessage: function (contextPtr, dataChannelPtr, DataChannelNativeOnMessage) { + if (!uwcom_existsCheck(dataChannelPtr, 'DataChannelRegisterOnMessage', 'dataChannel')) return; + uwevt_DCOnBinaryMessage = DataChannelNativeOnMessage; + }, + + DataChannelRegisterOnTextMessage: function (contextPtr, dataChannelPtr, DataChannelNativeOnTextMessage) { + if (!uwcom_existsCheck(dataChannelPtr, 'DataChannelRegisterOnTextMessage', 'dataChannel')) return; + uwevt_DCOnTextMessage = DataChannelNativeOnTextMessage; + }, + + DataChannelRegisterOnError: function (contextPtr, dataChannelPtr, DataChannelNativeOnClose) { + if (!uwcom_existsCheck(dataChannelPtr, 'DataChannelRegisterOnError', 'dataChannel')) return; + uwevt_DCOnError = DataChannelNativeOnClose; + }, + + DataChannelRegisterOnOpen: function (contextPtr, dataChannelPtr, DataChannelNativeOnOpen) { + if (!uwcom_existsCheck(dataChannelPtr, 'DataChannelRegisterOnOpen', 'dataChannel')) return; + uwevt_DCOnOpen = DataChannelNativeOnOpen; + }, + + DataChannelRegisterOnClose: function (contextPtr, dataChannelPtr, DataChannelNativeOnClose) { + if (!uwcom_existsCheck(dataChannelPtr, 'DataChannelRegisterOnClose', 'dataChannel')) return; + uwevt_DCOnClose = DataChannelNativeOnClose; + }, + + DataChannelSend: function (dataChannelPtr, textMsgPtr) { + if (!uwcom_existsCheck(dataChannelPtr, 'DataChannelSend', 'dataChannel')) return; + var dataChannel = UWManaged[dataChannelPtr]; + var textMsg = UTF8ToString(textMsgPtr); + dataChannel.send(textMsg); + }, + + DataChannelSendBinary: function (dataChannelPtr, binaryMsgPtr, size) { + if (!uwcom_existsCheck(dataChannelPtr, 'DataChannelSendBinary', 'dataChannel')) return; + var dataChannel = UWManaged[dataChannelPtr]; + var binaryMsg = HEAPU8.subarray(binaryMsgPtr, binaryMsgPtr + size); + dataChannel.send(binaryMsg); + }, + + DataChannelClose: function (dataChannelPtr) { + if (!uwcom_existsCheck(dataChannelPtr, 'DataChannelClose', 'dataChannel')) return; + var dataChannel = UWManaged[dataChannelPtr]; + dataChannel.close(); + } +}; +mergeInto(LibraryManager.library, UnityWebRTCDataChannel); \ No newline at end of file diff --git a/Runtime/Plugins/WebGL/RTCDataChannel.jslib.meta b/Runtime/Plugins/WebGL/RTCDataChannel.jslib.meta new file mode 100644 index 0000000000..2e1edfc67f --- /dev/null +++ b/Runtime/Plugins/WebGL/RTCDataChannel.jslib.meta @@ -0,0 +1,44 @@ +fileFormatVersion: 2 +guid: 32308ed992b14bf449be4e88101b706e +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 1 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 0 + Exclude Win: 1 + Exclude Win64: 1 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + WebGL: WebGL + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Plugins/WebGL/RTCIceCandidate.jslib b/Runtime/Plugins/WebGL/RTCIceCandidate.jslib new file mode 100644 index 0000000000..dbea81411d --- /dev/null +++ b/Runtime/Plugins/WebGL/RTCIceCandidate.jslib @@ -0,0 +1,57 @@ +var UnityWebRTCIceCandidate = { + CreateNativeRTCIceCandidate: function (candPtr, sdpMidPtr, sdpMLineIndex) { + var cand = UTF8ToString(candPtr); + var sdpMid = UTF8ToString(sdpMidPtr); + var candidate = new RTCIceCandidate({ + candidate: cand, + sdpMid: sdpMid, + sdpMLineIndex: sdpMLineIndex + }); + uwcom_addManageObj(candidate); + return candidate.managePtr; + }, + + IceCandidateGetCandidate: function (candPtr){ + if (!uwcom_existsCheck(candPtr, "IceCandidateGetCandidate", "iceCandidate")) return; + var candidate = UWManaged[candPtr]; + var ret = {}; + ret.candidate = candidate.candidate; + ret.component = UWRTCIceComponent.indexOf(candidate.component); + ret.foundation = candidate.foundation; + ret.ip = candidate.ip; + ret.port = candidate.port; + ret.priority = candidate.priority; + ret.address = candidate.address; + ret.protocol = candidate.protocol; + ret.relatedAddress = candidate.relatedAddress; // TODO: https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidate/relatedAddress - Is null for host candidates + ret.sdpMid = candidate.sdpMid; + ret.sdpMLineIndex = candidate.sdpMLineIndex; + ret.tcpType = candidate.tcpType; + ret.type = candidate.type; + ret.usernameFragment = candidate.usernameFragment; //TODO: https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidate/usernameFragment - This can be null? + var json = JSON.stringify(ret); + return uwcom_strToPtr(json); + }, + + IceCandidateGetSdp: function(candidatePtr){ + if (!uwcom_existsCheck(candidatePtr, "IceCandidateGetSdp", "iceCandidate")) return; + candidate = UWManaged[candidatePtr]; + return uwcom_strToPtr(candidate.candidate); + }, + + IceCandidateGetSdpMid: function(candidatePtr){ + if (!uwcom_existsCheck(candidatePtr, "IceCandidateGetSdpMid", "iceCandidate")) return; + return uwcom_strToPtr(candidate.sdpMid); + }, + + IceCandidateGetSdpLineIndex: function(candidatePtr){ + if (!uwcom_existsCheck(candidatePtr, "IceCandidateGetSdpMid", "iceCandidate")) return; + return candidate.sdpMLineIndex; + }, + + DeleteIceCandidate: function (candidatePtr) { + if (!uwcom_existsCheck(candidatePtr, 'DeleteIceCandidate', 'iceCandidate')) return; + delete UWManaged[candidatePtr]; + } +}; +mergeInto(LibraryManager.library, UnityWebRTCIceCandidate); \ No newline at end of file diff --git a/Runtime/Plugins/WebGL/RTCIceCandidate.jslib.meta b/Runtime/Plugins/WebGL/RTCIceCandidate.jslib.meta new file mode 100644 index 0000000000..c2006b4cde --- /dev/null +++ b/Runtime/Plugins/WebGL/RTCIceCandidate.jslib.meta @@ -0,0 +1,44 @@ +fileFormatVersion: 2 +guid: f295fbc53955655439b7db3ed0018597 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 1 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 0 + Exclude Win: 1 + Exclude Win64: 1 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + WebGL: WebGL + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Plugins/WebGL/RTCPeerConnection.jslib b/Runtime/Plugins/WebGL/RTCPeerConnection.jslib new file mode 100644 index 0000000000..658884a60a --- /dev/null +++ b/Runtime/Plugins/WebGL/RTCPeerConnection.jslib @@ -0,0 +1,695 @@ +var UnityWebRTCPeerConnection = { + CreatePeerConnection: function (conf) { + var label = ''; + if (conf) { + label = conf.label; + delete conf.label; + } + + //debugger; + conf = conf || {}; + + try { + var peer = new RTCPeerConnection(conf); + } catch (err) { + return 0; + } + peer.label = label; + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'CreatePeerConnection', 'create peer: ' + peer.label); + peer.onicecandidate = function (evt) { + var cnd = evt.candidate; + // TODO evt.candidate === null (candidate collect end) + if (cnd) { + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'onicecandidate', JSON.stringify(evt.candidate.toJSON())); + uwcom_addManageObj(cnd); + var candidatePtr = uwcom_strToPtr(cnd.candidate); + var sdpMidPtr = uwcom_strToPtr(cnd.sdpMid); + Module.dynCall_viiiii(uwevt_PCOnIceCandidate, peer.managePtr, cnd.managePtr, candidatePtr, sdpMidPtr, cnd.sdpMLineIndex); + } + }; + peer.oniceconnectionstatechange = function (evt) { + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'oniceconnectionstatechange', this.label + ':' + this.iceConnectionState); + var idx = UWRTCIceConnectionState.indexOf(this.iceConnectionState); + if (idx === -1) { + console.error('unknown iceConnectionState: "' + this.iceConnectionState + '"'); + } + Module.dynCall_vii(uwevt_PCOnIceConnectionChange, peer.managePtr, idx); + }; + peer.onconnectionstatechange = function (evt) { + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'onconnectionstatechange', this.label + ':' + this.connectionState); + var idx = UWRTCPeerConnectionState.indexOf(this.connectionState); + Module.dynCall_vii(uwevt_PCOnConnectionStateChange, peer.managePtr, idx); + }; + peer.onicegatheringstatechange = function (evt) { + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'onicegatheringstatechange', this.label + ':' + this.iceGatheringState); + var idx = UWRTCIceGatheringState.indexOf(this.iceGatheringState); + if (idx === -1) { + console.error('unknown iceGatheringState: "' + this.iceGatheringState + '"'); + } + Module.dynCall_vii(uwevt_PCOnIceGatheringChange, peer.managePtr, idx); + }; + peer.onnegotiationneeded = function (evt) { + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'onnegotiationneeded', this.label); + Module.dynCall_vi(uwevt_PCOnNegotiationNeeded, peer.managePtr); + }; + peer.ondatachannel = function (evt) { + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'ondatachannel', this.label + ':' + evt.channel.label); + var channel = evt.channel; + channel.onmessage = (function (evt) { + if (typeof evt.data === "string") { + var msgPtr = uwcom_strToPtr(evt.data); + Module.dynCall_vii(uwevt_DCOnTextMessage, channel.managePtr, msgPtr); + } else { + var msgPtr = uwcom_arrayToReturnPtr(evt.data, Uint8Array); + Module.dynCall_viii(uwevt_DCOnBinaryMessage, channel.managePtr, msgPtr + 4, evt.data.byteLength); + } + }); + channel.onopen = function (evt) { + if (!uwcom_existsCheck(channel.managePtr, "onopen", "dataChannel")) return; + Module.dynCall_vi(uwevt_DCOnOpen, channel.managePtr); + }; + channel.onclose = function (evt) { + if (!uwcom_existsCheck(channel.managePtr, "onclose", "dataChannel")) return; + Module.dynCall_vi(uwevt_DCOnClose, channel.managePtr); + }; + uwcom_addManageObj(channel); + Module.dynCall_vii(uwevt_PCOnDataChannel, peer.managePtr, channel.managePtr); + }; + peer.ontrack = function (evt) { + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'ontrack', this.label + ':' + evt.track.kind); + var receiver = evt.receiver; + var transceiver = evt.transceiver; + var track = evt.track; + if (evt.streams[0]) + evt.streams[0].removeTrack(track); + uwcom_addManageObj(receiver); + uwcom_addManageObj(transceiver); + uwcom_addManageObj(track); + + var stream = new MediaStream(); + stream.addTrack(track); + if (track.kind === "audio") { + var audio = document.createElement('audio'); + audio.id = "audio_remote_" + track.managePtr.toString(); + audio.style.display = "none"; + audio.srcObject = stream; + //document.body.appendChild(audio); + audio.play(); + uwcom_remoteAudioTracks[track.managePtr] = { + track: track, + audio: audio + }; + Module.dynCall_vii(uwevt_PCOnTrack, peer.managePtr, transceiver.managePtr); + } else if (track.kind === "video") { + var video = document.createElement("video"); + video.id = "video_receive_" + track.managePtr.toString(); + //document.body.appendChild(video); + video.muted = true; + video.srcObject = stream; + video.style.width = "300px"; + video.style.height = "200px"; + video.style.position = "absolute"; + video.style.left = video.style.top = 0; + uwcom_remoteVideoTracks[track.managePtr] = { + track: track, + video: video + }; + video.play(); + video.onloadedmetadata = function (evt) { + Module.dynCall_vii(uwevt_PCOnTrack, peer.managePtr, transceiver.managePtr); + } + } + }; + uwcom_addManageObj(peer); + return peer.managePtr; + }, + + CreatePeerConnectionWithConfig: function (confPtr) { + var confJson = UTF8ToString(confPtr); + var conf = JSON.parse(confJson); + // conf.bundlePolicy = 'bundlePolicy' in conf ? conf.bundlePolicy : 0; + // conf.bundlePolicy = UWRTCBundlePolicy[conf.bundlePolicy]; + // conf.iceTransportPolicy = 'iceTransportPolicy' in conf ? conf.iceTransportPolicy : 3; + // conf.iceTransportPolicy = UWRTCIceTransportPolicy.indexOf[conf.iceTransportPolicy]; + + var iceIdx = 0; + for (var iceIdx = 0; iceIdx < conf.iceServers.length; iceIdx++) { + var idx = conf.iceServers[iceIdx].credentialType; + conf.iceServers[iceIdx].credentialType = UWRTCIceCredentialType[idx]; + } + + if (conf.iceTransportPolicy) { + if(conf.iceTransportPolicy.hasValye) conf.iceTransportPolicy = UWRTCIceTransportPolicy[conf.iceTransportPolicy.value]; + else delete conf.iceTransportPolicy; + } + if (conf.iceCandidatePoolSize) { + if (conf.iceCandidatePoolSize.hasValue) conf.iceCandidatePoolSize = conf.iceCandidatePoolSize.value; + else delete conf.iceCandidatePoolSize; + } + if (conf.bundlePolicy) + { + if(conf.bundlePolicy.hasValue) conf.bundlePolicy = UWRTCBundlePolicy[conf.bundlePolicy.value]; + else delete conf.bundlePolicy; + } + if (conf.enableDtlsSrtp) { + if(conf.enableDtlsSrtp.hasValue) conf.enableDtlsSrtp = conf.enableDtlsSrtp.value; + else delete conf.enableDtlsSrtp; + } + + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'CreatePeerConnectionWithConfig', JSON.stringify(conf)); + var ptr = _CreatePeerConnection(conf); + return ptr; + }, + + PeerConnectionSetDescriptionWithoutDescription: function (peerPtr) { + var peer = UWManaged[peerPtr]; + peer.setLocalDescription() + .then(function () { + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionSetDescription', peer.label); + Module.dynCall_vi(uwevt_OnSetSessionDescSuccess, peer.managePtr); + }).catch(function (err) { + uwcom_debugLog('error', 'RTCPeerConnection.jslib', 'PeerConnectionSetDescription', peer.label + ':' + err.message); + var errorNo = uwcom_errorNo(err); + var errMsgPtr = uwcom_strToPtr(err.message); + Module.dynCall_viii(uwevt_OnSetSessionDescFailure, peer.managePtr, errorNo, errMsgPtr); + }); + + // TODO: Use promises to wait for resolve/reject and return RTCErrorType + return uwcom_arrayToReturnPtr([UWRTCErrorType.indexOf("None"), uwcom_strToPtr("no error") ], Int32Array); + }, + + PeerConnectionSetDescription: function (peerPtr, typeIdx, sdpPtr, side) { + var peer = UWManaged[peerPtr]; + var type = UWRTCSdpType[typeIdx]; + var sdp = UTF8ToString(sdpPtr); + peer['set' + side + 'Description']({type: type, sdp: sdp}) + .then(function () { + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionSetDescription', peer.label + ':' + side + ':' + type + ':' /*+ sdp*/); + Module.dynCall_vi(uwevt_OnSetSessionDescSuccess, peer.managePtr); + }).catch(function (err) { + uwcom_debugLog('error', 'RTCPeerConnection.jslib', 'PeerConnectionSetDescription', peer.label + ':' + side + ':' + err.message); + var errorNo = uwcom_errorNo(err); + var errMsgPtr = uwcom_strToPtr(err.message); + Module.dynCall_viii(uwevt_OnSetSessionDescFailure, peer.managePtr, errorNo, errMsgPtr); + }); + + // TODO: Use promises to wait for resolve/reject and return RTCErrorType + return uwcom_arrayToReturnPtr([UWRTCErrorType.indexOf("None"), uwcom_strToPtr("no error") ], Int32Array); + }, + + PeerConnectionIceConditionState: function (peerPtr) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionIceConditionState', 'peer')) return; + var peer = UWManaged[peerPtr]; + var idx = UWRTCIceConnectionState.indexOf(peer.iceConnectionState); + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionIceConditionState', peer.label + ':' + peer.iceConnectionState); + return idx; + }, + + PeerConnectionState: function (peerPtr) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionState', 'peer')) return; + var peer = UWManaged[peerPtr]; + var idx = UWRTCPeerConnectionState.indexOf(peer.connectionState); + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionState', peer.label + ':' + peer.connectionState); + return idx; + }, + + PeerConnectionSignalingState: function (peerPtr) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionSignalingState', 'peer')) return; + var peer = UWManaged[peerPtr]; + var idx = UWRTCSignalingState.indexOf(peer.signalingState); + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionSignalingState', peer.label + ':' + peer.signalingState); + return idx; + }, + + PeerConnectionIceGatheringState: function (peerPtr) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionIceGatheringState', 'peer')) return; + var peer = UWManaged[peerPtr]; + var idx = UWRTCIceGathererState.indexOf(peer.iceGatheringState); + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionIceGatheringState', peer.label + ':' + peer.iceGatheringState); + return idx; + }, + + PeerConnectionGetReceivers: function (contextPtr, peerPtr) { + if (!uwcom_existsCheck(contextPtr, 'PeerConnectionGetReceivers', 'context')) return; + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionGetReceivers', 'peer')) return; + var peer = UWManaged[peerPtr]; + var receivers = peer.getReceivers(); + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionGetReceivers', peer.label + ': receivers=' + receivers.length); + var ptrs = []; + receivers.forEach(function (receiver) { + uwcom_addManageObj(receiver); + ptrs.push(receiver.managePtr); + }); + var ptr = uwcom_arrayToReturnPtr(ptrs, Int32Array); + return ptr; + }, + + PeerConnectionGetSenders: function (contextPtr, peerPtr) { + if (!uwcom_existsCheck(contextPtr, 'PeerConnectionGetSenders', 'context')) return; + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionGetSenders', 'peer')) return; + var peer = UWManaged[peerPtr]; + var senders = peer.getSenders(); + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionGetSenders', peer.label + ': senders=' + senders.length); + var ptrs = []; + senders.forEach(function (sender) { + uwcom_addManageObj(sender); + ptrs.push(sender.managePtr); + }); + var ptr = uwcom_arrayToReturnPtr(ptrs, Int32Array); + return ptr; + }, + + PeerConnectionGetTransceivers: function (contextPtr, peerPtr) { + if (!uwcom_existsCheck(contextPtr, 'PeerConnectionGetTransceivers', 'context')) return; + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionGetTransceivers', 'peer')) return; + var peer = UWManaged[peerPtr]; + var transceivers = peer.getTransceivers(); + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionGetTransceivers', peer.label + ': transceivers=' + transceivers.length); + var ptrs = []; + transceivers.forEach(function (transceiver) { + uwcom_addManageObj(transceiver); + ptrs.push(transceiver.managePtr); + }); + var ptr = uwcom_arrayToReturnPtr(ptrs, Int32Array); + return ptr; + }, + + PeerConnectionGetConfiguration: function (peerPtr) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionGetConfiguration', 'peer')) return; + var peer = UWManaged[peerPtr]; + var conf = peer.getConfiguration(); + + // TODO: Clean + if(conf.iceTransportPolicy) + conf.iceTransportPolicy = { + hasValue: true, + value: UWRTCIceTransportPolicy.indexOf(conf.iceTransportPolicy) + }; + else + conf.iceTransportPolicy = { + hasValue: false, + value: UWRTCIceTransportPolicy.indexOf(conf.iceTransportPolicy) + }; + if(conf.bundlePolicy) + conf.bundlePolicy = { + hasValue: true, + value: UWRTCBundlePolicy.indexOf(conf.bundlePolicy) + }; + else + conf.bundlePolicy = { + hasValue: false, + value: UWRTCBundlePolicy.indexOf(conf.bundlePolicy) + }; + + if(conf.iceCandidatePoolSize !== undefined) + conf.iceCandidatePoolSize = { + hasValue: true, + value: conf.iceCandidatePoolSize + }; + else + conf.iceCandidatePoolSize = { + hasValue: false, + value: 0 + }; + if(conf.enableDtlsSrtp) + conf.enableDtlsSrtp = { + hasValue: false, + value: null + }; + else + conf.enableDtlsSrtp = { + hasValue: false, + value: null + }; + + + confJson = JSON.stringify(conf); + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionGetConfiguration', peer.label + ':' + confJson); + var ptr = uwcom_strToPtr(confJson); + return ptr; + }, + + PeerConnectionSetConfiguration: function (peerPtr, confPtr) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionSetConfiguration', 'peer')) return 11; // OperationErrorWithData + try { + var peer = UWManaged[peerPtr]; + var confJson = UTF8ToString(confPtr); + var conf = JSON.parse(confJson); + // conf.bundlePolicy = 'bundlePolicy' in conf ? conf.bundlePolicy : 0; + // conf.bundlePolicy = UWRTCBundlePolicy[conf.bundlePolicy]; + // conf.iceTransportPolicy = 'iceTransportPolicy' in conf ? conf.iceTransportPolicy : 3; + // conf.iceTransportPolicy = UWRTCIceTransportPolicy[conf.iceTransportPolicy]; + // uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionSetConfiguration', peer.label + ':' + JSON.stringify(conf)); + + var iceIdx = 0; + for (var iceIdx = 0; iceIdx < conf.iceServers.length; iceIdx++) { + var idx = conf.iceServers[iceIdx].credentialType; + conf.iceServers[iceIdx].credentialType = UWRTCIceCredentialType[idx]; + } + if (conf.iceTransportPolicy) conf.iceTransportPolicy = conf.iceTransportPolicy.value; + if (conf.iceCandidatePoolSize) conf.iceCandidatePoolSize = conf.iceCandidatePoolSize.value; + if (conf.bundlePolicy) conf.bundlePolicy = conf.bundlePolicy.value; + if (conf.enableDtlsSrtp) conf.enableDtlsSrtp = conf.enableDtlsSrtp.value; + + peer.setConfiguration(conf); + return 0; + } catch (err) { + uwcom_debugLog('error', 'RTCPeerConnection.jslib', 'PeerConnectionSetConfiguration', peer.label + ':' + err.message); + return uwcom_errorNo(err); + } + }, + + PeerConnectionRegisterCallbackCreateSD: function (peerPtr, OnSuccessCreateSessionDesc, OnFailureCreateSessionDesc) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionRegisterCallbackCreateSD', 'peer')) return; + var peer = UWManaged[peerPtr]; + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionRegisterCallbackCreateSD', peer.label); + uwevt_OnSuccessCreateSessionDesc = OnSuccessCreateSessionDesc; + uwevt_OnFailureCreateSessionDesc = OnFailureCreateSessionDesc; + }, + + PeerConnectionRegisterCallbackCollectStats: function (peerPtr, OnStatsDeliveredCallback) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionRegisterCallbackCollectStats', 'peer')) return; + var peer = UWManaged[peerPtr]; + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionRegisterCallbackCollectStats', peer.label); + uwevt_OnStatsDeliveredCallback = OnStatsDeliveredCallback; + }, + + PeerConnectionRegisterIceConnectionChange: function (peerPtr, PCOnIceConnectionChange) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionRegisterIceConnectionChange', 'peer')) return; + var peer = UWManaged[peerPtr]; + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionRegisterIceConnectionChange', peer.label); + uwevt_PCOnIceConnectionChange = PCOnIceConnectionChange; + }, + + PeerConnectionRegisterConnectionStateChange: function (peerPtr, PCOnConnectionStateChange) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionRegisterConnectionStateChange', 'peer')) return; + var peer = UWManaged[peerPtr]; + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionRegisterConnectionStateChange', peer.label); + uwevt_PCOnConnectionStateChange = PCOnConnectionStateChange; + }, + + PeerConnectionRegisterIceGatheringChange: function (peerPtr, PCOnIceGatheringChange) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionRegisterIceGatheringChange', 'peer')) return; + var peer = UWManaged[peerPtr]; + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionRegisterIceGatheringChange', peer.label); + uwevt_PCOnIceGatheringChange = PCOnIceGatheringChange; + }, + + PeerConnectionRegisterOnIceCandidate: function (peerPtr, PCOnIceCandidate) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionRegisterOnIceCandidate', 'peer')) return; + var peer = UWManaged[peerPtr]; + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionRegisterOnIceCandidate', peer.label); + uwevt_PCOnIceCandidate = PCOnIceCandidate; + }, + + PeerConnectionRegisterOnDataChannel: function (peerPtr, PCOnDataChannel) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionRegisterOnDataChannel', 'peer')) return; + var peer = UWManaged[peerPtr]; + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionRegisterOnDataChannel', peer.label); + uwevt_PCOnDataChannel = PCOnDataChannel; + }, + + PeerConnectionRegisterOnRenegotiationNeeded: function (peerPtr, PCOnNegotiationNeeded) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionRegisterOnRenegotiationNeeded', 'peer')) return; + var peer = UWManaged[peerPtr]; + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionRegisterOnRenegotiationNeeded', peer.label); + uwevt_PCOnNegotiationNeeded = PCOnNegotiationNeeded; + }, + + PeerConnectionRegisterOnTrack: function (peerPtr, PCOnTrack) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionRegisterOnTrack', 'peer')) return; + var peer = UWManaged[peerPtr]; + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionRegisterOnTrack', peer.label); + uwevt_PCOnTrack = PCOnTrack; + }, + + PeerConnectionRegisterOnRemoveTrack: function (peerPtr, PCOnRemoveTrack) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionRegisterOnRemoveTrack', 'peer')) return; + var peer = UWManaged[peerPtr]; + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionRegisterOnRemoveTrack', peer.label); + uwevt_PCOnRemoveTrack = PCOnRemoveTrack; + }, + + PeerConnectionClose: function (peerPtr) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionClose', 'peer')) return; + var peer = UWManaged[peerPtr]; + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionClose', peer.label); + peer.close(); + }, + + //TODO + PeerConnectionRestartIce: function (peerPtr) { + + }, + + PeerConnectionAddTrack: function (peerPtr, trackPtr, streamPtr) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionAddTrack', 'peer')) return; + if (!uwcom_existsCheck(trackPtr, 'PeerConnectionAddTrack', 'track')) return; + var peer = UWManaged[peerPtr]; + var track = UWManaged[trackPtr]; + var stream = null; + if (streamPtr === 0) { + stream = new MediaStream(); + uwcom_addManageObj(stream); + } else { + if (!uwcom_existsCheck(streamPtr, 'PeerConnectionAddTrack', 'stream')) return; + stream = UWManaged[streamPtr]; + } + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionAddTrack', peer.label + ':' + track.kind); + + // TODO: Only add video element for local webcam display + if (track.kind == "video") { + var video = document.createElement("video"); + video.id = "video_send_" + track.managePtr.toString(); + //document.body.appendChild(video); + video.muted = true; + video.srcObject = stream; + video.style.width = "300px"; + video.style.height = "200px"; + video.style.position = "absolute"; + video.style.left = video.style.top = 0; + uwcom_remoteVideoTracks[track.managePtr] = { + track: track, + video: video + }; + video.play(); + } + + var error = 0; + var sender = 0; + try { + sender = peer.addTrack(track); + uwcom_addManageObj(sender); + } + catch (err){ + error = UWRTCErrorType.indexOf("InvalidState"); + } + + var ptrs = [error, sender.managePtr]; + return uwcom_arrayToReturnPtr(ptrs, Int32Array); + }, + + PeerConnectionRemoveTrack: function (peerPtr, senderPtr) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionRemoveTrack', 'peer')) return; + if (!uwcom_existsCheck(senderPtr, 'PeerConnectionRemoveTrack', 'sender')) return; + var peer = UWManaged[peerPtr]; + var sender = UWManaged[senderPtr]; + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionRemoveTrack', peer.label + ':' + sender.track.kind); + peer.removeTrack(sender); + }, + + PeerConnectionAddTransceiver: function (peerPtr, trackPtr) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionAddTransceiver', 'peer')) return; + if (!uwcom_existsCheck(trackPtr, 'PeerConnectionAddTransceiver', 'track')) return; + var peer = UWManaged[peerPtr]; + var track = UWManaged[trackPtr]; + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionAddTransceiver', peer.label + ':' + track.kind); + var transceiver = peer.addTransceiver(track); + uwcom_addManageObj(transceiver); + return transceiver.managePtr; + }, + + PeerConnectionAddTransceiverWithType: function (peerPtr, kindIdx) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionAddTransceiverWithType', 'peer')) return; + var peer = UWManaged[peerPtr]; + var kind = UWMediaStreamTrackKind[kindIdx]; + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionAddTransceiverWithType', peer.label + ':' + kind); + var transceiver = peer.addTransceiver(kind); + uwcom_addManageObj(transceiver); + return transceiver.managePtr; + }, + + PeerConnectionAddIceCandidate: function (peerPtr, candidatePtr) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionAddIceCandidate', 'peer')) return; + if (!uwcom_existsCheck(candidatePtr, 'PeerConnectionAddIceCandidate', 'candidate')) return; + var peer = UWManaged[peerPtr]; + var candidate = UWManaged[candidatePtr]; + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionAddIceCandidate', peer.label + ':' + JSON.stringify(candidate)); + + // TEMP: Use timeout so we have a higher chance that the description is set before the first candidate arrives + // Timing bug: Often we icecandidate is received earlier than the description, and it's not possible to set an ice candiate without a description on the peer server + // Possible solution: Put icecandidates on a queue if the description is not set, and handle the queue when the description is set. + setTimeout(function () { + peer.addIceCandidate(candidate) + .then(function () { + }) + .catch(function (err) { + console.error(err.message, peerPtr) + }); + }, 1000); + + // TODO: Fix async return value + return true; + }, + + PeerConnectionCreateOffer: function (peerPtr, optionsPtr) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionCreateOffer', 'peer')) return; + var peer = UWManaged[peerPtr]; + var options = UTF8ToString(optionsPtr); + var options = JSON.parse(options); + peer.createOffer(options).then(function (offer) { + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionCreateOffer', peer.label + ':' + JSON.stringify(options) + ':' + offer.type); + uwcom_addManageObj(offer); + var sdpPtr = uwcom_strToPtr(offer.sdp); + Module.dynCall_viii(uwevt_OnSuccessCreateSessionDesc, peerPtr, 0, sdpPtr); // 0 === offer + }).catch(function (err) { + uwcom_debugLog('error', 'RTCPeerConnection.jslib', 'PeerConnectionCreateOffer', peer.label + ':' + err.message); + var errorNo = uwcom_errorNo(err); + var errMsgPtr = uwcom_strToPtr(err.message); + Module.dynCall_viii(uwevt_OnFailureCreateSessionDesc, peerPtr, errorNo, errMsgPtr); + }); + }, + + PeerConnectionCreateAnswer: function (peerPtr, optionsPtr) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionCreateAnswer', 'peer')) return; + var peer = UWManaged[peerPtr]; + var options = UTF8ToString(optionsPtr); + var options = JSON.parse(options); + peer.createAnswer(options).then(function (answer) { + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionCreateAnswer', peer.label + ':' + JSON.stringify(options) + ':' + answer.type); + uwcom_addManageObj(answer); + var sdpPtr = uwcom_strToPtr(answer.sdp); + Module.dynCall_viii(uwevt_OnSuccessCreateSessionDesc, peerPtr, 2, sdpPtr); // 2 == answer + }).catch(function (err) { + uwcom_debugLog('error', 'RTCPeerConnection.jslib', 'PeerConnectionCreateAnswer', peer.label + ':' + err.message); + var errorNo = uwcom_errorNo(err); + var errMsgPtr = uwcom_strToPtr(err.message); + Module.dynCall_viii(uwevt_OnFailureCreateSessionDesc, peerPtr, errorNo, errMsgPtr); + }); + }, + + PeerConnectionGetStats: function (peerPtr) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionGetStats', 'peer')) return; + var peer = UWManaged[peerPtr]; + peer.getStats().then(function (stats) { + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionGetStats', peer.label + ': stats=' + stats.length); + uwcom_addManageObj(stats); + Module.dynCall_vii(uwevt_OnStatsDeliveredCallback, peer.managePtr, stats.managePtr); + }).catch(function (err) { + uwcom_debugLog('error', 'RTCPeerConnection.jslib', 'PeerConnectionGetStats', peer.label + ':' + err.message); + }); + }, + + PeerConnectionTrackGetStats: function (peerPtr, trackPtr) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionTrackGetStats', 'peer')) return; + if (!uwcom_existsCheck(trackPtr, 'PeerConnectionTrackGetStats', 'track')) return; + var peer = UWManaged[peerPtr]; + var track = UWManaged[trackPtr]; + peer.getStats(track).then(function (stats) { + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionTrackGetStats', peer.label + ':' + track.kind + ':stats=' + stats.length); + uwcom_addManageObj(stats); + Module.dynCall_vii(uwevt_OnStatsDeliveredCallback, peer.managePtr, stats.managePtr); + }).catch(function (err) { + uwcom_debugLog('error', 'RTCPeerConnection.jslib', 'PeerConnectionTrackGetStats', peer.label + ':' + err.message); + }); + }, + + PeerConnectionSenderGetStats: function (peerPtr, senderPtr) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionSenderGetStats', 'peer')) return; + if (!uwcom_existsCheck(senderPtr, 'PeerConnectionSenderGetStats', 'sender')) return; + var peer = UWManaged[peerPtr]; + var sender = UWManaged[senderPtr]; + sender.getStats().then(function (stats) { + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionSenderGetStats', peer.label + ':' + sender.track.kind + ':stats=' + stats.length); + uwcom_addManageObj(stats); + Module.dynCall_vii(uwevt_OnStatsDeliveredCallback, peer.managePtr, stats.managePtr); + }).catch(function (err) { + uwcom_debugLog('error', 'RTCPeerConnection.jslib', 'PeerConnectionSenderGetStats', peer.label + ':' + err.message); + }); + }, + + PeerConnectionReceiverGetStats: function (peerPtr, receiverPtr) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionReceiverGetStats', 'peer')) return; + if (!uwcom_existsCheck(receiverPtr, 'PeerConnectionReceiverGetStats', 'receiver')) return; + var peer = UWManaged[peerPtr]; + var receiver = UWManaged[receiverPtr]; + receiver.getStats().then(function (stats) { + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionReceiverGetStats', peer.label + ':' + receiver.track.kind + ':stats=' + stats.length); + uwcom_addManageObj(stats); + Module.dynCall_vii(uwevt_OnStatsDeliveredCallback, peer.managePtr, stats.managePtr); + }).catch(function (err) { + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionReceiverGetStats', peer.label + ':' + err.message); + }) + }, + + PeerConnectionGetLocalDescription: function (peerPtr) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionGetLocalDescription', 'peer')) return uwcom_strToPtr("false"); + var peer = UWManaged[peerPtr]; + if (!peer.localDescription) return uwcom_strToPtr("false"); + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionGetLocalDescription', peer.label + ':' + peer.localDescription.type); + var type = UWRTCSdpType.indexOf(peer.localDescription.type); + var sdp = peer.localDescription.sdp; + return uwcom_strToPtr(JSON.stringify({type: type, sdp: sdp})); + }, + + PeerConnectionGetRemoteDescription: function (peerPtr) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionGetRemoteDescription', 'peer')) return uwcom_strToPtr("false"); + var peer = UWManaged[peerPtr]; + if (!peer.remoteDescription) return uwcom_strToPtr("false"); + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionGetRemoteDescription', peer.label + ':' + peer.remoteDescription.type); + var type = UWRTCSdpType.indexOf(peer.remoteDescription.type); + var sdp = peer.remoteDescription.sdp; + return uwcom_strToPtr(JSON.stringify({type: type, sdp: sdp})); + }, + + PeerConnectionGetCurrentLocalDescription: function (peerPtr) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionGetCurrentLocalDescription', 'peer')) return uwcom_strToPtr("false"); + var peer = UWManaged[peerPtr]; + if (!peer.currentLocalDescription) return uwcom_strToPtr("false"); + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionGetCurrentLocalDescription', peer.label + ':' + peer.currentLocalDescription.type); + var type = UWRTCSdpType.indexOf(peer.currentLocalDescription.type); + var sdp = peer.currentLocalDescription.sdp; + return uwcom_strToPtr(JSON.stringify({type: type, sdp: sdp})); + }, + + PeerConnectionGetCurrentRemoteDescription: function (peerPtr) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionGetCurrentRemoteDescription', 'peer')) return uwcom_strToPtr("false"); + var peer = UWManaged[peerPtr]; + if (!peer.currentRemoteDescription) return uwcom_strToPtr("false"); + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionGetCurrentRemoteDescription', peer.label + ':' + peer.currentRemoteDescription.type); + var type = UWRTCSdpType.indexOf(peer.currentRemoteDescription.type); + var sdp = peer.currentRemoteDescription.sdp; + return uwcom_strToPtr(JSON.stringify({type: type, sdp: sdp})); + }, + + PeerConnectionGetPendingLocalDescription: function (peerPtr) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionGetPendingLocalDescription', 'peer')) return uwcom_strToPtr("false"); + var peer = UWManaged[peerPtr]; + if (!peer.pendingLocalDescription) return uwcom_strToPtr("false"); + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionGetPendingLocalDescription', peer.label + ':' + peer.pendingLocalDescription.type); + var type = UWRTCSdpType.indexOf(peer.pendingLocalDescription.type); + var sdp = peer.pendingLocalDescription.sdp; + return uwcom_strToPtr(JSON.stringify({type: type, sdp: sdp})); + }, + + PeerConnectionGetPendingRemoteDescription: function (peerPtr) { + if (!uwcom_existsCheck(peerPtr, 'PeerConnectionGetPendingRemoteDescription', 'peer')) return uwcom_strToPtr("false"); + var peer = UWManaged[peerPtr]; + if (!peer.pendingRemoteDescription) return uwcom_strToPtr("false"); + uwcom_debugLog('log', 'RTCPeerConnection.jslib', 'PeerConnectionGetPendingRemoteDescription', peer.label + ':' + peer.pendingRemoteDescription.type); + var type = UWRTCSdpType.indexOf(peer.pendingRemoteDescription.type); + var sdp = peer.pendingRemoteDescription.sdp; + return uwcom_strToPtr(JSON.stringify({type: type, sdp: sdp})); + } +}; +mergeInto(LibraryManager.library, UnityWebRTCPeerConnection); \ No newline at end of file diff --git a/Runtime/Plugins/WebGL/RTCPeerConnection.jslib.meta b/Runtime/Plugins/WebGL/RTCPeerConnection.jslib.meta new file mode 100644 index 0000000000..0fdd14eabb --- /dev/null +++ b/Runtime/Plugins/WebGL/RTCPeerConnection.jslib.meta @@ -0,0 +1,44 @@ +fileFormatVersion: 2 +guid: efe1ed2a05ce44d428f4f632a4a91e46 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 1 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 0 + Exclude Win: 1 + Exclude Win64: 1 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + WebGL: WebGL + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Plugins/WebGL/RTCRtpReceiver.jslib b/Runtime/Plugins/WebGL/RTCRtpReceiver.jslib new file mode 100644 index 0000000000..e75f05dbed --- /dev/null +++ b/Runtime/Plugins/WebGL/RTCRtpReceiver.jslib @@ -0,0 +1,18 @@ +var UnityWebRTCRtpReceiver = { + DeleteReceiver: function (receiverPtr) { + if (!uwcom_existsCheck(receiverPtr, 'DeleteReceiver', 'receiver')) return; + delete UWManaged[receiverPtr]; + }, + + ReceiverGetTrack: function (receiverPtr) { + if (!uwcom_existsCheck(receiverPtr, 'ReceiverGetTrack', 'receiver')) return; + var receiver = UWManaged[receiverPtr]; + uwcom_addManageObj(receiver.track); + return receiver.track.managePtr; + }, + + ReceiverGetStreams: function(receiverPtr, length){ + + } +}; +mergeInto(LibraryManager.library, UnityWebRTCRtpReceiver); \ No newline at end of file diff --git a/Runtime/Plugins/WebGL/RTCRtpReceiver.jslib.meta b/Runtime/Plugins/WebGL/RTCRtpReceiver.jslib.meta new file mode 100644 index 0000000000..8254f7fc6c --- /dev/null +++ b/Runtime/Plugins/WebGL/RTCRtpReceiver.jslib.meta @@ -0,0 +1,44 @@ +fileFormatVersion: 2 +guid: 04644cf0d01425245be2f29da80d9db2 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 1 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 0 + Exclude Win: 1 + Exclude Win64: 1 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + WebGL: WebGL + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Plugins/WebGL/RTCRtpSender.jslib b/Runtime/Plugins/WebGL/RTCRtpSender.jslib new file mode 100644 index 0000000000..8f9f21a601 --- /dev/null +++ b/Runtime/Plugins/WebGL/RTCRtpSender.jslib @@ -0,0 +1,43 @@ +var UnityWebRTCRtpSender = { + DeleteSender: function (senderPtr) { + if (!uwcom_existsCheck(senderPtr, 'DeleteSender', 'sender')) return; + delete UWManaged[senderPtr]; + }, + + SenderGetTrack: function (senderPtr) { + if (!uwcom_existsCheck(senderPtr, 'SenderGetTrack', 'sender')) return; + var sender = UWManaged[senderPtr]; + if(sender.track){ + uwcom_addManageObj(sender.track); + return sender.track.managePtr; + } + }, + + SenderGetParameters: function (senderPtr) { + if (!uwcom_existsCheck(senderPtr, 'SenderGetParameters', 'sender')) return; + var sender = UWManaged[senderPtr]; + var parameters = sender.getParameters(); + var parametersJson = JSON.stringify(parameters); + var parametersJsonPtr = uwcom_strToPtr(parametersJson); + return parametersJsonPtr; + }, + + SenderSetParameters: function (senderPtr, parametersJsonPtr) { + if (!uwcom_existsCheck(senderPtr, 'SenderSetParameters', 'sender')) return; + var sender = UWManaged[senderPtr]; + var parametersJson = UTF8ToString(parametersJsonPtr); + var parameters = JSON.parse(parametersJson); + sender.setParameters(parameters).then(function () { + // TODO Send correct RTCErrorType. + }); + }, + + SenderReplaceTrack: function (senderPtr, trackPtr) { + if (!uwcom_existsCheck(senderPtr, 'SenderReplaceTrack', 'sender')) return; + if (!uwcom_existsCheck(trackPtr, 'SenderReplaceTrack', 'track')) return; + var sender = UWManaged[senderPtr]; + var track = UWManaged[trackPtr]; + sender.replaceTrack(track); + } +}; +mergeInto(LibraryManager.library, UnityWebRTCRtpSender); \ No newline at end of file diff --git a/Runtime/Plugins/WebGL/RTCRtpSender.jslib.meta b/Runtime/Plugins/WebGL/RTCRtpSender.jslib.meta new file mode 100644 index 0000000000..dd9cbf8c50 --- /dev/null +++ b/Runtime/Plugins/WebGL/RTCRtpSender.jslib.meta @@ -0,0 +1,44 @@ +fileFormatVersion: 2 +guid: bb7c8a3b5ea8c91499b72c9bf0c8056e +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 1 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 0 + Exclude Win: 1 + Exclude Win64: 1 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + WebGL: WebGL + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Plugins/WebGL/RTCRtpTransceiver.jslib b/Runtime/Plugins/WebGL/RTCRtpTransceiver.jslib new file mode 100644 index 0000000000..ba032b3510 --- /dev/null +++ b/Runtime/Plugins/WebGL/RTCRtpTransceiver.jslib @@ -0,0 +1,70 @@ +var UnityWebRTCRtpTransceiver = { + DeleteTransceiver: function (transceiverPtr) { + if (!uwcom_existsCheck(transceiverPtr, 'DeleteTransceiver', 'transceiver')) return; + delete UWManaged[transceiverPtr]; + }, + + TransceiverGetDirection: function (transceiverPtr) { + if (!uwcom_existsCheck(transceiverPtr, 'TransceiverGetDirection', 'transceiver')) return; + var transceiver = UWManaged[transceiverPtr]; + return UWRTCRtpTransceiverDirection.indexOf(transceiver.direction); + }, + + TransceiverSetDirection: function (transceiverPtr, directionIdx) { + if (!uwcom_existsCheck(transceiverPtr, 'TransceiverSetDirection', 'transceiver')) return 0; + var transceiver = UWManaged[transceiverPtr]; + transceiver.direction = UWRTCRtpTransceiverDirection[directionIdx]; + return 0; + }, + + TransceiverGetCurrentDirection: function (transceiverPtr) { + if (!uwcom_existsCheck(transceiverPtr, 'TransceiverGetCurrentDirection', 'transceiver')) return; + var transceiver = UWManaged[transceiverPtr]; + return UWRTCRtpTransceiverDirection.indexOf(transceiver.currentDirection); + }, + + TransceiverGetReceiver: function (transceiverPtr) { + if (!uwcom_existsCheck(transceiverPtr, 'TransceiverGetReceiver', 'transceiver')) return; + var transceiver = UWManaged[transceiverPtr]; + uwcom_addManageObj(transceiver.receiver); + return transceiver.receiver.managePtr; + }, + + TransceiverGetSender: function (transceiverPtr) { + if (!uwcom_existsCheck(transceiverPtr, 'TransceiverGetSender', 'transceiver')) return; + var transceiver = UWManaged[transceiverPtr]; + uwcom_addManageObj(transceiver.sender); + return transceiver.sender.managePtr; + }, + + TransceiverSetCodecPreferences: function (transceiverPtr, codecsPtr) { + if (!uwcom_existsCheck(transceiverPtr, 'TransceiverSetCodecPreferences', 'transceiver')) return UWRTCErrorType.indexOf("OperationErrorWithData"); + + var transceiver = UWManaged[transceiverPtr]; + var codecsJson = UTF8ToString(codecsPtr); + var codecs = JSON.parse(codecsJson); + + const supportsSetCodecPreferences = window.RTCRtpTransceiver && 'setCodecPreferences' in window.RTCRtpTransceiver.prototype; + if (supportsSetCodecPreferences) { + try { + transceiver.setCodecPreferences(codecs); + } catch (err) { + return UWRTCErrorType.indexOf("InvalidModification"); + } + } + else return UWRTCErrorType.indexOf("UnsupportedOperation"); + return UWRTCErrorType.indexOf("None"); + }, + + TransceiverStop: function (transceiverPtr) { + if (!uwcom_existsCheck(transceiverPtr, 'TransceiverStop', 'transceiver')) return; + try { + var transceiver = UWManaged[transceiverPtr]; + transceiver.stop(); + return true; + } catch (err) { + return false; + } + } +}; +mergeInto(LibraryManager.library, UnityWebRTCRtpTransceiver); \ No newline at end of file diff --git a/Runtime/Plugins/WebGL/RTCRtpTransceiver.jslib.meta b/Runtime/Plugins/WebGL/RTCRtpTransceiver.jslib.meta new file mode 100644 index 0000000000..3a05944fc2 --- /dev/null +++ b/Runtime/Plugins/WebGL/RTCRtpTransceiver.jslib.meta @@ -0,0 +1,44 @@ +fileFormatVersion: 2 +guid: 7e5d8020b50ed6a47ab8ab8dd12975b9 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 1 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 0 + Exclude Win: 1 + Exclude Win64: 1 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + WebGL: WebGL + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Plugins/WebGL/VideoRenderer.jslib b/Runtime/Plugins/WebGL/VideoRenderer.jslib new file mode 100644 index 0000000000..79b97bc64c --- /dev/null +++ b/Runtime/Plugins/WebGL/VideoRenderer.jslib @@ -0,0 +1,72 @@ +var UnityWebRTCVideoRenderer = { + CreateVideoRenderer: function (contextPtr) { + }, + + CreateNativeTexture: function() { + //console.log('nativeTexture'); + var texPtr = 0; + for(var texPtr = 0; texPtr < GL.textures.length; texPtr++) { + if(GL.textures[texPtr] === undefined) + break; + } + var tex = GLctx.createTexture(); + tex.name = texPtr; + GL.textures[texPtr] = tex; + //console.log('nativeTexture' + texPtr); + return texPtr; + }, + + VideoSourceGetSyncApplicationFramerate: function (reportPtr) { + console.error("Not implemented"); + return false; + }, + + VideoSourceSetSyncApplicationFramerate: function (reportPtr) { + console.error("Not implemented"); + }, + + SetGraphicsSyncTimeout: function(nSecTimeout) { + throw new Error("Not implemented"); + }, + + GetVideoRendererId: function (sinkPtr) { + throw new Error("Not implemented"); + }, + + DeleteVideoRenderer: function (contextPtr, sinkPtr) { + throw new Error("Not implemented"); + }, + + UpdateRendererTexture: function (trackPtr, renderTexturePtr, needFlip) { + // console.log('UpdateRendererTexture'); + if (!uwcom_existsCheck(trackPtr, 'UpdateRendererTexture', 'track')) return; + if (!uwcom_remoteVideoTracks[trackPtr]) return; + //console.log('UpdateRendererTexture', renderTexturePtr); + var video = uwcom_remoteVideoTracks[trackPtr].video; + var tex = GL.textures[renderTexturePtr]; + GLctx.bindTexture(GLctx.TEXTURE_2D, tex); + if (!!needFlip){ + //GLctx.pixelStorei(GLctx.UNPACK_FLIP_Y_WEBGL, true); + } + // For now: Flip every time, since we want the correct image transfered over WebRTC + GLctx.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); + GLctx.texImage2D(GLctx.TEXTURE_2D, 0, GLctx.RGBA, GLctx.RGBA, GLctx.UNSIGNED_BYTE, video); + GLctx.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + // GLctx.texSubImage2D(GLctx.TEXTURE_2D, 0, 0, 0, GLctx.RGBA, GLctx.UNSIGNED_BYTE, video); + GLctx.texParameteri(GLctx.TEXTURE_2D, GLctx.TEXTURE_MAG_FILTER, GLctx.LINEAR); + GLctx.texParameteri(GLctx.TEXTURE_2D, GLctx.TEXTURE_MIN_FILTER, GLctx.LINEAR); + GLctx.texParameteri(GLctx.TEXTURE_2D, GLctx.TEXTURE_WRAP_S, GLctx.CLAMP_TO_EDGE); + GLctx.texParameteri(GLctx.TEXTURE_2D, GLctx.TEXTURE_WRAP_T, GLctx.CLAMP_TO_EDGE); + GLctx.bindTexture(GLctx.TEXTURE_2D, null); + }, + + // Not used in WebGL, but is here to make the NativeMethods more maintainable. + VideoTrackAddOrUpdateSink: function(trackPtr, sinkPtr){ + + }, + + VideoTrackRemoveSink: function(trackPtr, sinkPtr){ + + } +}; +mergeInto(LibraryManager.library, UnityWebRTCVideoRenderer); \ No newline at end of file diff --git a/Runtime/Plugins/WebGL/VideoRenderer.jslib.meta b/Runtime/Plugins/WebGL/VideoRenderer.jslib.meta new file mode 100644 index 0000000000..9241b52f58 --- /dev/null +++ b/Runtime/Plugins/WebGL/VideoRenderer.jslib.meta @@ -0,0 +1,44 @@ +fileFormatVersion: 2 +guid: 50b140155b56d87498473c49584b36d9 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 1 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 0 + Exclude Win: 1 + Exclude Win64: 1 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + WebGL: WebGL + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Plugins/WebGL/VideoStreamTrack.jslib b/Runtime/Plugins/WebGL/VideoStreamTrack.jslib new file mode 100644 index 0000000000..76bd802266 --- /dev/null +++ b/Runtime/Plugins/WebGL/VideoStreamTrack.jslib @@ -0,0 +1,164 @@ +var UnityWebRTCVideoStreamTrack = { + CreateVideoTrack: function (srcPtr, dstPtr, width, height) { + var cnv = document.createElement('canvas'); + cnv.width = width; + cnv.height = height; + var ctx = cnv.getContext('2d'); + var imgData = ctx.createImageData(width, height); + var stream = cnv.captureStream(); + var track = stream.getVideoTracks()[0]; + var srcTexture = GL.textures[srcPtr]; + var dstTexture = GL.textures[dstPtr]; + var frameBuffer = GLctx.createFramebuffer(); + GLctx.bindFramebuffer(GLctx.FRAMEBUFFER, frameBuffer); + GLctx.framebufferTexture2D( + GLctx.FRAMEBUFFER, + GLctx.COLOR_ATTACHMENT0, + GLctx.TEXTURE_2D, + srcTexture, + 0 + ); + var canRead = (GLctx.checkFramebufferStatus(GLctx.FRAMEBUFFER) === GLctx.FRAMEBUFFER_COMPLETE); + GLctx.bindFramebuffer(GLctx.FRAMEBUFFER, null); + var localVideoData = { + cnv: cnv, + ctx: ctx, + imgData: imgData, + width: width, + height: height, + dstTexture: dstTexture, + stream: stream, + canRead: canRead, + buffer: new Uint8Array(width * height * 4), + lineBuffer: new Uint8Array(width * 4), + frameBuffer: frameBuffer + }; + + //uwcom_addManageObj(stream); + uwcom_addManageObj(track); + uwcom_localVideoTracks[track.managePtr] = localVideoData; + //console.log('localVideoData', track.managePtr); + return track.managePtr; + }, + + // Not finished, don't delete. + $readPixelsAsync: function (data) { + var w = data.width; + var h = data.height; + var buffer = data.buffer; + var lineBuffer = data.lineBuffer; + var frameBuffer = data.frameBuffer; + var imgData = data.imgData; + var cnv = data.cnv; + var ctx = data.ctx; + var dstTexture = data.dstTexture; + var buf = GLctx.createBuffer(); + GLctx.bindTexture(GLctx.TEXTURE_2D, dstTexture); + GLctx.texImage2D(GLctx.TEXTURE_2D, 0, GLctx.RGBA, GLctx.RGBA, GLctx.UNSIGNED_BYTE, cnv); + GLctx.bindBuffer(GLctx.PIXEL_PACK_BUFFER, buf); + GLctx.bufferData(GLctx.PIXEL_PACK_BUFFER, buffer.byteLength, GLctx.STREAM_READ); + GLctx.readPixels(0, 0, w, h, GLctx.RGBA, GLctx.UNSIGNED_BYTE, 0); + GLctx.bindBuffer(GLctx.PIXEL_PACK_BUFFER, null); + + var sync = GLctx.fenceSync(GLctx.SYNC_GPU_COMMANDS_COMPLETE, 0); + if (!sync) { + return null; + } + + GLctx.flush(); + + return clientWaitAsync(sync, 0, 10).then(function () { + GLctx.deleteSync(sync); + + GLctx.bindBuffer(GLctx.PIXEL_PACK_BUFFER, buf); + GLctx.getBufferSubData(GLctx.PIXEL_PACK_BUFFER, 0, buffer); + GLctx.bindBuffer(GLctx.PIXEL_PACK_BUFFER, null); + GLctx.deleteBuffer(buf); + + imgData.data.set(buffer); + ctx.putImageData(imgData, 0, 0); + + GLctx.bindTexture(GLctx.TEXTURE_2D, dstTexture); + GLctx.texImage2D(GLctx.TEXTURE_2D, 0, GLctx.RGBA, GLctx.RGBA, GLctx.UNSIGNED_BYTE, cnv); + //GLctx.texSubImage2D(GLctx.TEXTURE_2D, 0, 0, 0, GLctx.RGBA, GLctx.UNSIGNED_BYTE, cnv); + GLctx.texParameteri(GLctx.TEXTURE_2D, GLctx.TEXTURE_MAG_FILTER, GLctx.LINEAR); + GLctx.texParameteri(GLctx.TEXTURE_2D, GLctx.TEXTURE_MIN_FILTER, GLctx.LINEAR); + GLctx.generateMipmap(GLctx.TEXTURE_2D); + GLctx.bindTexture(GLctx.TEXTURE_2D, null); + }); + }, + + // Not finished, don't delete. + $clientWaitAsync: function (sync, flags, interval_ms) { + return new Promise(function (resolve, reject) { + var check = function() { + var res = GLctx.clientWaitSync(sync, flags, 0); + if (res === GLctx.WAIT_FAILED) { + reject(); + return; + } + if (res === GLctx.TIMEOUT_EXPIRED) { + setTimeout(check, interval_ms); + return; + } + resolve(); + }; + check(); + }); + }, + + RenderLocalVideotrack__deps: ['$readPixelsAsync', '$clientWaitAsync'], + RenderLocalVideotrack: function (trackPtr, needFlip) { + var data = uwcom_localVideoTracks[trackPtr]; + // console.log('RenderLocalVideotrack', trackPtr, data); + if (!data) return; + // readPixelsAsync(data); + // return; + var w = data.width; + var h = data.height; + var buffer = data.buffer; + var lineBuffer = data.lineBuffer; + var frameBuffer = data.frameBuffer; + var imgData = data.imgData; + var cnv = data.cnv; + var ctx = data.ctx; + var dstTexture = data.dstTexture; + + if (data.canRead) { + GLctx.bindFramebuffer(GLctx.FRAMEBUFFER, frameBuffer); + GLctx.readPixels(0, 0, w, h, GLctx.RGBA, GLctx.UNSIGNED_BYTE, buffer); + GLctx.bindFramebuffer(GLctx.FRAMEBUFFER, null); + + // var halfHeight = h / 2 | 0; + // var bytesPerRow = w; + // for (var y = 0; y < halfHeight; y++) { + // var topOffset = y * bytesPerRow; + // var bottomOffset = (h - y - 1) * bytesPerRow; + // data.lineBuffer.set(buffer.subarray(topOffset, topOffset + bytesPerRow)); + // buffer.copyWithin(topOffset, bottomOffset, bottomOffset + bytesPerRow); + // buffer.set(lineBuffer, bottomOffset); + // } + + imgData.data.set(buffer); + ctx.putImageData(imgData, 0, 0); + + // For now: Flip every time, since we want the correct image transfered over WebRTC + ctx.globalCompositeOperation = 'copy'; + ctx.scale(1,-1); + ctx.translate(0, -imgData.height); + ctx.drawImage(cnv,0,0); + ctx.setTransform(1,0,0,1,0,0); + ctx.globalCompositeOperation = 'source-over'; + + GLctx.bindTexture(GLctx.TEXTURE_2D, dstTexture); + GLctx.texImage2D(GLctx.TEXTURE_2D, 0, GLctx.RGBA, GLctx.RGBA, GLctx.UNSIGNED_BYTE, cnv); + //GLctx.texSubImage2D(GLctx.TEXTURE_2D, 0, 0, 0, GLctx.RGBA, GLctx.UNSIGNED_BYTE, cnv); + GLctx.texParameteri(GLctx.TEXTURE_2D, GLctx.TEXTURE_MAG_FILTER, GLctx.LINEAR); + GLctx.texParameteri(GLctx.TEXTURE_2D, GLctx.TEXTURE_MIN_FILTER, GLctx.LINEAR); + GLctx.generateMipmap(GLctx.TEXTURE_2D); + GLctx.bindTexture(GLctx.TEXTURE_2D, null); + } + }, + +}; +mergeInto(LibraryManager.library, UnityWebRTCVideoStreamTrack); \ No newline at end of file diff --git a/Runtime/Plugins/WebGL/VideoStreamTrack.jslib.meta b/Runtime/Plugins/WebGL/VideoStreamTrack.jslib.meta new file mode 100644 index 0000000000..18bd12fb0b --- /dev/null +++ b/Runtime/Plugins/WebGL/VideoStreamTrack.jslib.meta @@ -0,0 +1,44 @@ +fileFormatVersion: 2 +guid: d1d833d3ec5c529469746ebf5753090a +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 1 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 0 + Exclude Win: 1 + Exclude Win64: 1 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + WebGL: WebGL + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Plugins/WebGL/adapter.jspre b/Runtime/Plugins/WebGL/adapter.jspre new file mode 100644 index 0000000000..a0b3a6319b --- /dev/null +++ b/Runtime/Plugins/WebGL/adapter.jspre @@ -0,0 +1,3423 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.adapter = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i 0 && arguments[0] !== undefined ? arguments[0] : {}, + window = _ref.window; + + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { + shimChrome: true, + shimFirefox: true, + shimSafari: true + }; + + // Utils. + var logging = utils.log; + var browserDetails = utils.detectBrowser(window); + + var adapter = { + browserDetails: browserDetails, + commonShim: commonShim, + extractVersion: utils.extractVersion, + disableLog: utils.disableLog, + disableWarnings: utils.disableWarnings, + // Expose sdp as a convenience. For production apps include directly. + sdp: sdp + }; + + // Shim browser if found. + switch (browserDetails.browser) { + case 'chrome': + if (!chromeShim || !chromeShim.shimPeerConnection || !options.shimChrome) { + logging('Chrome shim is not included in this adapter release.'); + return adapter; + } + if (browserDetails.version === null) { + logging('Chrome shim can not determine version, not shimming.'); + return adapter; + } + logging('adapter.js shimming chrome.'); + // Export to the adapter global object visible in the browser. + adapter.browserShim = chromeShim; + + // Must be called before shimPeerConnection. + commonShim.shimAddIceCandidateNullOrEmpty(window, browserDetails); + + chromeShim.shimGetUserMedia(window, browserDetails); + chromeShim.shimMediaStream(window, browserDetails); + chromeShim.shimPeerConnection(window, browserDetails); + chromeShim.shimOnTrack(window, browserDetails); + chromeShim.shimAddTrackRemoveTrack(window, browserDetails); + chromeShim.shimGetSendersWithDtmf(window, browserDetails); + chromeShim.shimGetStats(window, browserDetails); + chromeShim.shimSenderReceiverGetStats(window, browserDetails); + chromeShim.fixNegotiationNeeded(window, browserDetails); + + commonShim.shimRTCIceCandidate(window, browserDetails); + commonShim.shimConnectionState(window, browserDetails); + commonShim.shimMaxMessageSize(window, browserDetails); + commonShim.shimSendThrowTypeError(window, browserDetails); + commonShim.removeExtmapAllowMixed(window, browserDetails); + break; + case 'firefox': + if (!firefoxShim || !firefoxShim.shimPeerConnection || !options.shimFirefox) { + logging('Firefox shim is not included in this adapter release.'); + return adapter; + } + logging('adapter.js shimming firefox.'); + // Export to the adapter global object visible in the browser. + adapter.browserShim = firefoxShim; + + // Must be called before shimPeerConnection. + commonShim.shimAddIceCandidateNullOrEmpty(window, browserDetails); + + firefoxShim.shimGetUserMedia(window, browserDetails); + firefoxShim.shimPeerConnection(window, browserDetails); + firefoxShim.shimOnTrack(window, browserDetails); + firefoxShim.shimRemoveStream(window, browserDetails); + firefoxShim.shimSenderGetStats(window, browserDetails); + firefoxShim.shimReceiverGetStats(window, browserDetails); + firefoxShim.shimRTCDataChannel(window, browserDetails); + firefoxShim.shimAddTransceiver(window, browserDetails); + firefoxShim.shimGetParameters(window, browserDetails); + firefoxShim.shimCreateOffer(window, browserDetails); + firefoxShim.shimCreateAnswer(window, browserDetails); + + commonShim.shimRTCIceCandidate(window, browserDetails); + commonShim.shimConnectionState(window, browserDetails); + commonShim.shimMaxMessageSize(window, browserDetails); + commonShim.shimSendThrowTypeError(window, browserDetails); + break; + case 'safari': + if (!safariShim || !options.shimSafari) { + logging('Safari shim is not included in this adapter release.'); + return adapter; + } + logging('adapter.js shimming safari.'); + // Export to the adapter global object visible in the browser. + adapter.browserShim = safariShim; + + // Must be called before shimCallbackAPI. + commonShim.shimAddIceCandidateNullOrEmpty(window, browserDetails); + + safariShim.shimRTCIceServerUrls(window, browserDetails); + safariShim.shimCreateOfferLegacy(window, browserDetails); + safariShim.shimCallbacksAPI(window, browserDetails); + safariShim.shimLocalStreamsAPI(window, browserDetails); + safariShim.shimRemoteStreamsAPI(window, browserDetails); + safariShim.shimTrackEventTransceiver(window, browserDetails); + safariShim.shimGetUserMedia(window, browserDetails); + safariShim.shimAudioContext(window, browserDetails); + + commonShim.shimRTCIceCandidate(window, browserDetails); + commonShim.shimMaxMessageSize(window, browserDetails); + commonShim.shimSendThrowTypeError(window, browserDetails); + commonShim.removeExtmapAllowMixed(window, browserDetails); + break; + default: + logging('Unsupported browser!'); + break; + } + + return adapter; + } + +// Browser shims. + + },{"./chrome/chrome_shim":3,"./common_shim":6,"./firefox/firefox_shim":7,"./safari/safari_shim":10,"./utils":11,"sdp":12}],3:[function(require,module,exports){ + /* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.shimGetDisplayMedia = exports.shimGetUserMedia = undefined; + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + + var _getusermedia = require('./getusermedia'); + + Object.defineProperty(exports, 'shimGetUserMedia', { + enumerable: true, + get: function get() { + return _getusermedia.shimGetUserMedia; + } + }); + + var _getdisplaymedia = require('./getdisplaymedia'); + + Object.defineProperty(exports, 'shimGetDisplayMedia', { + enumerable: true, + get: function get() { + return _getdisplaymedia.shimGetDisplayMedia; + } + }); + exports.shimMediaStream = shimMediaStream; + exports.shimOnTrack = shimOnTrack; + exports.shimGetSendersWithDtmf = shimGetSendersWithDtmf; + exports.shimGetStats = shimGetStats; + exports.shimSenderReceiverGetStats = shimSenderReceiverGetStats; + exports.shimAddTrackRemoveTrackWithNative = shimAddTrackRemoveTrackWithNative; + exports.shimAddTrackRemoveTrack = shimAddTrackRemoveTrack; + exports.shimPeerConnection = shimPeerConnection; + exports.fixNegotiationNeeded = fixNegotiationNeeded; + + var _utils = require('../utils.js'); + + var utils = _interopRequireWildcard(_utils); + + function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + + function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + + function shimMediaStream(window) { + window.MediaStream = window.MediaStream || window.webkitMediaStream; + } + + function shimOnTrack(window) { + if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection && !('ontrack' in window.RTCPeerConnection.prototype)) { + Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', { + get: function get() { + return this._ontrack; + }, + set: function set(f) { + if (this._ontrack) { + this.removeEventListener('track', this._ontrack); + } + this.addEventListener('track', this._ontrack = f); + }, + + enumerable: true, + configurable: true + }); + var origSetRemoteDescription = window.RTCPeerConnection.prototype.setRemoteDescription; + window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription() { + var _this = this; + + if (!this._ontrackpoly) { + this._ontrackpoly = function (e) { + // onaddstream does not fire when a track is added to an existing + // stream. But stream.onaddtrack is implemented so we use that. + e.stream.addEventListener('addtrack', function (te) { + var receiver = void 0; + if (window.RTCPeerConnection.prototype.getReceivers) { + receiver = _this.getReceivers().find(function (r) { + return r.track && r.track.id === te.track.id; + }); + } else { + receiver = { track: te.track }; + } + + var event = new Event('track'); + event.track = te.track; + event.receiver = receiver; + event.transceiver = { receiver: receiver }; + event.streams = [e.stream]; + _this.dispatchEvent(event); + }); + e.stream.getTracks().forEach(function (track) { + var receiver = void 0; + if (window.RTCPeerConnection.prototype.getReceivers) { + receiver = _this.getReceivers().find(function (r) { + return r.track && r.track.id === track.id; + }); + } else { + receiver = { track: track }; + } + var event = new Event('track'); + event.track = track; + event.receiver = receiver; + event.transceiver = { receiver: receiver }; + event.streams = [e.stream]; + _this.dispatchEvent(event); + }); + }; + this.addEventListener('addstream', this._ontrackpoly); + } + return origSetRemoteDescription.apply(this, arguments); + }; + } else { + // even if RTCRtpTransceiver is in window, it is only used and + // emitted in unified-plan. Unfortunately this means we need + // to unconditionally wrap the event. + utils.wrapPeerConnectionEvent(window, 'track', function (e) { + if (!e.transceiver) { + Object.defineProperty(e, 'transceiver', { value: { receiver: e.receiver } }); + } + return e; + }); + } + } + + function shimGetSendersWithDtmf(window) { + // Overrides addTrack/removeTrack, depends on shimAddTrackRemoveTrack. + if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection && !('getSenders' in window.RTCPeerConnection.prototype) && 'createDTMFSender' in window.RTCPeerConnection.prototype) { + var shimSenderWithDtmf = function shimSenderWithDtmf(pc, track) { + return { + track: track, + get dtmf() { + if (this._dtmf === undefined) { + if (track.kind === 'audio') { + this._dtmf = pc.createDTMFSender(track); + } else { + this._dtmf = null; + } + } + return this._dtmf; + }, + _pc: pc + }; + }; + + // augment addTrack when getSenders is not available. + if (!window.RTCPeerConnection.prototype.getSenders) { + window.RTCPeerConnection.prototype.getSenders = function getSenders() { + this._senders = this._senders || []; + return this._senders.slice(); // return a copy of the internal state. + }; + var origAddTrack = window.RTCPeerConnection.prototype.addTrack; + window.RTCPeerConnection.prototype.addTrack = function addTrack(track, stream) { + var sender = origAddTrack.apply(this, arguments); + if (!sender) { + sender = shimSenderWithDtmf(this, track); + this._senders.push(sender); + } + return sender; + }; + + var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack; + window.RTCPeerConnection.prototype.removeTrack = function removeTrack(sender) { + origRemoveTrack.apply(this, arguments); + var idx = this._senders.indexOf(sender); + if (idx !== -1) { + this._senders.splice(idx, 1); + } + }; + } + var origAddStream = window.RTCPeerConnection.prototype.addStream; + window.RTCPeerConnection.prototype.addStream = function addStream(stream) { + var _this2 = this; + + this._senders = this._senders || []; + origAddStream.apply(this, [stream]); + stream.getTracks().forEach(function (track) { + _this2._senders.push(shimSenderWithDtmf(_this2, track)); + }); + }; + + var origRemoveStream = window.RTCPeerConnection.prototype.removeStream; + window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) { + var _this3 = this; + + this._senders = this._senders || []; + origRemoveStream.apply(this, [stream]); + + stream.getTracks().forEach(function (track) { + var sender = _this3._senders.find(function (s) { + return s.track === track; + }); + if (sender) { + // remove sender + _this3._senders.splice(_this3._senders.indexOf(sender), 1); + } + }); + }; + } else if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection && 'getSenders' in window.RTCPeerConnection.prototype && 'createDTMFSender' in window.RTCPeerConnection.prototype && window.RTCRtpSender && !('dtmf' in window.RTCRtpSender.prototype)) { + var origGetSenders = window.RTCPeerConnection.prototype.getSenders; + window.RTCPeerConnection.prototype.getSenders = function getSenders() { + var _this4 = this; + + var senders = origGetSenders.apply(this, []); + senders.forEach(function (sender) { + return sender._pc = _this4; + }); + return senders; + }; + + Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', { + get: function get() { + if (this._dtmf === undefined) { + if (this.track.kind === 'audio') { + this._dtmf = this._pc.createDTMFSender(this.track); + } else { + this._dtmf = null; + } + } + return this._dtmf; + } + }); + } + } + + function shimGetStats(window) { + if (!window.RTCPeerConnection) { + return; + } + + var origGetStats = window.RTCPeerConnection.prototype.getStats; + window.RTCPeerConnection.prototype.getStats = function getStats() { + var _this5 = this; + + var _arguments = Array.prototype.slice.call(arguments), + selector = _arguments[0], + onSucc = _arguments[1], + onErr = _arguments[2]; + + // If selector is a function then we are in the old style stats so just + // pass back the original getStats format to avoid breaking old users. + + + if (arguments.length > 0 && typeof selector === 'function') { + return origGetStats.apply(this, arguments); + } + + // When spec-style getStats is supported, return those when called with + // either no arguments or the selector argument is null. + if (origGetStats.length === 0 && (arguments.length === 0 || typeof selector !== 'function')) { + return origGetStats.apply(this, []); + } + + var fixChromeStats_ = function fixChromeStats_(response) { + var standardReport = {}; + var reports = response.result(); + reports.forEach(function (report) { + var standardStats = { + id: report.id, + timestamp: report.timestamp, + type: { + localcandidate: 'local-candidate', + remotecandidate: 'remote-candidate' + }[report.type] || report.type + }; + report.names().forEach(function (name) { + standardStats[name] = report.stat(name); + }); + standardReport[standardStats.id] = standardStats; + }); + + return standardReport; + }; + + // shim getStats with maplike support + var makeMapStats = function makeMapStats(stats) { + return new Map(Object.keys(stats).map(function (key) { + return [key, stats[key]]; + })); + }; + + if (arguments.length >= 2) { + var successCallbackWrapper_ = function successCallbackWrapper_(response) { + onSucc(makeMapStats(fixChromeStats_(response))); + }; + + return origGetStats.apply(this, [successCallbackWrapper_, selector]); + } + + // promise-support + return new Promise(function (resolve, reject) { + origGetStats.apply(_this5, [function (response) { + resolve(makeMapStats(fixChromeStats_(response))); + }, reject]); + }).then(onSucc, onErr); + }; + } + + function shimSenderReceiverGetStats(window) { + if (!((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection && window.RTCRtpSender && window.RTCRtpReceiver)) { + return; + } + + // shim sender stats. + if (!('getStats' in window.RTCRtpSender.prototype)) { + var origGetSenders = window.RTCPeerConnection.prototype.getSenders; + if (origGetSenders) { + window.RTCPeerConnection.prototype.getSenders = function getSenders() { + var _this6 = this; + + var senders = origGetSenders.apply(this, []); + senders.forEach(function (sender) { + return sender._pc = _this6; + }); + return senders; + }; + } + + var origAddTrack = window.RTCPeerConnection.prototype.addTrack; + if (origAddTrack) { + window.RTCPeerConnection.prototype.addTrack = function addTrack() { + var sender = origAddTrack.apply(this, arguments); + sender._pc = this; + return sender; + }; + } + window.RTCRtpSender.prototype.getStats = function getStats() { + var sender = this; + return this._pc.getStats().then(function (result) { + return ( + /* Note: this will include stats of all senders that + * send a track with the same id as sender.track as + * it is not possible to identify the RTCRtpSender. + */ + utils.filterStats(result, sender.track, true) + ); + }); + }; + } + + // shim receiver stats. + if (!('getStats' in window.RTCRtpReceiver.prototype)) { + var origGetReceivers = window.RTCPeerConnection.prototype.getReceivers; + if (origGetReceivers) { + window.RTCPeerConnection.prototype.getReceivers = function getReceivers() { + var _this7 = this; + + var receivers = origGetReceivers.apply(this, []); + receivers.forEach(function (receiver) { + return receiver._pc = _this7; + }); + return receivers; + }; + } + utils.wrapPeerConnectionEvent(window, 'track', function (e) { + e.receiver._pc = e.srcElement; + return e; + }); + window.RTCRtpReceiver.prototype.getStats = function getStats() { + var receiver = this; + return this._pc.getStats().then(function (result) { + return utils.filterStats(result, receiver.track, false); + }); + }; + } + + if (!('getStats' in window.RTCRtpSender.prototype && 'getStats' in window.RTCRtpReceiver.prototype)) { + return; + } + + // shim RTCPeerConnection.getStats(track). + var origGetStats = window.RTCPeerConnection.prototype.getStats; + window.RTCPeerConnection.prototype.getStats = function getStats() { + if (arguments.length > 0 && arguments[0] instanceof window.MediaStreamTrack) { + var track = arguments[0]; + var sender = void 0; + var receiver = void 0; + var err = void 0; + this.getSenders().forEach(function (s) { + if (s.track === track) { + if (sender) { + err = true; + } else { + sender = s; + } + } + }); + this.getReceivers().forEach(function (r) { + if (r.track === track) { + if (receiver) { + err = true; + } else { + receiver = r; + } + } + return r.track === track; + }); + if (err || sender && receiver) { + return Promise.reject(new DOMException('There are more than one sender or receiver for the track.', 'InvalidAccessError')); + } else if (sender) { + return sender.getStats(); + } else if (receiver) { + return receiver.getStats(); + } + return Promise.reject(new DOMException('There is no sender or receiver for the track.', 'InvalidAccessError')); + } + return origGetStats.apply(this, arguments); + }; + } + + function shimAddTrackRemoveTrackWithNative(window) { + // shim addTrack/removeTrack with native variants in order to make + // the interactions with legacy getLocalStreams behave as in other browsers. + // Keeps a mapping stream.id => [stream, rtpsenders...] + window.RTCPeerConnection.prototype.getLocalStreams = function getLocalStreams() { + var _this8 = this; + + this._shimmedLocalStreams = this._shimmedLocalStreams || {}; + return Object.keys(this._shimmedLocalStreams).map(function (streamId) { + return _this8._shimmedLocalStreams[streamId][0]; + }); + }; + + var origAddTrack = window.RTCPeerConnection.prototype.addTrack; + window.RTCPeerConnection.prototype.addTrack = function addTrack(track, stream) { + if (!stream) { + return origAddTrack.apply(this, arguments); + } + this._shimmedLocalStreams = this._shimmedLocalStreams || {}; + + var sender = origAddTrack.apply(this, arguments); + if (!this._shimmedLocalStreams[stream.id]) { + this._shimmedLocalStreams[stream.id] = [stream, sender]; + } else if (this._shimmedLocalStreams[stream.id].indexOf(sender) === -1) { + this._shimmedLocalStreams[stream.id].push(sender); + } + return sender; + }; + + var origAddStream = window.RTCPeerConnection.prototype.addStream; + window.RTCPeerConnection.prototype.addStream = function addStream(stream) { + var _this9 = this; + + this._shimmedLocalStreams = this._shimmedLocalStreams || {}; + + stream.getTracks().forEach(function (track) { + var alreadyExists = _this9.getSenders().find(function (s) { + return s.track === track; + }); + if (alreadyExists) { + throw new DOMException('Track already exists.', 'InvalidAccessError'); + } + }); + var existingSenders = this.getSenders(); + origAddStream.apply(this, arguments); + var newSenders = this.getSenders().filter(function (newSender) { + return existingSenders.indexOf(newSender) === -1; + }); + this._shimmedLocalStreams[stream.id] = [stream].concat(newSenders); + }; + + var origRemoveStream = window.RTCPeerConnection.prototype.removeStream; + window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) { + this._shimmedLocalStreams = this._shimmedLocalStreams || {}; + delete this._shimmedLocalStreams[stream.id]; + return origRemoveStream.apply(this, arguments); + }; + + var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack; + window.RTCPeerConnection.prototype.removeTrack = function removeTrack(sender) { + var _this10 = this; + + this._shimmedLocalStreams = this._shimmedLocalStreams || {}; + if (sender) { + Object.keys(this._shimmedLocalStreams).forEach(function (streamId) { + var idx = _this10._shimmedLocalStreams[streamId].indexOf(sender); + if (idx !== -1) { + _this10._shimmedLocalStreams[streamId].splice(idx, 1); + } + if (_this10._shimmedLocalStreams[streamId].length === 1) { + delete _this10._shimmedLocalStreams[streamId]; + } + }); + } + return origRemoveTrack.apply(this, arguments); + }; + } + + function shimAddTrackRemoveTrack(window, browserDetails) { + if (!window.RTCPeerConnection) { + return; + } + // shim addTrack and removeTrack. + if (window.RTCPeerConnection.prototype.addTrack && browserDetails.version >= 65) { + return shimAddTrackRemoveTrackWithNative(window); + } + + // also shim pc.getLocalStreams when addTrack is shimmed + // to return the original streams. + var origGetLocalStreams = window.RTCPeerConnection.prototype.getLocalStreams; + window.RTCPeerConnection.prototype.getLocalStreams = function getLocalStreams() { + var _this11 = this; + + var nativeStreams = origGetLocalStreams.apply(this); + this._reverseStreams = this._reverseStreams || {}; + return nativeStreams.map(function (stream) { + return _this11._reverseStreams[stream.id]; + }); + }; + + var origAddStream = window.RTCPeerConnection.prototype.addStream; + window.RTCPeerConnection.prototype.addStream = function addStream(stream) { + var _this12 = this; + + this._streams = this._streams || {}; + this._reverseStreams = this._reverseStreams || {}; + + stream.getTracks().forEach(function (track) { + var alreadyExists = _this12.getSenders().find(function (s) { + return s.track === track; + }); + if (alreadyExists) { + throw new DOMException('Track already exists.', 'InvalidAccessError'); + } + }); + // Add identity mapping for consistency with addTrack. + // Unless this is being used with a stream from addTrack. + if (!this._reverseStreams[stream.id]) { + var newStream = new window.MediaStream(stream.getTracks()); + this._streams[stream.id] = newStream; + this._reverseStreams[newStream.id] = stream; + stream = newStream; + } + origAddStream.apply(this, [stream]); + }; + + var origRemoveStream = window.RTCPeerConnection.prototype.removeStream; + window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) { + this._streams = this._streams || {}; + this._reverseStreams = this._reverseStreams || {}; + + origRemoveStream.apply(this, [this._streams[stream.id] || stream]); + delete this._reverseStreams[this._streams[stream.id] ? this._streams[stream.id].id : stream.id]; + delete this._streams[stream.id]; + }; + + window.RTCPeerConnection.prototype.addTrack = function addTrack(track, stream) { + var _this13 = this; + + if (this.signalingState === 'closed') { + throw new DOMException('The RTCPeerConnection\'s signalingState is \'closed\'.', 'InvalidStateError'); + } + var streams = [].slice.call(arguments, 1); + if (streams.length !== 1 || !streams[0].getTracks().find(function (t) { + return t === track; + })) { + // this is not fully correct but all we can manage without + // [[associated MediaStreams]] internal slot. + throw new DOMException('The adapter.js addTrack polyfill only supports a single ' + ' stream which is associated with the specified track.', 'NotSupportedError'); + } + + var alreadyExists = this.getSenders().find(function (s) { + return s.track === track; + }); + if (alreadyExists) { + throw new DOMException('Track already exists.', 'InvalidAccessError'); + } + + this._streams = this._streams || {}; + this._reverseStreams = this._reverseStreams || {}; + var oldStream = this._streams[stream.id]; + if (oldStream) { + // this is using odd Chrome behaviour, use with caution: + // https://bugs.chromium.org/p/webrtc/issues/detail?id=7815 + // Note: we rely on the high-level addTrack/dtmf shim to + // create the sender with a dtmf sender. + oldStream.addTrack(track); + + // Trigger ONN async. + Promise.resolve().then(function () { + _this13.dispatchEvent(new Event('negotiationneeded')); + }); + } else { + var newStream = new window.MediaStream([track]); + this._streams[stream.id] = newStream; + this._reverseStreams[newStream.id] = stream; + this.addStream(newStream); + } + return this.getSenders().find(function (s) { + return s.track === track; + }); + }; + + // replace the internal stream id with the external one and + // vice versa. + function replaceInternalStreamId(pc, description) { + var sdp = description.sdp; + Object.keys(pc._reverseStreams || []).forEach(function (internalId) { + var externalStream = pc._reverseStreams[internalId]; + var internalStream = pc._streams[externalStream.id]; + sdp = sdp.replace(new RegExp(internalStream.id, 'g'), externalStream.id); + }); + return new RTCSessionDescription({ + type: description.type, + sdp: sdp + }); + } + function replaceExternalStreamId(pc, description) { + var sdp = description.sdp; + Object.keys(pc._reverseStreams || []).forEach(function (internalId) { + var externalStream = pc._reverseStreams[internalId]; + var internalStream = pc._streams[externalStream.id]; + sdp = sdp.replace(new RegExp(externalStream.id, 'g'), internalStream.id); + }); + return new RTCSessionDescription({ + type: description.type, + sdp: sdp + }); + } + ['createOffer', 'createAnswer'].forEach(function (method) { + var nativeMethod = window.RTCPeerConnection.prototype[method]; + var methodObj = _defineProperty({}, method, function () { + var _this14 = this; + + var args = arguments; + var isLegacyCall = arguments.length && typeof arguments[0] === 'function'; + if (isLegacyCall) { + return nativeMethod.apply(this, [function (description) { + var desc = replaceInternalStreamId(_this14, description); + args[0].apply(null, [desc]); + }, function (err) { + if (args[1]) { + args[1].apply(null, err); + } + }, arguments[2]]); + } + return nativeMethod.apply(this, arguments).then(function (description) { + return replaceInternalStreamId(_this14, description); + }); + }); + window.RTCPeerConnection.prototype[method] = methodObj[method]; + }); + + var origSetLocalDescription = window.RTCPeerConnection.prototype.setLocalDescription; + window.RTCPeerConnection.prototype.setLocalDescription = function setLocalDescription() { + if (!arguments.length || !arguments[0].type) { + return origSetLocalDescription.apply(this, arguments); + } + arguments[0] = replaceExternalStreamId(this, arguments[0]); + return origSetLocalDescription.apply(this, arguments); + }; + + // TODO: mangle getStats: https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamstats-streamidentifier + + var origLocalDescription = Object.getOwnPropertyDescriptor(window.RTCPeerConnection.prototype, 'localDescription'); + Object.defineProperty(window.RTCPeerConnection.prototype, 'localDescription', { + get: function get() { + var description = origLocalDescription.get.apply(this); + if (description.type === '') { + return description; + } + return replaceInternalStreamId(this, description); + } + }); + + window.RTCPeerConnection.prototype.removeTrack = function removeTrack(sender) { + var _this15 = this; + + if (this.signalingState === 'closed') { + throw new DOMException('The RTCPeerConnection\'s signalingState is \'closed\'.', 'InvalidStateError'); + } + // We can not yet check for sender instanceof RTCRtpSender + // since we shim RTPSender. So we check if sender._pc is set. + if (!sender._pc) { + throw new DOMException('Argument 1 of RTCPeerConnection.removeTrack ' + 'does not implement interface RTCRtpSender.', 'TypeError'); + } + var isLocal = sender._pc === this; + if (!isLocal) { + throw new DOMException('Sender was not created by this connection.', 'InvalidAccessError'); + } + + // Search for the native stream the senders track belongs to. + this._streams = this._streams || {}; + var stream = void 0; + Object.keys(this._streams).forEach(function (streamid) { + var hasTrack = _this15._streams[streamid].getTracks().find(function (track) { + return sender.track === track; + }); + if (hasTrack) { + stream = _this15._streams[streamid]; + } + }); + + if (stream) { + if (stream.getTracks().length === 1) { + // if this is the last track of the stream, remove the stream. This + // takes care of any shimmed _senders. + this.removeStream(this._reverseStreams[stream.id]); + } else { + // relying on the same odd chrome behaviour as above. + stream.removeTrack(sender.track); + } + this.dispatchEvent(new Event('negotiationneeded')); + } + }; + } + + function shimPeerConnection(window, browserDetails) { + if (!window.RTCPeerConnection && window.webkitRTCPeerConnection) { + // very basic support for old versions. + window.RTCPeerConnection = window.webkitRTCPeerConnection; + } + if (!window.RTCPeerConnection) { + return; + } + + // shim implicit creation of RTCSessionDescription/RTCIceCandidate + if (browserDetails.version < 53) { + ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'].forEach(function (method) { + var nativeMethod = window.RTCPeerConnection.prototype[method]; + var methodObj = _defineProperty({}, method, function () { + arguments[0] = new (method === 'addIceCandidate' ? window.RTCIceCandidate : window.RTCSessionDescription)(arguments[0]); + return nativeMethod.apply(this, arguments); + }); + window.RTCPeerConnection.prototype[method] = methodObj[method]; + }); + } + } + +// Attempt to fix ONN in plan-b mode. + function fixNegotiationNeeded(window, browserDetails) { + utils.wrapPeerConnectionEvent(window, 'negotiationneeded', function (e) { + var pc = e.target; + if (browserDetails.version < 72 || pc.getConfiguration && pc.getConfiguration().sdpSemantics === 'plan-b') { + if (pc.signalingState !== 'stable') { + return; + } + } + return e; + }); + } + + },{"../utils.js":11,"./getdisplaymedia":4,"./getusermedia":5}],4:[function(require,module,exports){ + /* + * Copyright (c) 2018 The adapter.js project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.shimGetDisplayMedia = shimGetDisplayMedia; + function shimGetDisplayMedia(window, getSourceId) { + if (window.navigator.mediaDevices && 'getDisplayMedia' in window.navigator.mediaDevices) { + return; + } + if (!window.navigator.mediaDevices) { + return; + } + // getSourceId is a function that returns a promise resolving with + // the sourceId of the screen/window/tab to be shared. + if (typeof getSourceId !== 'function') { + console.error('shimGetDisplayMedia: getSourceId argument is not ' + 'a function'); + return; + } + window.navigator.mediaDevices.getDisplayMedia = function getDisplayMedia(constraints) { + return getSourceId(constraints).then(function (sourceId) { + var widthSpecified = constraints.video && constraints.video.width; + var heightSpecified = constraints.video && constraints.video.height; + var frameRateSpecified = constraints.video && constraints.video.frameRate; + constraints.video = { + mandatory: { + chromeMediaSource: 'desktop', + chromeMediaSourceId: sourceId, + maxFrameRate: frameRateSpecified || 3 + } + }; + if (widthSpecified) { + constraints.video.mandatory.maxWidth = widthSpecified; + } + if (heightSpecified) { + constraints.video.mandatory.maxHeight = heightSpecified; + } + return window.navigator.mediaDevices.getUserMedia(constraints); + }); + }; + } + + },{}],5:[function(require,module,exports){ + /* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + + exports.shimGetUserMedia = shimGetUserMedia; + + var _utils = require('../utils.js'); + + var utils = _interopRequireWildcard(_utils); + + function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + + var logging = utils.log; + + function shimGetUserMedia(window, browserDetails) { + var navigator = window && window.navigator; + + if (!navigator.mediaDevices) { + return; + } + + var constraintsToChrome_ = function constraintsToChrome_(c) { + if ((typeof c === 'undefined' ? 'undefined' : _typeof(c)) !== 'object' || c.mandatory || c.optional) { + return c; + } + var cc = {}; + Object.keys(c).forEach(function (key) { + if (key === 'require' || key === 'advanced' || key === 'mediaSource') { + return; + } + var r = _typeof(c[key]) === 'object' ? c[key] : { ideal: c[key] }; + if (r.exact !== undefined && typeof r.exact === 'number') { + r.min = r.max = r.exact; + } + var oldname_ = function oldname_(prefix, name) { + if (prefix) { + return prefix + name.charAt(0).toUpperCase() + name.slice(1); + } + return name === 'deviceId' ? 'sourceId' : name; + }; + if (r.ideal !== undefined) { + cc.optional = cc.optional || []; + var oc = {}; + if (typeof r.ideal === 'number') { + oc[oldname_('min', key)] = r.ideal; + cc.optional.push(oc); + oc = {}; + oc[oldname_('max', key)] = r.ideal; + cc.optional.push(oc); + } else { + oc[oldname_('', key)] = r.ideal; + cc.optional.push(oc); + } + } + if (r.exact !== undefined && typeof r.exact !== 'number') { + cc.mandatory = cc.mandatory || {}; + cc.mandatory[oldname_('', key)] = r.exact; + } else { + ['min', 'max'].forEach(function (mix) { + if (r[mix] !== undefined) { + cc.mandatory = cc.mandatory || {}; + cc.mandatory[oldname_(mix, key)] = r[mix]; + } + }); + } + }); + if (c.advanced) { + cc.optional = (cc.optional || []).concat(c.advanced); + } + return cc; + }; + + var shimConstraints_ = function shimConstraints_(constraints, func) { + if (browserDetails.version >= 61) { + return func(constraints); + } + constraints = JSON.parse(JSON.stringify(constraints)); + if (constraints && _typeof(constraints.audio) === 'object') { + var remap = function remap(obj, a, b) { + if (a in obj && !(b in obj)) { + obj[b] = obj[a]; + delete obj[a]; + } + }; + constraints = JSON.parse(JSON.stringify(constraints)); + remap(constraints.audio, 'autoGainControl', 'googAutoGainControl'); + remap(constraints.audio, 'noiseSuppression', 'googNoiseSuppression'); + constraints.audio = constraintsToChrome_(constraints.audio); + } + if (constraints && _typeof(constraints.video) === 'object') { + // Shim facingMode for mobile & surface pro. + var face = constraints.video.facingMode; + face = face && ((typeof face === 'undefined' ? 'undefined' : _typeof(face)) === 'object' ? face : { ideal: face }); + var getSupportedFacingModeLies = browserDetails.version < 66; + + if (face && (face.exact === 'user' || face.exact === 'environment' || face.ideal === 'user' || face.ideal === 'environment') && !(navigator.mediaDevices.getSupportedConstraints && navigator.mediaDevices.getSupportedConstraints().facingMode && !getSupportedFacingModeLies)) { + delete constraints.video.facingMode; + var matches = void 0; + if (face.exact === 'environment' || face.ideal === 'environment') { + matches = ['back', 'rear']; + } else if (face.exact === 'user' || face.ideal === 'user') { + matches = ['front']; + } + if (matches) { + // Look for matches in label, or use last cam for back (typical). + return navigator.mediaDevices.enumerateDevices().then(function (devices) { + devices = devices.filter(function (d) { + return d.kind === 'videoinput'; + }); + var dev = devices.find(function (d) { + return matches.some(function (match) { + return d.label.toLowerCase().includes(match); + }); + }); + if (!dev && devices.length && matches.includes('back')) { + dev = devices[devices.length - 1]; // more likely the back cam + } + if (dev) { + constraints.video.deviceId = face.exact ? { exact: dev.deviceId } : { ideal: dev.deviceId }; + } + constraints.video = constraintsToChrome_(constraints.video); + logging('chrome: ' + JSON.stringify(constraints)); + return func(constraints); + }); + } + } + constraints.video = constraintsToChrome_(constraints.video); + } + logging('chrome: ' + JSON.stringify(constraints)); + return func(constraints); + }; + + var shimError_ = function shimError_(e) { + if (browserDetails.version >= 64) { + return e; + } + return { + name: { + PermissionDeniedError: 'NotAllowedError', + PermissionDismissedError: 'NotAllowedError', + InvalidStateError: 'NotAllowedError', + DevicesNotFoundError: 'NotFoundError', + ConstraintNotSatisfiedError: 'OverconstrainedError', + TrackStartError: 'NotReadableError', + MediaDeviceFailedDueToShutdown: 'NotAllowedError', + MediaDeviceKillSwitchOn: 'NotAllowedError', + TabCaptureError: 'AbortError', + ScreenCaptureError: 'AbortError', + DeviceCaptureError: 'AbortError' + }[e.name] || e.name, + message: e.message, + constraint: e.constraint || e.constraintName, + toString: function toString() { + return this.name + (this.message && ': ') + this.message; + } + }; + }; + + var getUserMedia_ = function getUserMedia_(constraints, onSuccess, onError) { + shimConstraints_(constraints, function (c) { + navigator.webkitGetUserMedia(c, onSuccess, function (e) { + if (onError) { + onError(shimError_(e)); + } + }); + }); + }; + navigator.getUserMedia = getUserMedia_.bind(navigator); + + // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia + // function which returns a Promise, it does not accept spec-style + // constraints. + if (navigator.mediaDevices.getUserMedia) { + var origGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices); + navigator.mediaDevices.getUserMedia = function (cs) { + return shimConstraints_(cs, function (c) { + return origGetUserMedia(c).then(function (stream) { + if (c.audio && !stream.getAudioTracks().length || c.video && !stream.getVideoTracks().length) { + stream.getTracks().forEach(function (track) { + track.stop(); + }); + throw new DOMException('', 'NotFoundError'); + } + return stream; + }, function (e) { + return Promise.reject(shimError_(e)); + }); + }); + }; + } + } + + },{"../utils.js":11}],6:[function(require,module,exports){ + /* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + + exports.shimRTCIceCandidate = shimRTCIceCandidate; + exports.shimMaxMessageSize = shimMaxMessageSize; + exports.shimSendThrowTypeError = shimSendThrowTypeError; + exports.shimConnectionState = shimConnectionState; + exports.removeExtmapAllowMixed = removeExtmapAllowMixed; + exports.shimAddIceCandidateNullOrEmpty = shimAddIceCandidateNullOrEmpty; + + var _sdp = require('sdp'); + + var _sdp2 = _interopRequireDefault(_sdp); + + var _utils = require('./utils'); + + var utils = _interopRequireWildcard(_utils); + + function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function shimRTCIceCandidate(window) { + // foundation is arbitrarily chosen as an indicator for full support for + // https://w3c.github.io/webrtc-pc/#rtcicecandidate-interface + if (!window.RTCIceCandidate || window.RTCIceCandidate && 'foundation' in window.RTCIceCandidate.prototype) { + return; + } + + var NativeRTCIceCandidate = window.RTCIceCandidate; + window.RTCIceCandidate = function RTCIceCandidate(args) { + // Remove the a= which shouldn't be part of the candidate string. + if ((typeof args === 'undefined' ? 'undefined' : _typeof(args)) === 'object' && args.candidate && args.candidate.indexOf('a=') === 0) { + args = JSON.parse(JSON.stringify(args)); + args.candidate = args.candidate.substr(2); + } + + if (args.candidate && args.candidate.length) { + // Augment the native candidate with the parsed fields. + var nativeCandidate = new NativeRTCIceCandidate(args); + var parsedCandidate = _sdp2.default.parseCandidate(args.candidate); + var augmentedCandidate = Object.assign(nativeCandidate, parsedCandidate); + + // Add a serializer that does not serialize the extra attributes. + augmentedCandidate.toJSON = function toJSON() { + return { + candidate: augmentedCandidate.candidate, + sdpMid: augmentedCandidate.sdpMid, + sdpMLineIndex: augmentedCandidate.sdpMLineIndex, + usernameFragment: augmentedCandidate.usernameFragment + }; + }; + return augmentedCandidate; + } + return new NativeRTCIceCandidate(args); + }; + window.RTCIceCandidate.prototype = NativeRTCIceCandidate.prototype; + + // Hook up the augmented candidate in onicecandidate and + // addEventListener('icecandidate', ...) + utils.wrapPeerConnectionEvent(window, 'icecandidate', function (e) { + if (e.candidate) { + Object.defineProperty(e, 'candidate', { + value: new window.RTCIceCandidate(e.candidate), + writable: 'false' + }); + } + return e; + }); + } + + function shimMaxMessageSize(window, browserDetails) { + if (!window.RTCPeerConnection) { + return; + } + + if (!('sctp' in window.RTCPeerConnection.prototype)) { + Object.defineProperty(window.RTCPeerConnection.prototype, 'sctp', { + get: function get() { + return typeof this._sctp === 'undefined' ? null : this._sctp; + } + }); + } + + var sctpInDescription = function sctpInDescription(description) { + if (!description || !description.sdp) { + return false; + } + var sections = _sdp2.default.splitSections(description.sdp); + sections.shift(); + return sections.some(function (mediaSection) { + var mLine = _sdp2.default.parseMLine(mediaSection); + return mLine && mLine.kind === 'application' && mLine.protocol.indexOf('SCTP') !== -1; + }); + }; + + var getRemoteFirefoxVersion = function getRemoteFirefoxVersion(description) { + // TODO: Is there a better solution for detecting Firefox? + var match = description.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/); + if (match === null || match.length < 2) { + return -1; + } + var version = parseInt(match[1], 10); + // Test for NaN (yes, this is ugly) + return version !== version ? -1 : version; + }; + + var getCanSendMaxMessageSize = function getCanSendMaxMessageSize(remoteIsFirefox) { + // Every implementation we know can send at least 64 KiB. + // Note: Although Chrome is technically able to send up to 256 KiB, the + // data does not reach the other peer reliably. + // See: https://bugs.chromium.org/p/webrtc/issues/detail?id=8419 + var canSendMaxMessageSize = 65536; + if (browserDetails.browser === 'firefox') { + if (browserDetails.version < 57) { + if (remoteIsFirefox === -1) { + // FF < 57 will send in 16 KiB chunks using the deprecated PPID + // fragmentation. + canSendMaxMessageSize = 16384; + } else { + // However, other FF (and RAWRTC) can reassemble PPID-fragmented + // messages. Thus, supporting ~2 GiB when sending. + canSendMaxMessageSize = 2147483637; + } + } else if (browserDetails.version < 60) { + // Currently, all FF >= 57 will reset the remote maximum message size + // to the default value when a data channel is created at a later + // stage. :( + // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831 + canSendMaxMessageSize = browserDetails.version === 57 ? 65535 : 65536; + } else { + // FF >= 60 supports sending ~2 GiB + canSendMaxMessageSize = 2147483637; + } + } + return canSendMaxMessageSize; + }; + + var getMaxMessageSize = function getMaxMessageSize(description, remoteIsFirefox) { + // Note: 65536 bytes is the default value from the SDP spec. Also, + // every implementation we know supports receiving 65536 bytes. + var maxMessageSize = 65536; + + // FF 57 has a slightly incorrect default remote max message size, so + // we need to adjust it here to avoid a failure when sending. + // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1425697 + if (browserDetails.browser === 'firefox' && browserDetails.version === 57) { + maxMessageSize = 65535; + } + + var match = _sdp2.default.matchPrefix(description.sdp, 'a=max-message-size:'); + if (match.length > 0) { + maxMessageSize = parseInt(match[0].substr(19), 10); + } else if (browserDetails.browser === 'firefox' && remoteIsFirefox !== -1) { + // If the maximum message size is not present in the remote SDP and + // both local and remote are Firefox, the remote peer can receive + // ~2 GiB. + maxMessageSize = 2147483637; + } + return maxMessageSize; + }; + + var origSetRemoteDescription = window.RTCPeerConnection.prototype.setRemoteDescription; + window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription() { + this._sctp = null; + // Chrome decided to not expose .sctp in plan-b mode. + // As usual, adapter.js has to do an 'ugly worakaround' + // to cover up the mess. + if (browserDetails.browser === 'chrome' && browserDetails.version >= 76) { + var _getConfiguration = this.getConfiguration(), + sdpSemantics = _getConfiguration.sdpSemantics; + + if (sdpSemantics === 'plan-b') { + Object.defineProperty(this, 'sctp', { + get: function get() { + return typeof this._sctp === 'undefined' ? null : this._sctp; + }, + + enumerable: true, + configurable: true + }); + } + } + + if (sctpInDescription(arguments[0])) { + // Check if the remote is FF. + var isFirefox = getRemoteFirefoxVersion(arguments[0]); + + // Get the maximum message size the local peer is capable of sending + var canSendMMS = getCanSendMaxMessageSize(isFirefox); + + // Get the maximum message size of the remote peer. + var remoteMMS = getMaxMessageSize(arguments[0], isFirefox); + + // Determine final maximum message size + var maxMessageSize = void 0; + if (canSendMMS === 0 && remoteMMS === 0) { + maxMessageSize = Number.POSITIVE_INFINITY; + } else if (canSendMMS === 0 || remoteMMS === 0) { + maxMessageSize = Math.max(canSendMMS, remoteMMS); + } else { + maxMessageSize = Math.min(canSendMMS, remoteMMS); + } + + // Create a dummy RTCSctpTransport object and the 'maxMessageSize' + // attribute. + var sctp = {}; + Object.defineProperty(sctp, 'maxMessageSize', { + get: function get() { + return maxMessageSize; + } + }); + this._sctp = sctp; + } + + return origSetRemoteDescription.apply(this, arguments); + }; + } + + function shimSendThrowTypeError(window) { + if (!(window.RTCPeerConnection && 'createDataChannel' in window.RTCPeerConnection.prototype)) { + return; + } + + // Note: Although Firefox >= 57 has a native implementation, the maximum + // message size can be reset for all data channels at a later stage. + // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831 + + function wrapDcSend(dc, pc) { + var origDataChannelSend = dc.send; + dc.send = function send() { + var data = arguments[0]; + var length = data.length || data.size || data.byteLength; + if (dc.readyState === 'open' && pc.sctp && length > pc.sctp.maxMessageSize) { + throw new TypeError('Message too large (can send a maximum of ' + pc.sctp.maxMessageSize + ' bytes)'); + } + return origDataChannelSend.apply(dc, arguments); + }; + } + var origCreateDataChannel = window.RTCPeerConnection.prototype.createDataChannel; + window.RTCPeerConnection.prototype.createDataChannel = function createDataChannel() { + var dataChannel = origCreateDataChannel.apply(this, arguments); + wrapDcSend(dataChannel, this); + return dataChannel; + }; + utils.wrapPeerConnectionEvent(window, 'datachannel', function (e) { + wrapDcSend(e.channel, e.target); + return e; + }); + } + + /* shims RTCConnectionState by pretending it is the same as iceConnectionState. + * See https://bugs.chromium.org/p/webrtc/issues/detail?id=6145#c12 + * for why this is a valid hack in Chrome. In Firefox it is slightly incorrect + * since DTLS failures would be hidden. See + * https://bugzilla.mozilla.org/show_bug.cgi?id=1265827 + * for the Firefox tracking bug. + */ + function shimConnectionState(window) { + if (!window.RTCPeerConnection || 'connectionState' in window.RTCPeerConnection.prototype) { + return; + } + var proto = window.RTCPeerConnection.prototype; + Object.defineProperty(proto, 'connectionState', { + get: function get() { + return { + completed: 'connected', + checking: 'connecting' + }[this.iceConnectionState] || this.iceConnectionState; + }, + + enumerable: true, + configurable: true + }); + Object.defineProperty(proto, 'onconnectionstatechange', { + get: function get() { + return this._onconnectionstatechange || null; + }, + set: function set(cb) { + if (this._onconnectionstatechange) { + this.removeEventListener('connectionstatechange', this._onconnectionstatechange); + delete this._onconnectionstatechange; + } + if (cb) { + this.addEventListener('connectionstatechange', this._onconnectionstatechange = cb); + } + }, + + enumerable: true, + configurable: true + }); + + ['setLocalDescription', 'setRemoteDescription'].forEach(function (method) { + var origMethod = proto[method]; + proto[method] = function () { + if (!this._connectionstatechangepoly) { + this._connectionstatechangepoly = function (e) { + var pc = e.target; + if (pc._lastConnectionState !== pc.connectionState) { + pc._lastConnectionState = pc.connectionState; + var newEvent = new Event('connectionstatechange', e); + pc.dispatchEvent(newEvent); + } + return e; + }; + this.addEventListener('iceconnectionstatechange', this._connectionstatechangepoly); + } + return origMethod.apply(this, arguments); + }; + }); + } + + function removeExtmapAllowMixed(window, browserDetails) { + /* remove a=extmap-allow-mixed for webrtc.org < M71 */ + if (!window.RTCPeerConnection) { + return; + } + if (browserDetails.browser === 'chrome' && browserDetails.version >= 71) { + return; + } + if (browserDetails.browser === 'safari' && browserDetails.version >= 605) { + return; + } + var nativeSRD = window.RTCPeerConnection.prototype.setRemoteDescription; + window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription(desc) { + if (desc && desc.sdp && desc.sdp.indexOf('\na=extmap-allow-mixed') !== -1) { + var sdp = desc.sdp.split('\n').filter(function (line) { + return line.trim() !== 'a=extmap-allow-mixed'; + }).join('\n'); + // Safari enforces read-only-ness of RTCSessionDescription fields. + if (window.RTCSessionDescription && desc instanceof window.RTCSessionDescription) { + arguments[0] = new window.RTCSessionDescription({ + type: desc.type, + sdp: sdp + }); + } else { + desc.sdp = sdp; + } + } + return nativeSRD.apply(this, arguments); + }; + } + + function shimAddIceCandidateNullOrEmpty(window, browserDetails) { + // Support for addIceCandidate(null or undefined) + // as well as addIceCandidate({candidate: "", ...}) + // https://bugs.chromium.org/p/chromium/issues/detail?id=978582 + // Note: must be called before other polyfills which change the signature. + if (!(window.RTCPeerConnection && window.RTCPeerConnection.prototype)) { + return; + } + var nativeAddIceCandidate = window.RTCPeerConnection.prototype.addIceCandidate; + if (!nativeAddIceCandidate || nativeAddIceCandidate.length === 0) { + return; + } + window.RTCPeerConnection.prototype.addIceCandidate = function addIceCandidate() { + if (!arguments[0]) { + if (arguments[1]) { + arguments[1].apply(null); + } + return Promise.resolve(); + } + // Firefox 68+ emits and processes {candidate: "", ...}, ignore + // in older versions. + // Native support for ignoring exists for Chrome M77+. + // Safari ignores as well, exact version unknown but works in the same + // version that also ignores addIceCandidate(null). + if ((browserDetails.browser === 'chrome' && browserDetails.version < 78 || browserDetails.browser === 'firefox' && browserDetails.version < 68 || browserDetails.browser === 'safari') && arguments[0] && arguments[0].candidate === '') { + return Promise.resolve(); + } + return nativeAddIceCandidate.apply(this, arguments); + }; + } + + },{"./utils":11,"sdp":12}],7:[function(require,module,exports){ + /* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.shimGetDisplayMedia = exports.shimGetUserMedia = undefined; + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + + var _getusermedia = require('./getusermedia'); + + Object.defineProperty(exports, 'shimGetUserMedia', { + enumerable: true, + get: function get() { + return _getusermedia.shimGetUserMedia; + } + }); + + var _getdisplaymedia = require('./getdisplaymedia'); + + Object.defineProperty(exports, 'shimGetDisplayMedia', { + enumerable: true, + get: function get() { + return _getdisplaymedia.shimGetDisplayMedia; + } + }); + exports.shimOnTrack = shimOnTrack; + exports.shimPeerConnection = shimPeerConnection; + exports.shimSenderGetStats = shimSenderGetStats; + exports.shimReceiverGetStats = shimReceiverGetStats; + exports.shimRemoveStream = shimRemoveStream; + exports.shimRTCDataChannel = shimRTCDataChannel; + exports.shimAddTransceiver = shimAddTransceiver; + exports.shimGetParameters = shimGetParameters; + exports.shimCreateOffer = shimCreateOffer; + exports.shimCreateAnswer = shimCreateAnswer; + + var _utils = require('../utils'); + + var utils = _interopRequireWildcard(_utils); + + function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + + function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + + function shimOnTrack(window) { + if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCTrackEvent && 'receiver' in window.RTCTrackEvent.prototype && !('transceiver' in window.RTCTrackEvent.prototype)) { + Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', { + get: function get() { + return { receiver: this.receiver }; + } + }); + } + } + + function shimPeerConnection(window, browserDetails) { + if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) !== 'object' || !(window.RTCPeerConnection || window.mozRTCPeerConnection)) { + return; // probably media.peerconnection.enabled=false in about:config + } + if (!window.RTCPeerConnection && window.mozRTCPeerConnection) { + // very basic support for old versions. + window.RTCPeerConnection = window.mozRTCPeerConnection; + } + + if (browserDetails.version < 53) { + // shim away need for obsolete RTCIceCandidate/RTCSessionDescription. + ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'].forEach(function (method) { + var nativeMethod = window.RTCPeerConnection.prototype[method]; + var methodObj = _defineProperty({}, method, function () { + arguments[0] = new (method === 'addIceCandidate' ? window.RTCIceCandidate : window.RTCSessionDescription)(arguments[0]); + return nativeMethod.apply(this, arguments); + }); + window.RTCPeerConnection.prototype[method] = methodObj[method]; + }); + } + + var modernStatsTypes = { + inboundrtp: 'inbound-rtp', + outboundrtp: 'outbound-rtp', + candidatepair: 'candidate-pair', + localcandidate: 'local-candidate', + remotecandidate: 'remote-candidate' + }; + + var nativeGetStats = window.RTCPeerConnection.prototype.getStats; + window.RTCPeerConnection.prototype.getStats = function getStats() { + var _arguments = Array.prototype.slice.call(arguments), + selector = _arguments[0], + onSucc = _arguments[1], + onErr = _arguments[2]; + + return nativeGetStats.apply(this, [selector || null]).then(function (stats) { + if (browserDetails.version < 53 && !onSucc) { + // Shim only promise getStats with spec-hyphens in type names + // Leave callback version alone; misc old uses of forEach before Map + try { + stats.forEach(function (stat) { + stat.type = modernStatsTypes[stat.type] || stat.type; + }); + } catch (e) { + if (e.name !== 'TypeError') { + throw e; + } + // Avoid TypeError: "type" is read-only, in old versions. 34-43ish + stats.forEach(function (stat, i) { + stats.set(i, Object.assign({}, stat, { + type: modernStatsTypes[stat.type] || stat.type + })); + }); + } + } + return stats; + }).then(onSucc, onErr); + }; + } + + function shimSenderGetStats(window) { + if (!((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection && window.RTCRtpSender)) { + return; + } + if (window.RTCRtpSender && 'getStats' in window.RTCRtpSender.prototype) { + return; + } + var origGetSenders = window.RTCPeerConnection.prototype.getSenders; + if (origGetSenders) { + window.RTCPeerConnection.prototype.getSenders = function getSenders() { + var _this = this; + + var senders = origGetSenders.apply(this, []); + senders.forEach(function (sender) { + return sender._pc = _this; + }); + return senders; + }; + } + + var origAddTrack = window.RTCPeerConnection.prototype.addTrack; + if (origAddTrack) { + window.RTCPeerConnection.prototype.addTrack = function addTrack() { + var sender = origAddTrack.apply(this, arguments); + sender._pc = this; + return sender; + }; + } + window.RTCRtpSender.prototype.getStats = function getStats() { + return this.track ? this._pc.getStats(this.track) : Promise.resolve(new Map()); + }; + } + + function shimReceiverGetStats(window) { + if (!((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection && window.RTCRtpSender)) { + return; + } + if (window.RTCRtpSender && 'getStats' in window.RTCRtpReceiver.prototype) { + return; + } + var origGetReceivers = window.RTCPeerConnection.prototype.getReceivers; + if (origGetReceivers) { + window.RTCPeerConnection.prototype.getReceivers = function getReceivers() { + var _this2 = this; + + var receivers = origGetReceivers.apply(this, []); + receivers.forEach(function (receiver) { + return receiver._pc = _this2; + }); + return receivers; + }; + } + utils.wrapPeerConnectionEvent(window, 'track', function (e) { + e.receiver._pc = e.srcElement; + return e; + }); + window.RTCRtpReceiver.prototype.getStats = function getStats() { + return this._pc.getStats(this.track); + }; + } + + function shimRemoveStream(window) { + if (!window.RTCPeerConnection || 'removeStream' in window.RTCPeerConnection.prototype) { + return; + } + window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) { + var _this3 = this; + + utils.deprecated('removeStream', 'removeTrack'); + this.getSenders().forEach(function (sender) { + if (sender.track && stream.getTracks().includes(sender.track)) { + _this3.removeTrack(sender); + } + }); + }; + } + + function shimRTCDataChannel(window) { + // rename DataChannel to RTCDataChannel (native fix in FF60): + // https://bugzilla.mozilla.org/show_bug.cgi?id=1173851 + if (window.DataChannel && !window.RTCDataChannel) { + window.RTCDataChannel = window.DataChannel; + } + } + + function shimAddTransceiver(window) { + // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647 + // Firefox ignores the init sendEncodings options passed to addTransceiver + // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918 + if (!((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection)) { + return; + } + var origAddTransceiver = window.RTCPeerConnection.prototype.addTransceiver; + if (origAddTransceiver) { + window.RTCPeerConnection.prototype.addTransceiver = function addTransceiver() { + this.setParametersPromises = []; + var initParameters = arguments[1]; + var shouldPerformCheck = initParameters && 'sendEncodings' in initParameters; + if (shouldPerformCheck) { + // If sendEncodings params are provided, validate grammar + initParameters.sendEncodings.forEach(function (encodingParam) { + if ('rid' in encodingParam) { + var ridRegex = /^[a-z0-9]{0,16}$/i; + if (!ridRegex.test(encodingParam.rid)) { + throw new TypeError('Invalid RID value provided.'); + } + } + if ('scaleResolutionDownBy' in encodingParam) { + if (!(parseFloat(encodingParam.scaleResolutionDownBy) >= 1.0)) { + throw new RangeError('scale_resolution_down_by must be >= 1.0'); + } + } + if ('maxFramerate' in encodingParam) { + if (!(parseFloat(encodingParam.maxFramerate) >= 0)) { + throw new RangeError('max_framerate must be >= 0.0'); + } + } + }); + } + var transceiver = origAddTransceiver.apply(this, arguments); + if (shouldPerformCheck) { + // Check if the init options were applied. If not we do this in an + // asynchronous way and save the promise reference in a global object. + // This is an ugly hack, but at the same time is way more robust than + // checking the sender parameters before and after the createOffer + // Also note that after the createoffer we are not 100% sure that + // the params were asynchronously applied so we might miss the + // opportunity to recreate offer. + var sender = transceiver.sender; + + var params = sender.getParameters(); + if (!('encodings' in params) || + // Avoid being fooled by patched getParameters() below. + params.encodings.length === 1 && Object.keys(params.encodings[0]).length === 0) { + params.encodings = initParameters.sendEncodings; + sender.sendEncodings = initParameters.sendEncodings; + this.setParametersPromises.push(sender.setParameters(params).then(function () { + delete sender.sendEncodings; + }).catch(function () { + delete sender.sendEncodings; + })); + } + } + return transceiver; + }; + } + } + + function shimGetParameters(window) { + if (!((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCRtpSender)) { + return; + } + var origGetParameters = window.RTCRtpSender.prototype.getParameters; + if (origGetParameters) { + window.RTCRtpSender.prototype.getParameters = function getParameters() { + var params = origGetParameters.apply(this, arguments); + if (!('encodings' in params)) { + params.encodings = [].concat(this.sendEncodings || [{}]); + } + return params; + }; + } + } + + function shimCreateOffer(window) { + // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647 + // Firefox ignores the init sendEncodings options passed to addTransceiver + // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918 + if (!((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection)) { + return; + } + var origCreateOffer = window.RTCPeerConnection.prototype.createOffer; + window.RTCPeerConnection.prototype.createOffer = function createOffer() { + var _this4 = this, + _arguments2 = arguments; + + if (this.setParametersPromises && this.setParametersPromises.length) { + return Promise.all(this.setParametersPromises).then(function () { + return origCreateOffer.apply(_this4, _arguments2); + }).finally(function () { + _this4.setParametersPromises = []; + }); + } + return origCreateOffer.apply(this, arguments); + }; + } + + function shimCreateAnswer(window) { + // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647 + // Firefox ignores the init sendEncodings options passed to addTransceiver + // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918 + if (!((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection)) { + return; + } + var origCreateAnswer = window.RTCPeerConnection.prototype.createAnswer; + window.RTCPeerConnection.prototype.createAnswer = function createAnswer() { + var _this5 = this, + _arguments3 = arguments; + + if (this.setParametersPromises && this.setParametersPromises.length) { + return Promise.all(this.setParametersPromises).then(function () { + return origCreateAnswer.apply(_this5, _arguments3); + }).finally(function () { + _this5.setParametersPromises = []; + }); + } + return origCreateAnswer.apply(this, arguments); + }; + } + + },{"../utils":11,"./getdisplaymedia":8,"./getusermedia":9}],8:[function(require,module,exports){ + /* + * Copyright (c) 2018 The adapter.js project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.shimGetDisplayMedia = shimGetDisplayMedia; + function shimGetDisplayMedia(window, preferredMediaSource) { + if (window.navigator.mediaDevices && 'getDisplayMedia' in window.navigator.mediaDevices) { + return; + } + if (!window.navigator.mediaDevices) { + return; + } + window.navigator.mediaDevices.getDisplayMedia = function getDisplayMedia(constraints) { + if (!(constraints && constraints.video)) { + var err = new DOMException('getDisplayMedia without video ' + 'constraints is undefined'); + err.name = 'NotFoundError'; + // from https://heycam.github.io/webidl/#idl-DOMException-error-names + err.code = 8; + return Promise.reject(err); + } + if (constraints.video === true) { + constraints.video = { mediaSource: preferredMediaSource }; + } else { + constraints.video.mediaSource = preferredMediaSource; + } + return window.navigator.mediaDevices.getUserMedia(constraints); + }; + } + + },{}],9:[function(require,module,exports){ + /* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + + exports.shimGetUserMedia = shimGetUserMedia; + + var _utils = require('../utils'); + + var utils = _interopRequireWildcard(_utils); + + function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + + function shimGetUserMedia(window, browserDetails) { + var navigator = window && window.navigator; + var MediaStreamTrack = window && window.MediaStreamTrack; + + navigator.getUserMedia = function (constraints, onSuccess, onError) { + // Replace Firefox 44+'s deprecation warning with unprefixed version. + utils.deprecated('navigator.getUserMedia', 'navigator.mediaDevices.getUserMedia'); + navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError); + }; + + if (!(browserDetails.version > 55 && 'autoGainControl' in navigator.mediaDevices.getSupportedConstraints())) { + var remap = function remap(obj, a, b) { + if (a in obj && !(b in obj)) { + obj[b] = obj[a]; + delete obj[a]; + } + }; + + var nativeGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices); + navigator.mediaDevices.getUserMedia = function (c) { + if ((typeof c === 'undefined' ? 'undefined' : _typeof(c)) === 'object' && _typeof(c.audio) === 'object') { + c = JSON.parse(JSON.stringify(c)); + remap(c.audio, 'autoGainControl', 'mozAutoGainControl'); + remap(c.audio, 'noiseSuppression', 'mozNoiseSuppression'); + } + return nativeGetUserMedia(c); + }; + + if (MediaStreamTrack && MediaStreamTrack.prototype.getSettings) { + var nativeGetSettings = MediaStreamTrack.prototype.getSettings; + MediaStreamTrack.prototype.getSettings = function () { + var obj = nativeGetSettings.apply(this, arguments); + remap(obj, 'mozAutoGainControl', 'autoGainControl'); + remap(obj, 'mozNoiseSuppression', 'noiseSuppression'); + return obj; + }; + } + + if (MediaStreamTrack && MediaStreamTrack.prototype.applyConstraints) { + var nativeApplyConstraints = MediaStreamTrack.prototype.applyConstraints; + MediaStreamTrack.prototype.applyConstraints = function (c) { + if (this.kind === 'audio' && (typeof c === 'undefined' ? 'undefined' : _typeof(c)) === 'object') { + c = JSON.parse(JSON.stringify(c)); + remap(c, 'autoGainControl', 'mozAutoGainControl'); + remap(c, 'noiseSuppression', 'mozNoiseSuppression'); + } + return nativeApplyConstraints.apply(this, [c]); + }; + } + } + } + + },{"../utils":11}],10:[function(require,module,exports){ + /* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + + exports.shimLocalStreamsAPI = shimLocalStreamsAPI; + exports.shimRemoteStreamsAPI = shimRemoteStreamsAPI; + exports.shimCallbacksAPI = shimCallbacksAPI; + exports.shimGetUserMedia = shimGetUserMedia; + exports.shimConstraints = shimConstraints; + exports.shimRTCIceServerUrls = shimRTCIceServerUrls; + exports.shimTrackEventTransceiver = shimTrackEventTransceiver; + exports.shimCreateOfferLegacy = shimCreateOfferLegacy; + exports.shimAudioContext = shimAudioContext; + + var _utils = require('../utils'); + + var utils = _interopRequireWildcard(_utils); + + function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + + function shimLocalStreamsAPI(window) { + if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) !== 'object' || !window.RTCPeerConnection) { + return; + } + if (!('getLocalStreams' in window.RTCPeerConnection.prototype)) { + window.RTCPeerConnection.prototype.getLocalStreams = function getLocalStreams() { + if (!this._localStreams) { + this._localStreams = []; + } + return this._localStreams; + }; + } + if (!('addStream' in window.RTCPeerConnection.prototype)) { + var _addTrack = window.RTCPeerConnection.prototype.addTrack; + window.RTCPeerConnection.prototype.addStream = function addStream(stream) { + var _this = this; + + if (!this._localStreams) { + this._localStreams = []; + } + if (!this._localStreams.includes(stream)) { + this._localStreams.push(stream); + } + // Try to emulate Chrome's behaviour of adding in audio-video order. + // Safari orders by track id. + stream.getAudioTracks().forEach(function (track) { + return _addTrack.call(_this, track, stream); + }); + stream.getVideoTracks().forEach(function (track) { + return _addTrack.call(_this, track, stream); + }); + }; + + window.RTCPeerConnection.prototype.addTrack = function addTrack(track) { + var _this2 = this; + + for (var _len = arguments.length, streams = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + streams[_key - 1] = arguments[_key]; + } + + if (streams) { + streams.forEach(function (stream) { + if (!_this2._localStreams) { + _this2._localStreams = [stream]; + } else if (!_this2._localStreams.includes(stream)) { + _this2._localStreams.push(stream); + } + }); + } + return _addTrack.apply(this, arguments); + }; + } + if (!('removeStream' in window.RTCPeerConnection.prototype)) { + window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) { + var _this3 = this; + + if (!this._localStreams) { + this._localStreams = []; + } + var index = this._localStreams.indexOf(stream); + if (index === -1) { + return; + } + this._localStreams.splice(index, 1); + var tracks = stream.getTracks(); + this.getSenders().forEach(function (sender) { + if (tracks.includes(sender.track)) { + _this3.removeTrack(sender); + } + }); + }; + } + } + + function shimRemoteStreamsAPI(window) { + if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) !== 'object' || !window.RTCPeerConnection) { + return; + } + if (!('getRemoteStreams' in window.RTCPeerConnection.prototype)) { + window.RTCPeerConnection.prototype.getRemoteStreams = function getRemoteStreams() { + return this._remoteStreams ? this._remoteStreams : []; + }; + } + if (!('onaddstream' in window.RTCPeerConnection.prototype)) { + Object.defineProperty(window.RTCPeerConnection.prototype, 'onaddstream', { + get: function get() { + return this._onaddstream; + }, + set: function set(f) { + var _this4 = this; + + if (this._onaddstream) { + this.removeEventListener('addstream', this._onaddstream); + this.removeEventListener('track', this._onaddstreampoly); + } + this.addEventListener('addstream', this._onaddstream = f); + this.addEventListener('track', this._onaddstreampoly = function (e) { + e.streams.forEach(function (stream) { + if (!_this4._remoteStreams) { + _this4._remoteStreams = []; + } + if (_this4._remoteStreams.includes(stream)) { + return; + } + _this4._remoteStreams.push(stream); + var event = new Event('addstream'); + event.stream = stream; + _this4.dispatchEvent(event); + }); + }); + } + }); + var origSetRemoteDescription = window.RTCPeerConnection.prototype.setRemoteDescription; + window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription() { + var pc = this; + if (!this._onaddstreampoly) { + this.addEventListener('track', this._onaddstreampoly = function (e) { + e.streams.forEach(function (stream) { + if (!pc._remoteStreams) { + pc._remoteStreams = []; + } + if (pc._remoteStreams.indexOf(stream) >= 0) { + return; + } + pc._remoteStreams.push(stream); + var event = new Event('addstream'); + event.stream = stream; + pc.dispatchEvent(event); + }); + }); + } + return origSetRemoteDescription.apply(pc, arguments); + }; + } + } + + function shimCallbacksAPI(window) { + if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) !== 'object' || !window.RTCPeerConnection) { + return; + } + var prototype = window.RTCPeerConnection.prototype; + var origCreateOffer = prototype.createOffer; + var origCreateAnswer = prototype.createAnswer; + var setLocalDescription = prototype.setLocalDescription; + var setRemoteDescription = prototype.setRemoteDescription; + var addIceCandidate = prototype.addIceCandidate; + + prototype.createOffer = function createOffer(successCallback, failureCallback) { + var options = arguments.length >= 2 ? arguments[2] : arguments[0]; + var promise = origCreateOffer.apply(this, [options]); + if (!failureCallback) { + return promise; + } + promise.then(successCallback, failureCallback); + return Promise.resolve(); + }; + + prototype.createAnswer = function createAnswer(successCallback, failureCallback) { + var options = arguments.length >= 2 ? arguments[2] : arguments[0]; + var promise = origCreateAnswer.apply(this, [options]); + if (!failureCallback) { + return promise; + } + promise.then(successCallback, failureCallback); + return Promise.resolve(); + }; + + var withCallback = function withCallback(description, successCallback, failureCallback) { + var promise = setLocalDescription.apply(this, [description]); + if (!failureCallback) { + return promise; + } + promise.then(successCallback, failureCallback); + return Promise.resolve(); + }; + prototype.setLocalDescription = withCallback; + + withCallback = function withCallback(description, successCallback, failureCallback) { + var promise = setRemoteDescription.apply(this, [description]); + if (!failureCallback) { + return promise; + } + promise.then(successCallback, failureCallback); + return Promise.resolve(); + }; + prototype.setRemoteDescription = withCallback; + + withCallback = function withCallback(candidate, successCallback, failureCallback) { + var promise = addIceCandidate.apply(this, [candidate]); + if (!failureCallback) { + return promise; + } + promise.then(successCallback, failureCallback); + return Promise.resolve(); + }; + prototype.addIceCandidate = withCallback; + } + + function shimGetUserMedia(window) { + var navigator = window && window.navigator; + + if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { + // shim not needed in Safari 12.1 + var mediaDevices = navigator.mediaDevices; + var _getUserMedia = mediaDevices.getUserMedia.bind(mediaDevices); + navigator.mediaDevices.getUserMedia = function (constraints) { + return _getUserMedia(shimConstraints(constraints)); + }; + } + + if (!navigator.getUserMedia && navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { + navigator.getUserMedia = function getUserMedia(constraints, cb, errcb) { + navigator.mediaDevices.getUserMedia(constraints).then(cb, errcb); + }.bind(navigator); + } + } + + function shimConstraints(constraints) { + if (constraints && constraints.video !== undefined) { + return Object.assign({}, constraints, { video: utils.compactObject(constraints.video) }); + } + + return constraints; + } + + function shimRTCIceServerUrls(window) { + if (!window.RTCPeerConnection) { + return; + } + // migrate from non-spec RTCIceServer.url to RTCIceServer.urls + var OrigPeerConnection = window.RTCPeerConnection; + window.RTCPeerConnection = function RTCPeerConnection(pcConfig, pcConstraints) { + if (pcConfig && pcConfig.iceServers) { + var newIceServers = []; + for (var i = 0; i < pcConfig.iceServers.length; i++) { + var server = pcConfig.iceServers[i]; + if (!server.hasOwnProperty('urls') && server.hasOwnProperty('url')) { + utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls'); + server = JSON.parse(JSON.stringify(server)); + server.urls = server.url; + delete server.url; + newIceServers.push(server); + } else { + newIceServers.push(pcConfig.iceServers[i]); + } + } + pcConfig.iceServers = newIceServers; + } + return new OrigPeerConnection(pcConfig, pcConstraints); + }; + window.RTCPeerConnection.prototype = OrigPeerConnection.prototype; + // wrap static methods. Currently just generateCertificate. + if ('generateCertificate' in OrigPeerConnection) { + Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { + get: function get() { + return OrigPeerConnection.generateCertificate; + } + }); + } + } + + function shimTrackEventTransceiver(window) { + // Add event.transceiver member over deprecated event.receiver + if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCTrackEvent && 'receiver' in window.RTCTrackEvent.prototype && !('transceiver' in window.RTCTrackEvent.prototype)) { + Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', { + get: function get() { + return { receiver: this.receiver }; + } + }); + } + } + + function shimCreateOfferLegacy(window) { + var origCreateOffer = window.RTCPeerConnection.prototype.createOffer; + window.RTCPeerConnection.prototype.createOffer = function createOffer(offerOptions) { + if (offerOptions) { + if (typeof offerOptions.offerToReceiveAudio !== 'undefined') { + // support bit values + offerOptions.offerToReceiveAudio = !!offerOptions.offerToReceiveAudio; + } + var audioTransceiver = this.getTransceivers().find(function (transceiver) { + return transceiver.receiver.track.kind === 'audio'; + }); + if (offerOptions.offerToReceiveAudio === false && audioTransceiver) { + if (audioTransceiver.direction === 'sendrecv') { + if (audioTransceiver.setDirection) { + audioTransceiver.setDirection('sendonly'); + } else { + audioTransceiver.direction = 'sendonly'; + } + } else if (audioTransceiver.direction === 'recvonly') { + if (audioTransceiver.setDirection) { + audioTransceiver.setDirection('inactive'); + } else { + audioTransceiver.direction = 'inactive'; + } + } + } else if (offerOptions.offerToReceiveAudio === true && !audioTransceiver) { + this.addTransceiver('audio'); + } + + if (typeof offerOptions.offerToReceiveVideo !== 'undefined') { + // support bit values + offerOptions.offerToReceiveVideo = !!offerOptions.offerToReceiveVideo; + } + var videoTransceiver = this.getTransceivers().find(function (transceiver) { + return transceiver.receiver.track.kind === 'video'; + }); + if (offerOptions.offerToReceiveVideo === false && videoTransceiver) { + if (videoTransceiver.direction === 'sendrecv') { + if (videoTransceiver.setDirection) { + videoTransceiver.setDirection('sendonly'); + } else { + videoTransceiver.direction = 'sendonly'; + } + } else if (videoTransceiver.direction === 'recvonly') { + if (videoTransceiver.setDirection) { + videoTransceiver.setDirection('inactive'); + } else { + videoTransceiver.direction = 'inactive'; + } + } + } else if (offerOptions.offerToReceiveVideo === true && !videoTransceiver) { + this.addTransceiver('video'); + } + } + return origCreateOffer.apply(this, arguments); + }; + } + + function shimAudioContext(window) { + if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) !== 'object' || window.AudioContext) { + return; + } + window.AudioContext = window.webkitAudioContext; + } + + },{"../utils":11}],11:[function(require,module,exports){ + /* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + + exports.extractVersion = extractVersion; + exports.wrapPeerConnectionEvent = wrapPeerConnectionEvent; + exports.disableLog = disableLog; + exports.disableWarnings = disableWarnings; + exports.log = log; + exports.deprecated = deprecated; + exports.detectBrowser = detectBrowser; + exports.compactObject = compactObject; + exports.walkStats = walkStats; + exports.filterStats = filterStats; + + function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + + var logDisabled_ = true; + var deprecationWarnings_ = true; + + /** + * Extract browser version out of the provided user agent string. + * + * @param {!string} uastring userAgent string. + * @param {!string} expr Regular expression used as match criteria. + * @param {!number} pos position in the version string to be returned. + * @return {!number} browser version. + */ + function extractVersion(uastring, expr, pos) { + var match = uastring.match(expr); + return match && match.length >= pos && parseInt(match[pos], 10); + } + +// Wraps the peerconnection event eventNameToWrap in a function +// which returns the modified event object (or false to prevent +// the event). + function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) { + if (!window.RTCPeerConnection) { + return; + } + var proto = window.RTCPeerConnection.prototype; + var nativeAddEventListener = proto.addEventListener; + proto.addEventListener = function (nativeEventName, cb) { + if (nativeEventName !== eventNameToWrap) { + return nativeAddEventListener.apply(this, arguments); + } + var wrappedCallback = function wrappedCallback(e) { + var modifiedEvent = wrapper(e); + if (modifiedEvent) { + if (cb.handleEvent) { + cb.handleEvent(modifiedEvent); + } else { + cb(modifiedEvent); + } + } + }; + this._eventMap = this._eventMap || {}; + if (!this._eventMap[eventNameToWrap]) { + this._eventMap[eventNameToWrap] = new Map(); + } + this._eventMap[eventNameToWrap].set(cb, wrappedCallback); + return nativeAddEventListener.apply(this, [nativeEventName, wrappedCallback]); + }; + + var nativeRemoveEventListener = proto.removeEventListener; + proto.removeEventListener = function (nativeEventName, cb) { + if (nativeEventName !== eventNameToWrap || !this._eventMap || !this._eventMap[eventNameToWrap]) { + return nativeRemoveEventListener.apply(this, arguments); + } + if (!this._eventMap[eventNameToWrap].has(cb)) { + return nativeRemoveEventListener.apply(this, arguments); + } + var unwrappedCb = this._eventMap[eventNameToWrap].get(cb); + this._eventMap[eventNameToWrap].delete(cb); + if (this._eventMap[eventNameToWrap].size === 0) { + delete this._eventMap[eventNameToWrap]; + } + if (Object.keys(this._eventMap).length === 0) { + delete this._eventMap; + } + return nativeRemoveEventListener.apply(this, [nativeEventName, unwrappedCb]); + }; + + Object.defineProperty(proto, 'on' + eventNameToWrap, { + get: function get() { + return this['_on' + eventNameToWrap]; + }, + set: function set(cb) { + if (this['_on' + eventNameToWrap]) { + this.removeEventListener(eventNameToWrap, this['_on' + eventNameToWrap]); + delete this['_on' + eventNameToWrap]; + } + if (cb) { + this.addEventListener(eventNameToWrap, this['_on' + eventNameToWrap] = cb); + } + }, + + enumerable: true, + configurable: true + }); + } + + function disableLog(bool) { + if (typeof bool !== 'boolean') { + return new Error('Argument type: ' + (typeof bool === 'undefined' ? 'undefined' : _typeof(bool)) + '. Please use a boolean.'); + } + logDisabled_ = bool; + return bool ? 'adapter.js logging disabled' : 'adapter.js logging enabled'; + } + + /** + * Disable or enable deprecation warnings + * @param {!boolean} bool set to true to disable warnings. + */ + function disableWarnings(bool) { + if (typeof bool !== 'boolean') { + return new Error('Argument type: ' + (typeof bool === 'undefined' ? 'undefined' : _typeof(bool)) + '. Please use a boolean.'); + } + deprecationWarnings_ = !bool; + return 'adapter.js deprecation warnings ' + (bool ? 'disabled' : 'enabled'); + } + + function log() { + if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object') { + if (logDisabled_) { + return; + } + if (typeof console !== 'undefined' && typeof console.log === 'function') { + console.log.apply(console, arguments); + } + } + } + + /** + * Shows a deprecation warning suggesting the modern and spec-compatible API. + */ + function deprecated(oldMethod, newMethod) { + if (!deprecationWarnings_) { + return; + } + console.warn(oldMethod + ' is deprecated, please use ' + newMethod + ' instead.'); + } + + /** + * Browser detector. + * + * @return {object} result containing browser and version + * properties. + */ + function detectBrowser(window) { + // Returned result object. + var result = { browser: null, version: null }; + + // Fail early if it's not a browser + if (typeof window === 'undefined' || !window.navigator) { + result.browser = 'Not a browser.'; + return result; + } + + var navigator = window.navigator; + + + if (navigator.mozGetUserMedia) { + // Firefox. + result.browser = 'firefox'; + result.version = extractVersion(navigator.userAgent, /Firefox\/(\d+)\./, 1); + } else if (navigator.webkitGetUserMedia || window.isSecureContext === false && window.webkitRTCPeerConnection && !window.RTCIceGatherer) { + // Chrome, Chromium, Webview, Opera. + // Version matches Chrome/WebRTC version. + // Chrome 74 removed webkitGetUserMedia on http as well so we need the + // more complicated fallback to webkitRTCPeerConnection. + result.browser = 'chrome'; + result.version = extractVersion(navigator.userAgent, /Chrom(e|ium)\/(\d+)\./, 2); + } else if (window.RTCPeerConnection && navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) { + // Safari. + result.browser = 'safari'; + result.version = extractVersion(navigator.userAgent, /AppleWebKit\/(\d+)\./, 1); + result.supportsUnifiedPlan = window.RTCRtpTransceiver && 'currentDirection' in window.RTCRtpTransceiver.prototype; + } else { + // Default fallthrough: not supported. + result.browser = 'Not a supported browser.'; + return result; + } + + return result; + } + + /** + * Checks if something is an object. + * + * @param {*} val The something you want to check. + * @return true if val is an object, false otherwise. + */ + function isObject(val) { + return Object.prototype.toString.call(val) === '[object Object]'; + } + + /** + * Remove all empty objects and undefined values + * from a nested object -- an enhanced and vanilla version + * of Lodash's `compact`. + */ + function compactObject(data) { + if (!isObject(data)) { + return data; + } + + return Object.keys(data).reduce(function (accumulator, key) { + var isObj = isObject(data[key]); + var value = isObj ? compactObject(data[key]) : data[key]; + var isEmptyObject = isObj && !Object.keys(value).length; + if (value === undefined || isEmptyObject) { + return accumulator; + } + return Object.assign(accumulator, _defineProperty({}, key, value)); + }, {}); + } + + /* iterates the stats graph recursively. */ + function walkStats(stats, base, resultSet) { + if (!base || resultSet.has(base.id)) { + return; + } + resultSet.set(base.id, base); + Object.keys(base).forEach(function (name) { + if (name.endsWith('Id')) { + walkStats(stats, stats.get(base[name]), resultSet); + } else if (name.endsWith('Ids')) { + base[name].forEach(function (id) { + walkStats(stats, stats.get(id), resultSet); + }); + } + }); + } + + /* filter getStats for a sender/receiver track. */ + function filterStats(result, track, outbound) { + var streamStatsType = outbound ? 'outbound-rtp' : 'inbound-rtp'; + var filteredResult = new Map(); + if (track === null) { + return filteredResult; + } + var trackStats = []; + result.forEach(function (value) { + if (value.type === 'track' && value.trackIdentifier === track.id) { + trackStats.push(value); + } + }); + trackStats.forEach(function (trackStat) { + result.forEach(function (stats) { + if (stats.type === streamStatsType && stats.trackId === trackStat.id) { + walkStats(result, stats, filteredResult); + } + }); + }); + return filteredResult; + } + + },{}],12:[function(require,module,exports){ + /* eslint-env node */ + 'use strict'; + +// SDP helpers. + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + + var SDPUtils = {}; + +// Generate an alphanumeric identifier for cname or mids. +// TODO: use UUIDs instead? https://gist.github.com/jed/982883 + SDPUtils.generateIdentifier = function () { + return Math.random().toString(36).substr(2, 10); + }; + +// The RTCP CNAME used by all peerconnections from the same JS. + SDPUtils.localCName = SDPUtils.generateIdentifier(); + +// Splits SDP into lines, dealing with both CRLF and LF. + SDPUtils.splitLines = function (blob) { + return blob.trim().split('\n').map(function (line) { + return line.trim(); + }); + }; +// Splits SDP into sessionpart and mediasections. Ensures CRLF. + SDPUtils.splitSections = function (blob) { + var parts = blob.split('\nm='); + return parts.map(function (part, index) { + return (index > 0 ? 'm=' + part : part).trim() + '\r\n'; + }); + }; + +// returns the session description. + SDPUtils.getDescription = function (blob) { + var sections = SDPUtils.splitSections(blob); + return sections && sections[0]; + }; + +// returns the individual media sections. + SDPUtils.getMediaSections = function (blob) { + var sections = SDPUtils.splitSections(blob); + sections.shift(); + return sections; + }; + +// Returns lines that start with a certain prefix. + SDPUtils.matchPrefix = function (blob, prefix) { + return SDPUtils.splitLines(blob).filter(function (line) { + return line.indexOf(prefix) === 0; + }); + }; + +// Parses an ICE candidate line. Sample input: +// candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8 +// rport 55996" + SDPUtils.parseCandidate = function (line) { + var parts = void 0; + // Parse both variants. + if (line.indexOf('a=candidate:') === 0) { + parts = line.substring(12).split(' '); + } else { + parts = line.substring(10).split(' '); + } + + var candidate = { + foundation: parts[0], + component: { 1: 'rtp', 2: 'rtcp' }[parts[1]], + protocol: parts[2].toLowerCase(), + priority: parseInt(parts[3], 10), + ip: parts[4], + address: parts[4], // address is an alias for ip. + port: parseInt(parts[5], 10), + // skip parts[6] == 'typ' + type: parts[7] + }; + + for (var i = 8; i < parts.length; i += 2) { + switch (parts[i]) { + case 'raddr': + candidate.relatedAddress = parts[i + 1]; + break; + case 'rport': + candidate.relatedPort = parseInt(parts[i + 1], 10); + break; + case 'tcptype': + candidate.tcpType = parts[i + 1]; + break; + case 'ufrag': + candidate.ufrag = parts[i + 1]; // for backward compatibility. + candidate.usernameFragment = parts[i + 1]; + break; + default: + // extension handling, in particular ufrag. Don't overwrite. + if (candidate[parts[i]] === undefined) { + candidate[parts[i]] = parts[i + 1]; + } + break; + } + } + return candidate; + }; + +// Translates a candidate object into SDP candidate attribute. + SDPUtils.writeCandidate = function (candidate) { + var sdp = []; + sdp.push(candidate.foundation); + + var component = candidate.component; + if (component === 'rtp') { + sdp.push(1); + } else if (component === 'rtcp') { + sdp.push(2); + } else { + sdp.push(component); + } + sdp.push(candidate.protocol.toUpperCase()); + sdp.push(candidate.priority); + sdp.push(candidate.address || candidate.ip); + sdp.push(candidate.port); + + var type = candidate.type; + sdp.push('typ'); + sdp.push(type); + if (type !== 'host' && candidate.relatedAddress && candidate.relatedPort) { + sdp.push('raddr'); + sdp.push(candidate.relatedAddress); + sdp.push('rport'); + sdp.push(candidate.relatedPort); + } + if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') { + sdp.push('tcptype'); + sdp.push(candidate.tcpType); + } + if (candidate.usernameFragment || candidate.ufrag) { + sdp.push('ufrag'); + sdp.push(candidate.usernameFragment || candidate.ufrag); + } + return 'candidate:' + sdp.join(' '); + }; + +// Parses an ice-options line, returns an array of option tags. +// a=ice-options:foo bar + SDPUtils.parseIceOptions = function (line) { + return line.substr(14).split(' '); + }; + +// Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input: +// a=rtpmap:111 opus/48000/2 + SDPUtils.parseRtpMap = function (line) { + var parts = line.substr(9).split(' '); + var parsed = { + payloadType: parseInt(parts.shift(), 10) // was: id + }; + + parts = parts[0].split('/'); + + parsed.name = parts[0]; + parsed.clockRate = parseInt(parts[1], 10); // was: clockrate + parsed.channels = parts.length === 3 ? parseInt(parts[2], 10) : 1; + // legacy alias, got renamed back to channels in ORTC. + parsed.numChannels = parsed.channels; + return parsed; + }; + +// Generate an a=rtpmap line from RTCRtpCodecCapability or +// RTCRtpCodecParameters. + SDPUtils.writeRtpMap = function (codec) { + var pt = codec.payloadType; + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + var channels = codec.channels || codec.numChannels || 1; + return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate + (channels !== 1 ? '/' + channels : '') + '\r\n'; + }; + +// Parses an a=extmap line (headerextension from RFC 5285). Sample input: +// a=extmap:2 urn:ietf:params:rtp-hdrext:toffset +// a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset + SDPUtils.parseExtmap = function (line) { + var parts = line.substr(9).split(' '); + return { + id: parseInt(parts[0], 10), + direction: parts[0].indexOf('/') > 0 ? parts[0].split('/')[1] : 'sendrecv', + uri: parts[1] + }; + }; + +// Generates a=extmap line from RTCRtpHeaderExtensionParameters or +// RTCRtpHeaderExtension. + SDPUtils.writeExtmap = function (headerExtension) { + return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) + (headerExtension.direction && headerExtension.direction !== 'sendrecv' ? '/' + headerExtension.direction : '') + ' ' + headerExtension.uri + '\r\n'; + }; + +// Parses an ftmp line, returns dictionary. Sample input: +// a=fmtp:96 vbr=on;cng=on +// Also deals with vbr=on; cng=on + SDPUtils.parseFmtp = function (line) { + var parsed = {}; + var kv = void 0; + var parts = line.substr(line.indexOf(' ') + 1).split(';'); + for (var j = 0; j < parts.length; j++) { + kv = parts[j].trim().split('='); + parsed[kv[0].trim()] = kv[1]; + } + return parsed; + }; + +// Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters. + SDPUtils.writeFmtp = function (codec) { + var line = ''; + var pt = codec.payloadType; + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + if (codec.parameters && Object.keys(codec.parameters).length) { + var params = []; + Object.keys(codec.parameters).forEach(function (param) { + if (codec.parameters[param]) { + params.push(param + '=' + codec.parameters[param]); + } else { + params.push(param); + } + }); + line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n'; + } + return line; + }; + +// Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input: +// a=rtcp-fb:98 nack rpsi + SDPUtils.parseRtcpFb = function (line) { + var parts = line.substr(line.indexOf(' ') + 1).split(' '); + return { + type: parts.shift(), + parameter: parts.join(' ') + }; + }; +// Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters. + SDPUtils.writeRtcpFb = function (codec) { + var lines = ''; + var pt = codec.payloadType; + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + if (codec.rtcpFeedback && codec.rtcpFeedback.length) { + // FIXME: special handling for trr-int? + codec.rtcpFeedback.forEach(function (fb) { + lines += 'a=rtcp-fb:' + pt + ' ' + fb.type + (fb.parameter && fb.parameter.length ? ' ' + fb.parameter : '') + '\r\n'; + }); + } + return lines; + }; + +// Parses an RFC 5576 ssrc media attribute. Sample input: +// a=ssrc:3735928559 cname:something + SDPUtils.parseSsrcMedia = function (line) { + var sp = line.indexOf(' '); + var parts = { + ssrc: parseInt(line.substr(7, sp - 7), 10) + }; + var colon = line.indexOf(':', sp); + if (colon > -1) { + parts.attribute = line.substr(sp + 1, colon - sp - 1); + parts.value = line.substr(colon + 1); + } else { + parts.attribute = line.substr(sp + 1); + } + return parts; + }; + + SDPUtils.parseSsrcGroup = function (line) { + var parts = line.substr(13).split(' '); + return { + semantics: parts.shift(), + ssrcs: parts.map(function (ssrc) { + return parseInt(ssrc, 10); + }) + }; + }; + +// Extracts the MID (RFC 5888) from a media section. +// returns the MID or undefined if no mid line was found. + SDPUtils.getMid = function (mediaSection) { + var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0]; + if (mid) { + return mid.substr(6); + } + }; + + SDPUtils.parseFingerprint = function (line) { + var parts = line.substr(14).split(' '); + return { + algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge. + value: parts[1] + }; + }; + +// Extracts DTLS parameters from SDP media section or sessionpart. +// FIXME: for consistency with other functions this should only +// get the fingerprint line as input. See also getIceParameters. + SDPUtils.getDtlsParameters = function (mediaSection, sessionpart) { + var lines = SDPUtils.matchPrefix(mediaSection + sessionpart, 'a=fingerprint:'); + // Note: a=setup line is ignored since we use the 'auto' role. + // Note2: 'algorithm' is not case sensitive except in Edge. + return { + role: 'auto', + fingerprints: lines.map(SDPUtils.parseFingerprint) + }; + }; + +// Serializes DTLS parameters to SDP. + SDPUtils.writeDtlsParameters = function (params, setupType) { + var sdp = 'a=setup:' + setupType + '\r\n'; + params.fingerprints.forEach(function (fp) { + sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n'; + }); + return sdp; + }; + +// Parses a=crypto lines into +// https://rawgit.com/aboba/edgertc/master/msortc-rs4.html#dictionary-rtcsrtpsdesparameters-members + SDPUtils.parseCryptoLine = function (line) { + var parts = line.substr(9).split(' '); + return { + tag: parseInt(parts[0], 10), + cryptoSuite: parts[1], + keyParams: parts[2], + sessionParams: parts.slice(3) + }; + }; + + SDPUtils.writeCryptoLine = function (parameters) { + return 'a=crypto:' + parameters.tag + ' ' + parameters.cryptoSuite + ' ' + (_typeof(parameters.keyParams) === 'object' ? SDPUtils.writeCryptoKeyParams(parameters.keyParams) : parameters.keyParams) + (parameters.sessionParams ? ' ' + parameters.sessionParams.join(' ') : '') + '\r\n'; + }; + +// Parses the crypto key parameters into +// https://rawgit.com/aboba/edgertc/master/msortc-rs4.html#rtcsrtpkeyparam* + SDPUtils.parseCryptoKeyParams = function (keyParams) { + if (keyParams.indexOf('inline:') !== 0) { + return null; + } + var parts = keyParams.substr(7).split('|'); + return { + keyMethod: 'inline', + keySalt: parts[0], + lifeTime: parts[1], + mkiValue: parts[2] ? parts[2].split(':')[0] : undefined, + mkiLength: parts[2] ? parts[2].split(':')[1] : undefined + }; + }; + + SDPUtils.writeCryptoKeyParams = function (keyParams) { + return keyParams.keyMethod + ':' + keyParams.keySalt + (keyParams.lifeTime ? '|' + keyParams.lifeTime : '') + (keyParams.mkiValue && keyParams.mkiLength ? '|' + keyParams.mkiValue + ':' + keyParams.mkiLength : ''); + }; + +// Extracts all SDES parameters. + SDPUtils.getCryptoParameters = function (mediaSection, sessionpart) { + var lines = SDPUtils.matchPrefix(mediaSection + sessionpart, 'a=crypto:'); + return lines.map(SDPUtils.parseCryptoLine); + }; + +// Parses ICE information from SDP media section or sessionpart. +// FIXME: for consistency with other functions this should only +// get the ice-ufrag and ice-pwd lines as input. + SDPUtils.getIceParameters = function (mediaSection, sessionpart) { + var ufrag = SDPUtils.matchPrefix(mediaSection + sessionpart, 'a=ice-ufrag:')[0]; + var pwd = SDPUtils.matchPrefix(mediaSection + sessionpart, 'a=ice-pwd:')[0]; + if (!(ufrag && pwd)) { + return null; + } + return { + usernameFragment: ufrag.substr(12), + password: pwd.substr(10) + }; + }; + +// Serializes ICE parameters to SDP. + SDPUtils.writeIceParameters = function (params) { + var sdp = 'a=ice-ufrag:' + params.usernameFragment + '\r\n' + 'a=ice-pwd:' + params.password + '\r\n'; + if (params.iceLite) { + sdp += 'a=ice-lite\r\n'; + } + return sdp; + }; + +// Parses the SDP media section and returns RTCRtpParameters. + SDPUtils.parseRtpParameters = function (mediaSection) { + var description = { + codecs: [], + headerExtensions: [], + fecMechanisms: [], + rtcp: [] + }; + var lines = SDPUtils.splitLines(mediaSection); + var mline = lines[0].split(' '); + for (var i = 3; i < mline.length; i++) { + // find all codecs from mline[3..] + var pt = mline[i]; + var rtpmapline = SDPUtils.matchPrefix(mediaSection, 'a=rtpmap:' + pt + ' ')[0]; + if (rtpmapline) { + var codec = SDPUtils.parseRtpMap(rtpmapline); + var fmtps = SDPUtils.matchPrefix(mediaSection, 'a=fmtp:' + pt + ' '); + // Only the first a=fmtp: is considered. + codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {}; + codec.rtcpFeedback = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-fb:' + pt + ' ').map(SDPUtils.parseRtcpFb); + description.codecs.push(codec); + // parse FEC mechanisms from rtpmap lines. + switch (codec.name.toUpperCase()) { + case 'RED': + case 'ULPFEC': + description.fecMechanisms.push(codec.name.toUpperCase()); + break; + default: + // only RED and ULPFEC are recognized as FEC mechanisms. + break; + } + } + } + SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(function (line) { + description.headerExtensions.push(SDPUtils.parseExtmap(line)); + }); + // FIXME: parse rtcp. + return description; + }; + +// Generates parts of the SDP media section describing the capabilities / +// parameters. + SDPUtils.writeRtpDescription = function (kind, caps) { + var sdp = ''; + + // Build the mline. + sdp += 'm=' + kind + ' '; + sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs. + sdp += ' UDP/TLS/RTP/SAVPF '; + sdp += caps.codecs.map(function (codec) { + if (codec.preferredPayloadType !== undefined) { + return codec.preferredPayloadType; + } + return codec.payloadType; + }).join(' ') + '\r\n'; + + sdp += 'c=IN IP4 0.0.0.0\r\n'; + sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n'; + + // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb. + caps.codecs.forEach(function (codec) { + sdp += SDPUtils.writeRtpMap(codec); + sdp += SDPUtils.writeFmtp(codec); + sdp += SDPUtils.writeRtcpFb(codec); + }); + var maxptime = 0; + caps.codecs.forEach(function (codec) { + if (codec.maxptime > maxptime) { + maxptime = codec.maxptime; + } + }); + if (maxptime > 0) { + sdp += 'a=maxptime:' + maxptime + '\r\n'; + } + + if (caps.headerExtensions) { + caps.headerExtensions.forEach(function (extension) { + sdp += SDPUtils.writeExtmap(extension); + }); + } + // FIXME: write fecMechanisms. + return sdp; + }; + +// Parses the SDP media section and returns an array of +// RTCRtpEncodingParameters. + SDPUtils.parseRtpEncodingParameters = function (mediaSection) { + var encodingParameters = []; + var description = SDPUtils.parseRtpParameters(mediaSection); + var hasRed = description.fecMechanisms.indexOf('RED') !== -1; + var hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1; + + // filter a=ssrc:... cname:, ignore PlanB-msid + var ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:').map(function (line) { + return SDPUtils.parseSsrcMedia(line); + }).filter(function (parts) { + return parts.attribute === 'cname'; + }); + var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc; + var secondarySsrc = void 0; + + var flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID').map(function (line) { + var parts = line.substr(17).split(' '); + return parts.map(function (part) { + return parseInt(part, 10); + }); + }); + if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) { + secondarySsrc = flows[0][1]; + } + + description.codecs.forEach(function (codec) { + if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) { + var encParam = { + ssrc: primarySsrc, + codecPayloadType: parseInt(codec.parameters.apt, 10) + }; + if (primarySsrc && secondarySsrc) { + encParam.rtx = { ssrc: secondarySsrc }; + } + encodingParameters.push(encParam); + if (hasRed) { + encParam = JSON.parse(JSON.stringify(encParam)); + encParam.fec = { + ssrc: primarySsrc, + mechanism: hasUlpfec ? 'red+ulpfec' : 'red' + }; + encodingParameters.push(encParam); + } + } + }); + if (encodingParameters.length === 0 && primarySsrc) { + encodingParameters.push({ + ssrc: primarySsrc + }); + } + + // we support both b=AS and b=TIAS but interpret AS as TIAS. + var bandwidth = SDPUtils.matchPrefix(mediaSection, 'b='); + if (bandwidth.length) { + if (bandwidth[0].indexOf('b=TIAS:') === 0) { + bandwidth = parseInt(bandwidth[0].substr(7), 10); + } else if (bandwidth[0].indexOf('b=AS:') === 0) { + // use formula from JSEP to convert b=AS to TIAS value. + bandwidth = parseInt(bandwidth[0].substr(5), 10) * 1000 * 0.95 - 50 * 40 * 8; + } else { + bandwidth = undefined; + } + encodingParameters.forEach(function (params) { + params.maxBitrate = bandwidth; + }); + } + return encodingParameters; + }; + +// parses http://draft.ortc.org/#rtcrtcpparameters* + SDPUtils.parseRtcpParameters = function (mediaSection) { + var rtcpParameters = {}; + + // Gets the first SSRC. Note that with RTX there might be multiple + // SSRCs. + var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:').map(function (line) { + return SDPUtils.parseSsrcMedia(line); + }).filter(function (obj) { + return obj.attribute === 'cname'; + })[0]; + if (remoteSsrc) { + rtcpParameters.cname = remoteSsrc.value; + rtcpParameters.ssrc = remoteSsrc.ssrc; + } + + // Edge uses the compound attribute instead of reducedSize + // compound is !reducedSize + var rsize = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-rsize'); + rtcpParameters.reducedSize = rsize.length > 0; + rtcpParameters.compound = rsize.length === 0; + + // parses the rtcp-mux attrіbute. + // Note that Edge does not support unmuxed RTCP. + var mux = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-mux'); + rtcpParameters.mux = mux.length > 0; + + return rtcpParameters; + }; + + SDPUtils.writeRtcpParameters = function (rtcpParameters) { + var sdp = ''; + if (rtcpParameters.reducedSize) { + sdp += 'a=rtcp-rsize\r\n'; + } + if (rtcpParameters.mux) { + sdp += 'a=rtcp-mux\r\n'; + } + if (rtcpParameters.ssrc !== undefined && rtcpParameters.cname) { + sdp += 'a=ssrc:' + rtcpParameters.ssrc + ' cname:' + rtcpParameters.cname + '\r\n'; + } + return sdp; + }; + +// parses either a=msid: or a=ssrc:... msid lines and returns +// the id of the MediaStream and MediaStreamTrack. + SDPUtils.parseMsid = function (mediaSection) { + var parts = void 0; + var spec = SDPUtils.matchPrefix(mediaSection, 'a=msid:'); + if (spec.length === 1) { + parts = spec[0].substr(7).split(' '); + return { stream: parts[0], track: parts[1] }; + } + var planB = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:').map(function (line) { + return SDPUtils.parseSsrcMedia(line); + }).filter(function (msidParts) { + return msidParts.attribute === 'msid'; + }); + if (planB.length > 0) { + parts = planB[0].value.split(' '); + return { stream: parts[0], track: parts[1] }; + } + }; + +// SCTP +// parses draft-ietf-mmusic-sctp-sdp-26 first and falls back +// to draft-ietf-mmusic-sctp-sdp-05 + SDPUtils.parseSctpDescription = function (mediaSection) { + var mline = SDPUtils.parseMLine(mediaSection); + var maxSizeLine = SDPUtils.matchPrefix(mediaSection, 'a=max-message-size:'); + var maxMessageSize = void 0; + if (maxSizeLine.length > 0) { + maxMessageSize = parseInt(maxSizeLine[0].substr(19), 10); + } + if (isNaN(maxMessageSize)) { + maxMessageSize = 65536; + } + var sctpPort = SDPUtils.matchPrefix(mediaSection, 'a=sctp-port:'); + if (sctpPort.length > 0) { + return { + port: parseInt(sctpPort[0].substr(12), 10), + protocol: mline.fmt, + maxMessageSize: maxMessageSize + }; + } + var sctpMapLines = SDPUtils.matchPrefix(mediaSection, 'a=sctpmap:'); + if (sctpMapLines.length > 0) { + var parts = sctpMapLines[0].substr(10).split(' '); + return { + port: parseInt(parts[0], 10), + protocol: parts[1], + maxMessageSize: maxMessageSize + }; + } + }; + +// SCTP +// outputs the draft-ietf-mmusic-sctp-sdp-26 version that all browsers +// support by now receiving in this format, unless we originally parsed +// as the draft-ietf-mmusic-sctp-sdp-05 format (indicated by the m-line +// protocol of DTLS/SCTP -- without UDP/ or TCP/) + SDPUtils.writeSctpDescription = function (media, sctp) { + var output = []; + if (media.protocol !== 'DTLS/SCTP') { + output = ['m=' + media.kind + ' 9 ' + media.protocol + ' ' + sctp.protocol + '\r\n', 'c=IN IP4 0.0.0.0\r\n', 'a=sctp-port:' + sctp.port + '\r\n']; + } else { + output = ['m=' + media.kind + ' 9 ' + media.protocol + ' ' + sctp.port + '\r\n', 'c=IN IP4 0.0.0.0\r\n', 'a=sctpmap:' + sctp.port + ' ' + sctp.protocol + ' 65535\r\n']; + } + if (sctp.maxMessageSize !== undefined) { + output.push('a=max-message-size:' + sctp.maxMessageSize + '\r\n'); + } + return output.join(''); + }; + +// Generate a session ID for SDP. +// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-20#section-5.2.1 +// recommends using a cryptographically random +ve 64-bit value +// but right now this should be acceptable and within the right range + SDPUtils.generateSessionId = function () { + return Math.random().toString().substr(2, 21); + }; + +// Write boiler plate for start of SDP +// sessId argument is optional - if not supplied it will +// be generated randomly +// sessVersion is optional and defaults to 2 +// sessUser is optional and defaults to 'thisisadapterortc' + SDPUtils.writeSessionBoilerplate = function (sessId, sessVer, sessUser) { + var sessionId = void 0; + var version = sessVer !== undefined ? sessVer : 2; + if (sessId) { + sessionId = sessId; + } else { + sessionId = SDPUtils.generateSessionId(); + } + var user = sessUser || 'thisisadapterortc'; + // FIXME: sess-id should be an NTP timestamp. + return 'v=0\r\n' + 'o=' + user + ' ' + sessionId + ' ' + version + ' IN IP4 127.0.0.1\r\n' + 's=-\r\n' + 't=0 0\r\n'; + }; + +// Gets the direction from the mediaSection or the sessionpart. + SDPUtils.getDirection = function (mediaSection, sessionpart) { + // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv. + var lines = SDPUtils.splitLines(mediaSection); + for (var i = 0; i < lines.length; i++) { + switch (lines[i]) { + case 'a=sendrecv': + case 'a=sendonly': + case 'a=recvonly': + case 'a=inactive': + return lines[i].substr(2); + default: + // FIXME: What should happen here? + } + } + if (sessionpart) { + return SDPUtils.getDirection(sessionpart); + } + return 'sendrecv'; + }; + + SDPUtils.getKind = function (mediaSection) { + var lines = SDPUtils.splitLines(mediaSection); + var mline = lines[0].split(' '); + return mline[0].substr(2); + }; + + SDPUtils.isRejected = function (mediaSection) { + return mediaSection.split(' ', 2)[1] === '0'; + }; + + SDPUtils.parseMLine = function (mediaSection) { + var lines = SDPUtils.splitLines(mediaSection); + var parts = lines[0].substr(2).split(' '); + return { + kind: parts[0], + port: parseInt(parts[1], 10), + protocol: parts[2], + fmt: parts.slice(3).join(' ') + }; + }; + + SDPUtils.parseOLine = function (mediaSection) { + var line = SDPUtils.matchPrefix(mediaSection, 'o=')[0]; + var parts = line.substr(2).split(' '); + return { + username: parts[0], + sessionId: parts[1], + sessionVersion: parseInt(parts[2], 10), + netType: parts[3], + addressType: parts[4], + address: parts[5] + }; + }; + +// a very naive interpretation of a valid SDP. + SDPUtils.isValidSDP = function (blob) { + if (typeof blob !== 'string' || blob.length === 0) { + return false; + } + var lines = SDPUtils.splitLines(blob); + for (var i = 0; i < lines.length; i++) { + if (lines[i].length < 2 || lines[i].charAt(1) !== '=') { + return false; + } + // TODO: check the modifier a bit more. + } + return true; + }; + +// Expose public methods. + if ((typeof module === 'undefined' ? 'undefined' : _typeof(module)) === 'object') { + module.exports = SDPUtils; + } + },{}]},{},[1])(1) +}); \ No newline at end of file diff --git a/Runtime/Plugins/WebGL/adapter.jspre.meta b/Runtime/Plugins/WebGL/adapter.jspre.meta new file mode 100644 index 0000000000..fe0f01103f --- /dev/null +++ b/Runtime/Plugins/WebGL/adapter.jspre.meta @@ -0,0 +1,76 @@ +fileFormatVersion: 2 +guid: d661aa39cbfa723409a4ba8a7aca10b9 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 1 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 0 + Exclude Win: 1 + Exclude Win64: 1 + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: x86 + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: x86_64 + - first: + WebGL: WebGL + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/AudioStreamTrack.cs b/Runtime/Scripts/AudioStreamTrack.cs index ca7d8ae077..77c5fca3b6 100644 --- a/Runtime/Scripts/AudioStreamTrack.cs +++ b/Runtime/Scripts/AudioStreamTrack.cs @@ -7,10 +7,22 @@ namespace Unity.WebRTC { /// - /// This event is called on audio thread. + /// Delegate to be called when new audio data is received. /// - /// - /// + /// + /// `AudioReadEventHandler` is a delegate to be called when new audio data is received. + /// + /// Float array containing audio data samples. + /// Number of audio channels. + /// Sample rate of the audio data + /// + /// + /// { + /// } + /// ]]> + /// + /// public delegate void AudioReadEventHandler(float[] data, int channels, int sampleRate); /// @@ -30,12 +42,22 @@ public static void SetTrack(this AudioSource source, AudioStreamTrack track) } /// - /// + /// Represents a single audio track within a stream. /// + /// + /// `AudioStreamTrack` is a `MediaStreamTrack` that represents a single audio track within a stream. + /// + /// + /// + /// + /// + /// public class AudioStreamTrack : MediaStreamTrack { /// - /// + /// AudioSource object. /// public AudioSource Source { @@ -182,17 +204,33 @@ internal void SetData(float[] data, int channels, int sampleRate) internal AudioTrackSource _trackSource; /// - /// + /// Creates a new AudioStreamTrack object. /// + /// + /// `AudioStreamTrack` constructor creates an instance of `AudioStreamTrack`. + /// + /// + /// + /// public AudioStreamTrack() : this(Guid.NewGuid().ToString(), new AudioTrackSource()) { } /// - /// + /// Creates a new AudioStreamTrack object. /// - /// + /// + /// `AudioStreamTrack` constructor creates an instance of `AudioStreamTrack` with a `source`. + /// + /// `AudioSource` object. + /// + /// + /// public AudioStreamTrack(AudioSource source) : this(Guid.NewGuid().ToString(), new AudioTrackSource()) { @@ -206,6 +244,18 @@ public AudioStreamTrack(AudioSource source) _audioCapturer.sender = true; } + /// + /// Creates a new AudioStreamTrack object. + /// + /// + /// `AudioStreamTrack` constructor creates an instance of `AudioStreamTrack` with a `listener`. + /// + /// `AudioListener` object. + /// + /// + /// public AudioStreamTrack(AudioListener listener) : this(Guid.NewGuid().ToString(), new AudioTrackSource()) { @@ -241,8 +291,16 @@ internal void RemoveSink(AudioStreamRenderer renderer) } /// - /// + /// Disposes of AudioStreamTrack. /// + /// + /// `Dispose` method disposes of the `AudioStreamTrack` and releases the associated resources. + /// + /// + /// + /// public override void Dispose() { if (this.disposed) @@ -270,11 +328,19 @@ public override void Dispose() } /// - /// + /// Provides the audio data to the track. /// - /// - /// - /// + /// + /// `SetData` method provides the audio data to the track. + /// + /// `NativeArray` containing audio data samples. + /// Number of audio channels. + /// Sample rate of the audio data + /// + /// + /// public void SetData(NativeArray.ReadOnly nativeArray, int channels, int sampleRate) { unsafe @@ -285,11 +351,19 @@ public void SetData(NativeArray.ReadOnly nativeArray, int channels, int s } /// - /// + /// Provides the audio data to the track. /// - /// - /// - /// + /// + /// `SetData` method provides the audio data to the track. + /// + /// `NativeSlice` containing audio data samples. + /// Number of audio channels. + /// Sample rate of the audio data + /// + /// + /// public void SetData(NativeSlice nativeSlice, int channels, int sampleRate) { unsafe @@ -310,11 +384,19 @@ static void ProcessAudio(AudioTrackSource source, IntPtr array, int sampleRate, } /// - /// + /// Provides the audio data to the track. /// - /// - /// - /// + /// + /// `SetData` method provides the audio data to the track. + /// + /// Float array containing audio data samples. + /// Number of audio channels. + /// Sample rate of the audio data + /// + /// + /// public void SetData(float[] array, int channels, int sampleRate) { if (array == null) @@ -332,11 +414,19 @@ public void SetData(float[] array, int channels, int sampleRate) // ReadOnlySpan is supported since .NET Standard 2.1. #if UNITY_2021_2_OR_NEWER /// - /// + /// Provides the audio data to the track. /// - /// - /// - /// + /// + /// `SetData` method provides the audio data to the track. + /// + /// `ReadOnlySpan` containing audio data samples. + /// Number of audio channels. + /// Sample rate of the audio data + /// + /// + /// public void SetData(ReadOnlySpan span, int channels, int sampleRate) { unsafe @@ -350,8 +440,19 @@ public void SetData(ReadOnlySpan span, int channels, int sampleRate) #endif /// - /// + /// Event to be fired when new audio data is received. /// + /// + /// `onReceived` event is fired when new audio data is received. + /// + /// + /// + /// { + /// } + /// ]]> + /// + /// public event AudioReadEventHandler onReceived { add diff --git a/Runtime/Scripts/CameraExtension.cs b/Runtime/Scripts/CameraExtension.cs new file mode 100644 index 0000000000..b58a327979 --- /dev/null +++ b/Runtime/Scripts/CameraExtension.cs @@ -0,0 +1,101 @@ +using System; +using System.ComponentModel; +using UnityEngine; + +namespace Unity.WebRTC +{ + /// + /// Provides extension methods for objects to facilitate video streaming functionalities. + /// + public static class CameraExtension + { + /// + /// Creates an instance of for streaming video from a object. + /// + /// + /// It is recommended to maintain a reference to the instance created by this method. + /// Without a reference, the instance may be collected by the garbage collector automatically. + /// + /// The camera from which to capture video frames + /// The desired width of the video stream, in pixels. Must be greater than zero + /// The desired height of the video stream, in pixels. Must be greater than zero + /// The depth buffer format for the render texture. Default is + /// An optional to facilitate texture copying. Default is null + /// A instance that can be used to stream video. + /// + /// Creates a GameObject with a Camera component and a VideoStreamTrack capturing video from the camera. + /// (); + /// RawImage newSource = new GameObject("SourceImage").AddComponent(); + /// try + /// { + /// videoStreamTrackList.Add(newCam.CaptureStreamTrack(WebRTCSettings.StreamSize.x, WebRTCSettings.StreamSize.y)); + /// newSource.texture = newCam.targetTexture; + /// } + /// catch (Exception e) + /// { + /// Debug.LogError(e.Message); + /// } + /// } + /// ]]> + /// + public static VideoStreamTrack CaptureStreamTrack(this Camera cam, int width, int height, + RenderTextureDepth depth = RenderTextureDepth.Depth24, CopyTexture textureCopy = null) + { + switch (depth) + { + case RenderTextureDepth.Depth16: + case RenderTextureDepth.Depth24: + case RenderTextureDepth.Depth32: + break; + default: + throw new InvalidEnumArgumentException(nameof(depth), (int)depth, typeof(RenderTextureDepth)); + } + + if (width <= 0 || height <= 0) + { + throw new ArgumentException("width and height are should be greater than zero."); + } + + int depthValue = (int)depth; + var format = WebRTC.GetSupportedRenderTextureFormat(SystemInfo.graphicsDeviceType); + var rt = new UnityEngine.RenderTexture(width, height, depthValue, format); + rt.Create(); + cam.targetTexture = rt; + return new VideoStreamTrack(rt, textureCopy); + } + + /// + /// Creates an instance of capturing video from a object. + /// + /// + /// It is recommended to maintain a reference to the instance created by this method. + /// Without a reference, the instance may be collected by the garbage collector automatically. + /// + /// The camera from which to capture video frames + /// The desired width of the video stream, in pixels. Must be greater than zero + /// The desired height of the video stream, in pixels. Must be greater than zero + /// The depth buffer format for the render texture. Default is + /// A containing the video track captured from the camera. + /// + /// Creates a MediaStream with a VideoStreamTrack capturing video from the camera. + /// + /// + + public static MediaStream CaptureStream(this Camera cam, int width, int height, RenderTextureDepth depth = RenderTextureDepth.Depth24) + { + var stream = new MediaStream(); + var track = cam.CaptureStreamTrack(width, height, depth); + stream.AddTrack(track); + return stream; + } + } +} diff --git a/Runtime/Scripts/CameraExtension.cs.meta b/Runtime/Scripts/CameraExtension.cs.meta new file mode 100644 index 0000000000..b84a4d0def --- /dev/null +++ b/Runtime/Scripts/CameraExtension.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 29348c895bc5d8f459da06bc3c17a6b4 +timeCreated: 1599008246 \ No newline at end of file diff --git a/Runtime/Scripts/Context.cs b/Runtime/Scripts/Context.cs index 55c5ca637c..b218f15dc5 100644 --- a/Runtime/Scripts/Context.cs +++ b/Runtime/Scripts/Context.cs @@ -1,7 +1,13 @@ using System; using System.Runtime.InteropServices; using System.Threading; +using System.Collections; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + using UnityEngine; +using UnityEngine.Experimental.Rendering; #if UNITY_EDITOR using UnityEditor; @@ -219,34 +225,139 @@ public void DeletePeerConnection(IntPtr ptr) NativeMethods.ContextDeletePeerConnection(self, ptr); } + public RTCError PeerConnectionSetLocalDescription(IntPtr ptr, ref RTCSessionDescription desc) + { + IntPtr ptrError = IntPtr.Zero; +#if !UNITY_WEBGL + var observer = NativeMethods.PeerConnectionSetLocalDescription(ptr, ref desc, out var errorType, ref ptrError); + string message = ptrError != IntPtr.Zero ? ptrError.AsAnsiStringWithFreeMem() : null; + return new RTCError { errorType = errorType, message = message}; +#else + IntPtr buf = NativeMethods.PeerConnectionSetLocalDescription(self, ptr, desc.type, desc.sdp); + var arr = NativeMethods.ptrToIntPtrArray(buf); + RTCErrorType errorType = (RTCErrorType)arr[0]; + string errorMsg = arr[1].AsAnsiStringWithFreeMem(); + return new RTCError { errorType = errorType, message = errorMsg }; +#endif + } + + public RTCError PeerConnectionSetLocalDescription(IntPtr ptr) + { + IntPtr ptrError = IntPtr.Zero; +#if !UNITY_WEBGL + var observer = NativeMethods.PeerConnectionSetLocalDescriptionWithoutDescription(ptr, out var errorType, ref ptrError); + string message = ptrError != IntPtr.Zero ? ptrError.AsAnsiStringWithFreeMem() : null; + return new RTCError { errorType = errorType, message = message }; +#else + IntPtr buf = NativeMethods.PeerConnectionSetLocalDescriptionWithoutDescription(self, ptr); + var arr = NativeMethods.ptrToIntPtrArray(buf); + RTCErrorType errorType = (RTCErrorType)arr[0]; + string errorMsg = arr[1].AsAnsiStringWithFreeMem(); + return new RTCError { errorType = errorType, message = errorMsg }; +#endif + + } + + public RTCError PeerConnectionSetRemoteDescription(IntPtr ptr, ref RTCSessionDescription desc) + { + IntPtr ptrError = IntPtr.Zero; +#if !UNITY_WEBGL + var observer = NativeMethods.PeerConnectionSetRemoteDescription(ptr, ref desc, out var errorType, ref ptrError); + string message = ptrError != IntPtr.Zero ? ptrError.AsAnsiStringWithFreeMem() : null; + return new RTCError { errorType = errorType, message = message}; +#else + IntPtr buf = NativeMethods.PeerConnectionSetRemoteDescription(self, ptr, desc.type, desc.sdp); + var arr = NativeMethods.ptrToIntPtrArray(buf); + RTCErrorType errorType = (RTCErrorType)arr[0]; + string errorMsg = arr[1].AsAnsiStringWithFreeMem(); + return new RTCError { errorType = errorType, message = errorMsg }; +#endif + } + + public void PeerConnectionRegisterOnSetSessionDescSuccess(IntPtr ptr, DelegateNativePeerConnectionSetSessionDescSuccess callback) + { + NativeMethods.PeerConnectionRegisterOnSetSessionDescSuccess(self, ptr, callback); + } + + public void PeerConnectionRegisterOnSetSessionDescFailure(IntPtr ptr, DelegateNativePeerConnectionSetSessionDescFailure callback) + { + NativeMethods.PeerConnectionRegisterOnSetSessionDescFailure(self, ptr, callback); + } + + public IntPtr PeerConnectionAddTransceiver(IntPtr pc, IntPtr track) + { + return NativeMethods.PeerConnectionAddTransceiver(pc, track); + } + + public IntPtr PeerConnectionAddTransceiverWithType(IntPtr pc, TrackKind kind) + { + return NativeMethods.PeerConnectionAddTransceiverWithType(pc, kind); + } + public IntPtr PeerConnectionGetReceivers(IntPtr ptr, out ulong length) { +#if !UNITY_WEBGL return NativeMethods.PeerConnectionGetReceivers(self, ptr, out length); +#else + length = 0; + return NativeMethods.PeerConnectionGetReceivers(self, ptr); +#endif } public IntPtr PeerConnectionGetSenders(IntPtr ptr, out ulong length) { +#if !UNITY_WEBGL return NativeMethods.PeerConnectionGetSenders(self, ptr, out length); +#else + length = 0; + return NativeMethods.PeerConnectionGetSenders(self, ptr); +#endif } public IntPtr PeerConnectionGetTransceivers(IntPtr ptr, out ulong length) { +#if !UNITY_WEBGL return NativeMethods.PeerConnectionGetTransceivers(self, ptr, out length); +#else + length = 0; + return NativeMethods.PeerConnectionGetTransceivers(self, ptr); +#endif } public CreateSessionDescriptionObserver PeerConnectionCreateOffer(IntPtr ptr, ref RTCOfferAnswerOptions options) { +#if !UNITY_WEBGL return NativeMethods.PeerConnectionCreateOffer(self, ptr, ref options); +#else + var observer = new CreateSessionDescriptionObserver(); + NativeMethods.PeerConnectionRegisterOnSetSessionDescSuccess(self,ptr,(result) => observer.Invoke(RTCSdpType.Offer,result.ToString(),RTCErrorType.None,null)); + NativeMethods.PeerConnectionRegisterOnSetSessionDescFailure(self,ptr,(result,errorType,msg) => observer.Invoke(RTCSdpType.Offer,result.ToString(),(RTCErrorType)errorType,msg.ToString())); + NativeMethods.PeerConnectionCreateOffer(ptr,JObject.FromObject(options).ToString()); + return observer; +#endif } public CreateSessionDescriptionObserver PeerConnectionCreateAnswer(IntPtr ptr, ref RTCOfferAnswerOptions options) { +#if !UNITY_WEBGL return NativeMethods.PeerConnectionCreateAnswer(self, ptr, ref options); +#else + var observer = new CreateSessionDescriptionObserver(); + NativeMethods.PeerConnectionRegisterOnSetSessionDescSuccess(self,ptr,(result) => observer.Invoke(RTCSdpType.Answer,result.ToString(),RTCErrorType.None,null)); + NativeMethods.PeerConnectionRegisterOnSetSessionDescFailure(self,ptr,(result,errorType,msg) => observer.Invoke(RTCSdpType.Answer,result.ToString(),(RTCErrorType)errorType,msg.ToString())); + NativeMethods.PeerConnectionCreateAnswer(ptr,JObject.FromObject(options).ToString()); + return observer; +#endif } public IntPtr CreateDataChannel(IntPtr ptr, string label, ref RTCDataChannelInitInternal options) { +#if !UNITY_WEBGL return NativeMethods.ContextCreateDataChannel(self, ptr, label, ref options); +#else + var optionsJson = JsonUtility.ToJson(options); + return NativeMethods.ContextCreateDataChannel(self, ptr, label, optionsJson); +#endif } public void DeleteDataChannel(IntPtr ptr) @@ -334,11 +445,22 @@ public IntPtr CreateAudioTrack(string label, IntPtr trackSource) { return NativeMethods.ContextCreateAudioTrack(self, label, trackSource); } - +#if !UNITY_WEBGL public IntPtr CreateVideoTrack(string label, IntPtr source) { return NativeMethods.ContextCreateVideoTrack(self, label, source); } +#else + public IntPtr CreateVideoTrack(string label) + { + return IntPtr.Zero; // NativeMethods.ContextCreateVideoTrack(self, label); + } + + public IntPtr CreateVideoTrack(IntPtr srcTexturePtr, IntPtr dstTexturePtr, int width, int height) + { + return NativeMethods.ContextCreateVideoTrack(self, srcTexturePtr, dstTexturePtr, width, height); + } +#endif public void StopMediaStreamTrack(IntPtr track) { @@ -366,15 +488,36 @@ public void DeleteStatsReport(IntPtr report) NativeMethods.ContextDeleteStatsReport(self, report); } + public void SetVideoEncoderParameter(IntPtr track, int width, int height, GraphicsFormat format, IntPtr texturePtr) + { + NativeMethods.ContextSetVideoEncoderParameter(self, track, width, height, format, texturePtr); + } + +#if !UNITY_WEBGL public void GetSenderCapabilities(TrackKind kind, out IntPtr capabilities) { NativeMethods.ContextGetSenderCapabilities(self, kind, out capabilities); } +#else + public RTCRtpCapabilities GetSenderCapabilities(TrackKind kind) + { + string json = NativeMethods.ContextGetSenderCapabilities(self, kind); + return JsonConvert.DeserializeObject(json); + } +#endif +#if !UNITY_WEBGL public void GetReceiverCapabilities(TrackKind kind, out IntPtr capabilities) { NativeMethods.ContextGetReceiverCapabilities(self, kind, out capabilities); } +#else + public RTCRtpCapabilities GetReceiverCapabilities(TrackKind kind) + { + string json = NativeMethods.ContextGetReceiverCapabilities(self, kind); + return JsonConvert.DeserializeObject(json); + } +#endif internal void BatchUpdate(IntPtr batchData) { diff --git a/Runtime/Scripts/CreateSessionDescriptionObserver.cs b/Runtime/Scripts/CreateSessionDescriptionObserver.cs index 385533fd51..c797f3b2a6 100644 --- a/Runtime/Scripts/CreateSessionDescriptionObserver.cs +++ b/Runtime/Scripts/CreateSessionDescriptionObserver.cs @@ -7,7 +7,7 @@ class CreateSessionDescriptionObserver : SafeHandle { public Action onCreateSessionDescription; - private CreateSessionDescriptionObserver() + internal CreateSessionDescriptionObserver() : base(IntPtr.Zero, true) { } diff --git a/Runtime/Scripts/MediaStream.cs b/Runtime/Scripts/MediaStream.cs index 4e4b2ce381..ba51fdeff2 100644 --- a/Runtime/Scripts/MediaStream.cs +++ b/Runtime/Scripts/MediaStream.cs @@ -1,24 +1,36 @@ using System; using System.Collections.Generic; using System.Linq; +using UnityEngine; namespace Unity.WebRTC { /// - /// + /// /// /// public delegate void DelegateOnAddTrack(MediaStreamTrackEvent e); /// - /// + /// /// /// public delegate void DelegateOnRemoveTrack(MediaStreamTrackEvent e); /// - /// + /// Represents a stream of media content. /// + /// + /// `MediaStream` represents a stream of media content. + /// A stream consists of several tracks, such as video or audio tracks. + /// Each track is specified as an instance of `MediaStreamTrack`. + /// + /// + /// + /// + /// public class MediaStream : RefCountedObject { private DelegateOnAddTrack onAddTrack; @@ -26,23 +38,48 @@ public class MediaStream : RefCountedObject private HashSet cacheTracks = new HashSet(); +#if UNITY_WEBGL + // TODO Use MediaTrackConstraints instead of booleans + public class MediaStreamConstraints + { + public bool audio = true; + public bool video = true; + } + + public void AddUserMedia(MediaStreamConstraints constraints) + { + NativeMethods.MediaStreamAddUserMedia(self, JsonUtility.ToJson(constraints)); + } +#endif + /// - /// + /// String containing 36 characters denoting a unique identifier for the object. /// public string Id => NativeMethods.MediaStreamGetID(GetSelfOrThrow()).AsAnsiStringWithFreeMem(); /// - /// + /// Finalizer for MediaStream. /// + /// + /// Ensures that resources are released by calling the `Dispose` method. + /// ~MediaStream() { this.Dispose(); } /// - /// + /// Disposes of MediaStream. /// + /// + /// `Dispose` method disposes of the MediaStream and releases the associated resources. + /// + /// + /// + /// public override void Dispose() { if (this.disposed) @@ -58,7 +95,7 @@ public override void Dispose() } /// - /// + /// Delegate to be called when a new MediaStreamTrack object has been added. /// /// todo:(kazuki) Rename to "onAddTrack" /// todo:(kazuki) Should we change the API to use UnityEvent or Action class? @@ -72,7 +109,7 @@ public DelegateOnAddTrack OnAddTrack } /// - /// + /// Delegate to be called when a new MediaStreamTrack object has been removed. /// /// todo:(kazuki) Rename to "onAddTrack" /// todo:(kazuki) Should we change the API to use UnityEvent or Action class? @@ -86,43 +123,89 @@ public DelegateOnRemoveTrack OnRemoveTrack } /// - /// + /// Returns a list of VideoStreamTrack objects in the stream. /// - /// + /// + /// `GetVideoTracks` method returns a sequence that represents all the `VideoStreamTrack` objects in this stream's track set. + /// + /// List of `MediaStreamTrack` objects, one for each video track contained in the media stream. + /// + /// videoTracks = mediaStream.GetVideoTracks(); + /// ]]> + /// public IEnumerable GetVideoTracks() { +#if !UNITY_WEBGL var buf = NativeMethods.MediaStreamGetVideoTracks(GetSelfOrThrow(), out ulong length); return WebRTC.Deserialize(buf, (int)length, ptr => new VideoStreamTrack(ptr)); +#else + var ptr = NativeMethods.MediaStreamGetVideoTracks(GetSelfOrThrow()); + var buf = NativeMethods.ptrToIntPtrArray(ptr); + return WebRTC.Deserialize(buf, p => new VideoStreamTrack(p)); +#endif } /// - /// + /// Returns a list of AudioStreamTrack objects in the stream. /// - /// + /// + /// `GetAudioTracks` method returns a sequence that represents all the `AudioStreamTrack` objects in this stream's track set. + /// + /// List of `AudioStreamTrack` objects, one for each audio track contained in the stream. + /// + /// audioTracks = mediaStream.GetAudioTracks(); + /// ]]> + /// public IEnumerable GetAudioTracks() { +#if !UNITY_WEBGL var buf = NativeMethods.MediaStreamGetAudioTracks(GetSelfOrThrow(), out ulong length); return WebRTC.Deserialize(buf, (int)length, ptr => new AudioStreamTrack(ptr)); +#else + var ptr = NativeMethods.MediaStreamGetAudioTracks(GetSelfOrThrow()); + var buf = NativeMethods.ptrToIntPtrArray(ptr); + return WebRTC.Deserialize(buf, p => new AudioStreamTrack(p)); +#endif } /// - /// + /// Returns a list of MediaStreamTrack objects in the stream. /// - /// + /// + /// `GetTracks` method returns a sequence that represents all the `MediaStreamTrack` objects in this stream's track set. + /// + /// List of `MediaStreamTrack` objects. + /// + /// tracks = mediaStream.GetTracks(); + /// ]]> + /// public IEnumerable GetTracks() { return GetAudioTracks().Cast().Concat(GetVideoTracks()); } /// - /// Add a new track to the stream. + /// Add a new track to the stream. /// /// - /// This class keeps references of to avoid GC. - /// Please call the method when it's no longer needed. + /// `AddTrack` method adds a new track to the stream. + /// This class keeps references of to avoid GC. + /// Please call the method when it's no longer needed. /// - /// - /// + /// `MediaStreamTrack` object to add to the stream. + /// `true` if the track successfully added to the stream. + /// + /// + /// { + /// bool result = receiveStream.AddTrack(e.Track); + /// } + /// ]]> + /// /// public bool AddTrack(MediaStreamTrack track) { @@ -131,10 +214,18 @@ public bool AddTrack(MediaStreamTrack track) } /// - /// Remove a new track to the stream. + /// Remove a track from the stream. /// - /// - /// + /// + /// `RemoveTrack` method removes a track from the stream. + /// + /// `MediaStreamTrack` object to remove from the stream. + /// `true` if the track successfully removed from the stream. + /// + /// + /// /// public bool RemoveTrack(MediaStreamTrack track) { @@ -143,14 +234,24 @@ public bool RemoveTrack(MediaStreamTrack track) } /// - /// + /// Creates a MediaStream instance. /// + /// + /// `MediaStream` constructor creates an instance of `MediaStream`, + /// which serves as a collection of media tracks, + /// each represented by a `MediaStreamTrack` object. + /// + /// + /// + /// public MediaStream() : this(WebRTC.Context.CreateMediaStream(Guid.NewGuid().ToString())) { } /// - /// + /// /// /// internal MediaStream(IntPtr ptr) : base(ptr) diff --git a/Runtime/Scripts/MediaStreamTrack.cs b/Runtime/Scripts/MediaStreamTrack.cs index e633af3b2c..dfcf963642 100644 --- a/Runtime/Scripts/MediaStreamTrack.cs +++ b/Runtime/Scripts/MediaStreamTrack.cs @@ -4,13 +4,27 @@ namespace Unity.WebRTC { /// - /// + /// Represents a single media track within a stream. /// + /// + /// `MediaStreamTrack` represents a single media track within a stream. + /// Typically, these are audio or video tracks, but other track types may exist as well. + /// Each track is associated with a `MediaStream` object. + /// + /// + /// mediaStreamTracks = mediaStream.GetTracks(); + /// ]]> + /// + /// public class MediaStreamTrack : RefCountedObject { /// - /// + /// Boolean value that indicates whether the track is allowed to render the source stream. /// + /// + /// When the value is `true`, a track's data is output from the source to the destination; otherwise, empty frames are output. + /// public bool Enabled { get @@ -24,19 +38,19 @@ public bool Enabled } /// - /// + /// TrackState value that describes the status of the track. /// public TrackState ReadyState => NativeMethods.MediaStreamTrackGetReadyState(GetSelfOrThrow()); /// - /// + /// TrackKind value that describes the type of media for the track. /// public TrackKind Kind => NativeMethods.MediaStreamTrackGetKind(GetSelfOrThrow()); /// - /// + /// String containing a unique identifier (GUID) for the track. /// public string Id => NativeMethods.MediaStreamTrackGetID(GetSelfOrThrow()).AsAnsiStringWithFreeMem(); @@ -47,16 +61,27 @@ internal MediaStreamTrack(IntPtr ptr) : base(ptr) } /// - /// + /// Finalizer for MediaStreamTrack. /// + /// + /// Ensures that resources are released by calling the `Dispose` method + /// ~MediaStreamTrack() { this.Dispose(); } /// - /// + /// Disposes of MediaStreamTrack. /// + /// + /// `Dispose` method disposes of the `MediaStreamTrack` and releases the associated resources. + /// + /// + /// + /// public override void Dispose() { if (this.disposed) @@ -72,8 +97,16 @@ public override void Dispose() } /// - /// Disassociate track from its source(video or audio), not for destroying the track + /// Stops the track. /// + /// + /// `Stop` method disassociates the track from its source (video or audio) without destroying the track. + /// + /// + /// + /// public void Stop() { WebRTC.Context.StopMediaStreamTrack(GetSelfOrThrow()); @@ -172,6 +205,40 @@ internal RTCTrackEvent(IntPtr ptrTransceiver, RTCPeerConnection peer) } } +#if UNITY_WEBGL + public class RTCVideoTrackEvent + { + public RTCRtpTransceiver Transceiver { get; } + + public RTCRtpReceiver Receiver + { + get + { + return Transceiver.Receiver; + } + } + + public MediaStreamTrack Track + { + get + { + return Receiver.Track; + } + } + + public int Width { get; } + public int Height { get; } + + internal RTCVideoTrackEvent(IntPtr ptrTransceiver, RTCPeerConnection peer, int width, int height) + { + Transceiver = WebRTC.FindOrCreate( + ptrTransceiver, ptr => new RTCRtpTransceiver(ptr, peer)); + Width = width; + Height = height; + } + } +#endif + /// /// /// diff --git a/Runtime/Scripts/RTCDataChannel.cs b/Runtime/Scripts/RTCDataChannel.cs index 18d9a7f513..1bb448e242 100644 --- a/Runtime/Scripts/RTCDataChannel.cs +++ b/Runtime/Scripts/RTCDataChannel.cs @@ -6,41 +6,46 @@ namespace Unity.WebRTC { /// - /// + /// Provides configuration options for the data channel. /// /// public class RTCDataChannelInit { /// - /// + /// Determines whether the data channel ensures the delivery of messages in the order they were sent. /// public bool? ordered; + /// - /// + /// Specifies the maximum number of transmission retries for a message when operating under non-guaranteed delivery conditions. /// /// /// Cannot be set along with . /// /// public int? maxPacketLifeTime; + /// - /// + /// Specifies the maximum number of times the data channel will attempt to resend a message if initial transmission fails under unreliable conditions. /// /// /// Cannot be set along with . /// /// public int? maxRetransmits; + /// - /// + /// Specifies the sub protocol being used by the data channel to transmit and process messages. /// public string protocol; + /// - /// + /// Specifies whether the data channel's connection is manually negotiated by the application or automatically handled by WebRTC. /// public bool? negotiated; + /// - /// + /// Specifies a unique 16-bit identifier for the data channel, allowing explicit channel setup during manual negotiation. /// public int? id; } @@ -72,52 +77,300 @@ public static explicit operator RTCDataChannelInitInternal(RTCDataChannelInit or } /// - /// + /// Represents the method that will be invoked when the data channel is successfully opened and ready for communication. /// + /// + /// The `DelegateOnOpen` is triggered when the data channel's transport layer becomes successfully established and ready for data transfer. + /// This indicates that messages can now be sent and received over the channel, marking the transition from connecting to an operational state. + /// Useful for initializing or signaling to the application that the channel setup is complete and ready for communication. + /// This delegate is typically assigned to the property. + /// + /// + /// + /// + /// { + /// Debug.Log("DataChannel is now open and ready for communication."); + /// }; + /// } + /// } + /// ]]> + /// public delegate void DelegateOnOpen(); + /// - /// + /// Represents the method that will be invoked when the data channel has been closed and is no longer available for communication. /// + /// + /// The `DelegateOnClose` is triggered when the data channel's underlying transport is terminated, signaling that no further messages can be sent or received. + /// Useful for cleaning up resources or notifying the application that the data channel is no longer in use. + /// This marks a transition to a non-operational state, and to resume communication, a new data channel must be established. + /// This delegate is typically assigned to the property. + /// + /// + /// + /// + /// { + /// Debug.Log("DataChannel has been closed."); + /// }; + /// } + /// } + /// ]]> + /// public delegate void DelegateOnClose(); + /// - /// + /// Represents the method that will be invoked when a message is received from the remote peer over the data channel. /// - /// + /// + /// The `DelegateOnMessage` is executed when the data channel successfully receives a message from the remote peer, providing the message content as a parameter. + /// This allows the application to process the incoming data, whether it's for updating the UI, triggering gameplay logic, or handling any response actions. + /// The method receives the message as a byte array, making it flexible for both textual and binary data. + /// This delegate is typically assigned to the property. + /// + /// The message received as a byte array. + /// + /// + /// + /// { + /// string message = System.Text.Encoding.UTF8.GetString(bytes); + /// Debug.Log("Received message: " + message); + /// }; + /// } + /// } + /// ]]> + /// public delegate void DelegateOnMessage(byte[] bytes); + /// - /// + /// Represents the method that will be invoked when a new data channel is added to the RTCPeerConnection. /// - /// + /// + /// The `DelegateOnDataChannel` is triggered when a new data channel is established, typically as a result of the remote peer creating a channel. + /// This provides an opportunity to configure the new channel, such as setting message handlers or adjusting properties. + /// Ensuring the application is prepared to handle the new data channel is crucial for seamless peer-to-peer communication. + /// This delegate is typically assigned to the property. + /// + /// The RTCDataChannel that has been added to the connection. + /// + /// + /// + /// { + /// Debug.Log("A new data channel has been added: " + channel.Label); + /// channel.OnMessage = (bytes) => + /// { + /// string message = System.Text.Encoding.UTF8.GetString(bytes); + /// Debug.Log("Received message on new channel: " + message); + /// }; + /// }; + /// } + /// } + /// ]]> + /// public delegate void DelegateOnDataChannel(RTCDataChannel channel); +#if UNITY_WEBGL + public delegate void DelegateOnTextMessage(string msg); +#endif + /// - /// + /// Represents the method that will be invoked when an error occurs on the data channel. /// + /// + /// The `DelegateOnError` is executed whenever an error arises within the data channel, allowing applications to handle various error scenarios gracefully. + /// It provides detailed information about the error, enabling developers to implement corrective measures or issue notifications to users. + /// Handling such errors is crucial for maintaining robust and reliable peer-to-peer communication. + /// This delegate is typically assigned to the property. + /// + /// The RTCError object that contains details about the error. + /// + /// + /// + /// { + /// Debug.LogError("DataChannel error occurred: " + error.message); + /// // Additional error handling logic can be implemented here + /// }; + /// } + /// } + /// ]]> + /// + public delegate void DelegateOnError(RTCError error); /// - /// + /// Creates a new RTCDataChannel for peer-to-peer data exchange, using the specified label and options. /// + /// + /// The `CreateDataChannel` method establishes a bidirectional communication channel between peers, identified by a unique label. + /// This channel allows for the transmission of arbitrary data, such as text or binary, directly between connected peers without the need for a traditional server. + /// The optional parameters provide flexibility in controlling the behavior of the data channel, including options for reliability and ordering of messages. + /// It's essential for applications to configure these channels according to their specific communication needs. + /// + /// + /// { + /// Debug.LogFormat("Received: {0}.",${event.data}); + /// }; + /// + /// dataChannel.OnOpen = () => { + /// Debug.Log("DataChannel opened."); + /// }; + /// + /// dataChannel.OnClose = () => { + /// Debug.Log("DataChannel closed."); + /// }; + /// ]]> + /// /// public class RTCDataChannel : RefCountedObject { private DelegateOnMessage onMessage; +#if UNITY_WEBGL + private DelegateOnTextMessage onTextMessage; +#endif private DelegateOnOpen onOpen; private DelegateOnClose onClose; private DelegateOnError onError; /// - /// + /// Delegate to be called when a message has been received from the remote peer. /// + /// + /// The `OnMessage` delegate is invoked whenever a message is received over the data channel from the remote peer. + /// This provides the application an opportunity to process the received data, which could include tasks such as updating the user interface, storing information, or triggering specific logic. + /// The message is delivered as a byte array, offering flexibility to handle both text and binary data formats. + /// + /// + /// { + /// Debug.LogFormat("Received: {0}.", e.data); + /// }; + /// } + /// } + /// ]]> + /// public DelegateOnMessage OnMessage { get => onMessage; set => onMessage = value; } +#if UNITY_WEBGL /// /// /// + public DelegateOnTextMessage OnTextMessage + { + get { return onTextMessage; } + set { onTextMessage = value; } + } +#endif + + /// + /// Delegate to be called when the data channel's message transport mechanism is opened or reopened. + /// + /// + /// The `OnOpen` delegate is triggered when the data channel successfully establishes its underlying transport mechanism. + /// This state transition indicates that the channel is ready for data transmission, providing an opportunity for the application to initialize any required states or notify the user that the channel is ready to use. + /// It is a critical event for setting up initial data exchanges between peers. + /// + /// + /// { + /// Debug.Log("DataChannel opened."); + /// }; + /// } + /// } + /// ]]> + /// public DelegateOnOpen OnOpen { get => onOpen; @@ -125,8 +378,33 @@ public DelegateOnOpen OnOpen } /// - /// + /// Delegate to be called when the data channel's message transport mechanism is closed. /// + /// + /// The close event is sent to the onclose event handler on an RTCDataChannel instance when the data transport for the data channel has closed. + /// Before any further data can be transferred using RTCDataChannel, a new 'RTCDataChannel' instance must be created. + /// This event is not cancelable and does not bubble. + /// + /// + /// { + /// Debug.Log("DataChannel closed."); + /// }; + /// } + /// } + /// ]]> + /// public DelegateOnClose OnClose { get => onClose; @@ -134,8 +412,33 @@ public DelegateOnClose OnClose } /// - /// + /// Delegate to be called when errors occur. /// + /// + /// The `OnClose` delegate is triggered when the data channel's transport layer is terminated, signifying the channel's transition to a closed state. + /// This event serves as a cue for the application to release resources, update the user interface, or handle any clean-up operations necessary to gracefully end the communication session. + /// Understanding this transition is vital for managing the lifecycle of data exchanges between peers. + /// + /// + /// { + /// Debug.LogError("DataChannel error: " + e.message); + /// }; + /// } + /// } + /// ]]> + /// public DelegateOnError OnError { get => onError; @@ -143,53 +446,317 @@ public DelegateOnError OnError } /// - /// + /// Returns an ID number (between 0 and 65,534) which uniquely identifies the RTCDataChannel. /// + /// + /// The `Id` property provides a unique identifier for the data channel, typically assigned during the channel's creation. + /// This identifier is used internally to differentiate between multiple data channels associated with a single RTCPeerConnection. + /// Understanding and referencing these IDs can be crucial when managing complex peer-to-peer communication setups where multiple channels are active. + /// The ID is automatically generated unless explicitly set during manual channel negotiation. + /// + /// + /// + /// public int Id => NativeMethods.DataChannelGetID(GetSelfOrThrow()); /// - /// + /// Returns a string description of the data channel, which is not required to be unique. /// + /// + /// The `Label` property specifies a name for the data channel, which is set when the channel is created. + /// This label is useful for identifying the purpose of the data channel, such as distinguishing between channels dedicated to different types of data or tasks. + /// While labels are not required to be unique, they provide meaningful context within an application, aiding in organization and management of multiple channels. + /// Developers can utilize labels to group channels by function or to describe their role in the communication process. + /// + /// + /// + /// public string Label => NativeMethods.DataChannelGetLabel(GetSelfOrThrow()).AsAnsiStringWithFreeMem(); /// - /// + /// Returns the subprotocol being used by the data channel to transmit and process messages. /// + /// + /// The `Protocol` property retrieves the subprotocol negotiated for this data channel, which governs the rules for message format and communication behavior between peers. + /// This property is critical for ensuring compatibility and understanding between different systems or applications using the channel, especially when custom protocols are used. + /// If no protocol was specified during the data channel's creation, this property returns an empty string, indicating that no particular subprotocol is in effect. + /// + /// + /// + /// public string Protocol => NativeMethods.DataChannelGetProtocol(GetSelfOrThrow()).AsAnsiStringWithFreeMem(); /// - /// + /// Returns the maximum number of times the browser should try to retransmit a message before giving up. /// + /// + /// The `MaxRetransmits` property defines the upper limit on the number of times a message will be retransmitted if initial delivery fails. + /// This setting is particularly valuable in conditions where reliable delivery is necessary, but the application is sensitive to potential delays caused by continuous retransmission attempts. + /// By specifying a limit, developers can balance the need for message reliability with the potential impact on performance and latency. + /// If no retransmit limit is set, the data channel may continue to attempt message delivery until it succeeds, which might not be suitable for all applications. /// + /// + /// + /// public ushort MaxRetransmits => NativeMethods.DataChannelGetMaxRetransmits(GetSelfOrThrow()); /// - /// + /// Returns the amount of time, in milliseconds, the browser is allowed to take to attempt to transmit a message, as set when the data channel was created, or null. /// + /// + /// The `MaxRetransmitTime` property sets the maximum duration, in milliseconds, that the data channel will attempt to retransmit a message in unreliable mode. + /// This constraint ensures that if a message cannot be delivered within the specified time frame, the channel will cease retransmission attempts. + /// It is particularly useful for applications where timing is critical, allowing developers to limit delays potentially caused by prolonged retransmission efforts. + /// By defining this timeout, applications can maintain performance efficiency while handling network fluctuations. + /// If not set, the retransmission will continue based on other reliability settings, possibly yielding variable delays. /// + /// + /// + /// public ushort MaxRetransmitTime => NativeMethods.DataChannelGetMaxRetransmitTime(GetSelfOrThrow()); /// - /// + /// Determines whether the data channel ensures the delivery of messages in the order they were sent. /// + /// + /// The `Ordered` property controls whether the data channel delivers messages in the sequence they were dispatched. + /// If set to true, messages will arrive in the exact order sent, ensuring consistent data flow, which can be critical for applications where order is important. + /// If false, the data channel allows out-of-order delivery to potentially enhance transmission speed but is best suited for applications where strict order isn't a concern. /// + /// + /// + /// public bool Ordered => NativeMethods.DataChannelGetOrdered(GetSelfOrThrow()); /// - /// + /// Returns the number of bytes of data currently queued to be sent over the data channel. /// + /// + /// The `BufferedAmount` property indicates the number of bytes of data currently queued to be sent over the data channel. + /// This value represents the amount of data buffered on the sender side that has not yet been transmitted to the network. + /// Monitoring this property helps developers understand and manage flow control, allowing for adjustments to data transmission rates to avoid congestion. + /// In scenarios where this value grows unexpectedly, it could indicate network congestion or slow peer processing, prompting the need to throttle data sending. + /// Proper use of this property ensures that applications can maintain efficient data flow while mitigating potential bottlenecks. + /// + /// + /// + /// public ulong BufferedAmount => NativeMethods.DataChannelGetBufferedAmount(GetSelfOrThrow()); /// - /// + /// Indicates whether the RTCDataChannel's connection is negotiated by the Web app or by the WebRTC layer. /// + /// + /// The `Negotiated` property indicates whether the data channel's connection parameters were explicitly negotiated by the application or automatically handled by the WebRTC implementation. + /// When set to `true`, it allows developers to manually manage the channel setup including selecting the channel ID, offering greater control over communication specifics. + /// This is especially useful in advanced scenarios where integration with complex signaling servers or custom negotiation processes are needed. + /// If `false`, the WebRTC stack automatically negotiates the channel's configuration, simplifying the setup but providing less granular control. + /// Proper switching between these modes ensures the application meets its communication requirements effectively. + /// + /// + /// + /// public bool Negotiated => NativeMethods.DataChannelGetNegotiated(GetSelfOrThrow()); /// - /// The property returns an enum of the RTCDataChannelState which shows + /// Returns an enum of the RTCDataChannelState which shows /// the state of the channel. /// /// /// method must be called when the state is Open. /// /// + /// + /// + /// public RTCDataChannelState ReadyState => NativeMethods.DataChannelGetReadyState(GetSelfOrThrow()); [AOT.MonoPInvokeCallback(typeof(DelegateNativeOnMessage))] @@ -204,6 +771,21 @@ static void DataChannelNativeOnMessage(IntPtr ptr, byte[] msg, int size) }); } +#if UNITY_WEBGL + [AOT.MonoPInvokeCallback(typeof(DelegateNativeOnMessage))] + static void DataChannelNativeOnTextMessage(IntPtr ptr, IntPtr msgPtr) + { + WebRTC.Sync(ptr, () => + { + if (WebRTC.Table[ptr] is RTCDataChannel channel) + { + var msg = msgPtr.AsAnsiStringWithFreeMem(); + channel.onTextMessage?.Invoke(msg); + } + }); + } +#endif + [AOT.MonoPInvokeCallback(typeof(DelegateNativeOnOpen))] static void DataChannelNativeOnOpen(IntPtr ptr) { @@ -252,7 +834,7 @@ internal RTCDataChannel(IntPtr ptr, RTCPeerConnection peerConnection) } /// - /// + /// /// ~RTCDataChannel() { @@ -260,8 +842,58 @@ internal RTCDataChannel(IntPtr ptr, RTCPeerConnection peerConnection) } /// - /// + /// Release all the resources RTCDataChannel instance has allocated. /// + /// + /// The Dispose method leaves the RTCDataChannel in an unusable state. + /// After calling Dispose, you must release all references to the RTCDataChannel + /// so the garbage collector can reclaim the memory that the RTCDataChannel was occupying. + /// + /// Note: Always call Dispose before you release your last reference to the + /// RTCDataChannel. Otherwise, the resources it is using will not be freed + /// until the garbage collector calls the Finalize method of the object. + /// + /// + /// + /// public override void Dispose() { if (this.disposed) @@ -278,14 +910,70 @@ public override void Dispose() } /// - /// The method Sends data across the data channel to the remote peer. + /// Sends data across the data channel to the remote peer. /// + /// + /// This can be done any time except during the initial process of creating the underlying transport channel. + /// Data sent before connecting is buffered if possible (or an error occurs if it's not possible), + /// and is also buffered if sent while the connection is closing or closed. + /// /// /// The method throws InvalidOperationException when /// is not Open. /// - /// + /// The string message to be sent to the remote peer. /// + /// + /// + /// { + /// Debug.Log("DataChannel opened."); + /// SendMessage("Hello, WebRTC!"); + /// }; + /// + /// dataChannel.OnMessage = (e) => + /// { + /// Debug.Log("Received message: " + e.data); + /// }; + /// } + /// + /// private void SendMessage(string message) + /// { + /// if (dataChannel.ReadyState == RTCDataChannelState.Open) + /// { + /// dataChannel.Send(message); + /// Debug.Log("Sent message: " + message); + /// } + /// else + /// { + /// Debug.LogWarning("DataChannel is not open. Cannot send message."); + /// } + /// } + /// + /// private void OnDestroy() + /// { + /// if (dataChannel != null) + /// { + /// dataChannel.Close(); + /// dataChannel.Dispose(); + /// } + /// } + /// } + /// ]]> + /// public void Send(string msg) { if (ReadyState != RTCDataChannelState.Open) @@ -296,14 +984,77 @@ public void Send(string msg) } /// - /// The method Sends data across the data channel to the remote peer. + /// Sends data across the data channel to the remote peer. /// + /// + /// This can be done any time except during the initial process of creating the underlying transport channel. + /// Data sent before connecting is buffered if possible (or an error occurs if it's not possible), + /// and is also buffered if sent while the connection is closing or closed. + /// /// /// The method throws InvalidOperationException when /// is not Open. /// - /// + /// The byte array to be sent to the remote peer. /// + /// + /// + /// { + /// Debug.Log("DataChannel opened."); + /// SendMessage(new byte[] { 0x01, 0x02, 0x03 }); + /// }; + /// + /// dataChannel.OnMessage = (e) => + /// { + /// if (e.binary) + /// { + /// Debug.Log("Received binary message of length: " + e.data.Length); + /// } + /// else + /// { + /// Debug.Log("Received message: " + e.data); + /// } + /// }; + /// } + /// + /// public void SendMessage(byte[] message) + /// { + /// if (dataChannel.ReadyState == RTCDataChannelState.Open) + /// { + /// dataChannel.Send(message); + /// Debug.Log("Sent binary message of length: " + message.Length); + /// } + /// else + /// { + /// Debug.LogWarning("DataChannel is not open. Cannot send message."); + /// } + /// } + /// + /// private void OnDestroy() + /// { + /// if (dataChannel != null) + /// { + /// dataChannel.Close(); + /// dataChannel.Dispose(); + /// } + /// } + /// } + /// ]]> + /// public void Send(byte[] msg) { if (ReadyState != RTCDataChannelState.Open) @@ -314,10 +1065,81 @@ public void Send(byte[] msg) } /// - /// + /// Sends data across the data channel to the remote peer. /// - /// - /// + /// + /// This can be done any time except during the initial process of creating the underlying transport channel. + /// Data sent before connecting is buffered if possible (or an error occurs if it's not possible), + /// and is also buffered if sent while the connection is closing or closed. + /// + /// + /// The method throws InvalidOperationException when + /// is not Open. + /// + /// The type of elements stored in the NativeArray, which must be a value type. + /// The NativeArray containing the data to be sent to the remote peer. + /// + /// + /// + /// { + /// Debug.Log("DataChannel opened."); + /// var nativeArray = new NativeArray(new byte[] { 0x01, 0x02, 0x03 }, Allocator.Temp); + /// Send(nativeArray); + /// nativeArray.Dispose(); + /// }; + /// + /// dataChannel.OnMessage = (e) => + /// { + /// if (e.binary) + /// { + /// Debug.Log("Received binary message of length: " + e.data.Length); + /// } + /// }; + /// } + /// + /// public unsafe void Send(NativeArray msg) where T : struct + /// { + /// if (dataChannel.ReadyState == RTCDataChannelState.Open) + /// { + /// void* ptr = msg.GetUnsafePtr(); + /// byte[] bytes = new byte[msg.Length * UnsafeUtility.SizeOf()]; + /// System.Runtime.InteropServices.Marshal.Copy(new IntPtr(ptr), bytes, 0, bytes.Length); + /// dataChannel.Send(bytes); + /// Debug.Log("Sent binary message of length: " + bytes.Length); + /// } + /// else + /// { + /// Debug.LogWarning("DataChannel is not open. Cannot send message."); + /// } + /// } + /// + /// private void OnDestroy() + /// { + /// if (dataChannel != null) + /// { + /// dataChannel.Close(); + /// dataChannel.Dispose(); + /// } + /// } + /// } + /// ]]> + /// public unsafe void Send(NativeArray msg) where T : struct { @@ -333,10 +1155,83 @@ public unsafe void Send(NativeArray msg) } /// - /// + /// Sends data across the data channel to the remote peer. /// - /// - /// + /// + /// This can be done any time except during the initial process of creating the underlying transport channel. + /// Data sent before connecting is buffered if possible (or an error occurs if it's not possible), + /// and is also buffered if sent while the connection is closing or closed. + /// + /// + /// Thrown when the is not Open. + /// + /// The type of elements stored in the NativeSlice, which must be a value type. + /// The NativeSlice containing the data to be sent to the remote peer. + /// + /// + /// + /// { + /// Debug.Log("DataChannel opened."); + /// var nativeArray = new NativeArray(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 }, Allocator.Temp); + /// var nativeSlice = new NativeSlice(nativeArray, 1, 3); // Slice from index 1 to 3 + /// Send(nativeSlice); + /// nativeArray.Dispose(); + /// }; + /// + /// dataChannel.OnMessage = (e) => + /// { + /// if (e.binary) + /// { + /// Debug.Log("Received binary message of length: " + e.data.Length); + /// } + /// }; + /// } + /// + /// public unsafe void Send(NativeSlice msg) where T : struct + /// { + /// if (dataChannel.ReadyState == RTCDataChannelState.Open) + /// { + /// void* ptr = msg.GetUnsafeReadOnlyPtr(); + /// byte[] bytes = new byte[msg.Length * UnsafeUtility.SizeOf()]; + /// System.Runtime.InteropServices.Marshal.Copy(new IntPtr(ptr), bytes, 0, bytes.Length); + /// dataChannel.Send(bytes); + /// Debug.Log("Sent binary message of length: " + bytes.Length); + /// } + /// else + /// { + /// Debug.LogWarning("DataChannel is not open. Cannot send message."); + /// } + /// } + /// + /// private void OnDestroy() + /// { + /// if (dataChannel != null) + /// { + /// dataChannel.Close(); + /// dataChannel.Dispose(); + /// } + /// } + /// } + /// ]]> + /// + public unsafe void Send(NativeSlice msg) where T : struct { @@ -350,10 +1245,82 @@ public unsafe void Send(NativeSlice msg) #if UNITY_2020_1_OR_NEWER // ReadOnly support was introduced in 2020.1 /// - /// + /// Sends data across the data channel to the remote peer. /// - /// - /// + /// + /// This can be done any time except during the initial process of creating the underlying transport channel. + /// Data sent before connecting is buffered if possible (or an error occurs if it's not possible), + /// and is also buffered if sent while the connection is closing or closed. + /// + /// + /// Thrown when the is not Open. + /// + /// The type of elements stored in the read-only NativeArray, which must be a value type. + /// The read-only NativeArray containing the data to be sent to the remote peer. + /// + /// + /// + /// { + /// Debug.Log("DataChannel opened."); + /// var nativeArray = new NativeArray(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 }, Allocator.Temp); + /// var readOnlyArray = nativeArray.AsReadOnly(); + /// Send(readOnlyArray); + /// nativeArray.Dispose(); + /// }; + /// + /// dataChannel.OnMessage = (e) => + /// { + /// if (e.binary) + /// { + /// Debug.Log("Received binary message of length: " + e.data.Length); + /// } + /// }; + /// } + /// + /// public unsafe void Send(NativeArray.ReadOnly msg) where T : struct + /// { + /// if (dataChannel.ReadyState == RTCDataChannelState.Open) + /// { + /// void* ptr = msg.GetUnsafeReadOnlyPtr(); + /// byte[] bytes = new byte[msg.Length * UnsafeUtility.SizeOf()]; + /// System.Runtime.InteropServices.Marshal.Copy(new IntPtr(ptr), bytes, 0, bytes.Length); + /// dataChannel.Send(bytes); + /// Debug.Log("Sent binary message of length: " + bytes.Length); + /// } + /// else + /// { + /// Debug.LogWarning("DataChannel is not open. Cannot send message."); + /// } + /// } + /// + /// private void OnDestroy() + /// { + /// if (dataChannel != null) + /// { + /// dataChannel.Close(); + /// dataChannel.Dispose(); + /// } + /// } + /// } + /// ]]> + /// public unsafe void Send(NativeArray.ReadOnly msg) where T : struct { @@ -366,10 +1333,19 @@ public unsafe void Send(NativeArray.ReadOnly msg) #endif /// - /// + /// Sends data across the data channel to the remote peer. /// - /// - /// + /// + /// This can be done any time except during the initial process of creating the underlying transport channel. + /// Data sent before connecting is buffered if possible (or an error occurs if it's not possible), + /// and is also buffered if sent while the connection is closing or closed. + /// + /// + /// Thrown when the is not Open. + /// + /// A pointer to the memory location containing the data to be sent. + /// The length of the data, in bytes, to be sent from the specified memory location. + /// public unsafe void Send(void* msgPtr, int length) { if (ReadyState != RTCDataChannelState.Open) @@ -380,10 +1356,72 @@ public unsafe void Send(void* msgPtr, int length) } /// - /// + /// Sends data across the data channel to the remote peer. /// - /// - /// + /// + /// This can be done any time except during the initial process of creating the underlying transport channel. + /// Data sent before connecting is buffered if possible (or an error occurs if it's not possible), + /// and is also buffered if sent while the connection is closing or closed. + /// + /// + /// Thrown when the is not Open. + /// + /// A pointer to the memory location containing the data to be sent. + /// The length of the data, in bytes, to be sent from the specified memory location. + /// + /// + /// + /// { + /// Debug.Log("DataChannel opened."); + /// + /// byte[] data = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 }; + /// unsafe + /// { + /// fixed (byte* dataPtr = data) + /// { + /// Send(dataPtr, data.Length); + /// } + /// } + /// }; + /// + /// dataChannel.OnMessage = (e) => + /// { + /// if (e.binary) + /// { + /// Debug.Log("Received binary message of length: " + e.data.Length); + /// } + /// }; + /// } + /// + /// + /// private void OnDestroy() + /// { + /// if (dataChannel != null) + /// { + /// dataChannel.Close(); + /// dataChannel.Dispose(); + /// } + /// } + /// } + /// ]]> + /// public void Send(IntPtr msgPtr, int length) { if (ReadyState != RTCDataChannelState.Open) @@ -397,8 +1435,61 @@ public void Send(IntPtr msgPtr, int length) } /// - /// + /// Closes the RTCDataChannel. Either peer is permitted to call this method to initiate closure of the channel. /// + /// + /// Closure of the data channel is not instantaneous. Most of the process of closing the connection is handled asynchronously; + /// you can detect when the channel has finished closing by watching for a close event on the data channel. + /// + /// + /// + /// { + /// Debug.Log("DataChannel opened."); + /// }; + /// + /// dataChannel.OnClose = () => + /// { + /// Debug.Log("DataChannel closed."); + /// }; + /// + /// // Assume some operation has been completed and we need to close the data channel + /// Invoke("CloseDataChannel", 5.0f); // Close the channel after 5 seconds + /// } + /// + /// private void CloseDataChannel() + /// { + /// if (dataChannel != null) + /// { + /// dataChannel.Close(); + /// Debug.Log("DataChannel has been closed manually."); + /// } + /// } + /// + /// private void OnDestroy() + /// { + /// // Clean up the data channel when the GameObject is destroyed + /// if (dataChannel != null) + /// { + /// dataChannel.Close(); + /// } + /// } + /// } + /// ]]> + /// public void Close() { NativeMethods.DataChannelClose(GetSelfOrThrow()); diff --git a/Runtime/Scripts/RTCIceCandidate.cs b/Runtime/Scripts/RTCIceCandidate.cs index ce00232554..c1f1b56358 100644 --- a/Runtime/Scripts/RTCIceCandidate.cs +++ b/Runtime/Scripts/RTCIceCandidate.cs @@ -1,23 +1,24 @@ using System; using System.Runtime.InteropServices; +using UnityEngine; namespace Unity.WebRTC { /// - /// + /// /// public class RTCIceCandidateInit { /// - /// + /// /// public string candidate; /// - /// + /// /// public string sdpMid; /// - /// + /// /// public int? sdpMLineIndex; } @@ -29,68 +30,68 @@ public class RTCIceCandidateInit public enum RTCIceComponent : int { /// - /// + /// /// Rtp = 1, /// - /// + /// /// Rtcp = 2, } /// - /// + /// /// public enum RTCIceProtocol : int { /// - /// + /// /// Udp = 1, /// - /// + /// /// Tcp = 2 } /// - /// + /// /// public enum RTCIceCandidateType { /// - /// + /// /// Host, /// - /// + /// /// Srflx, /// - /// + /// /// Prflx, /// - /// + /// /// Relay } /// - /// + /// /// public enum RTCIceTcpCandidateType { /// - /// + /// /// Active, /// - /// + /// /// Passive, /// - /// + /// /// So } @@ -114,8 +115,10 @@ public static RTCIceCandidateType ParseRTCIceCandidateType(this string src) { switch (src) { + case "host": case "local": return RTCIceCandidateType.Host; + case "srflx": case "stun": return RTCIceCandidateType.Srflx; case "prflx": @@ -147,64 +150,64 @@ public static RTCIceCandidateType ParseRTCIceCandidateType(this string src) } /// - /// + /// /// public class RTCIceCandidate : IDisposable { /// - /// + /// /// public string Candidate => NativeMethods.IceCandidateGetSdp(self); /// - /// + /// /// public string SdpMid => NativeMethods.IceCandidateGetSdpMid(self); /// - /// + /// /// public int? SdpMLineIndex => NativeMethods.IceCandidateGetSdpLineIndex(self); /// - /// + /// /// public string Foundation => _candidate.foundation; /// - /// + /// /// public RTCIceComponent? Component => _candidate.component; /// - /// + /// /// public uint Priority => _candidate.priority; /// - /// + /// /// public string Address => _candidate.address; /// - /// + /// /// public RTCIceProtocol? Protocol => _candidate.protocol.ParseRTCIceProtocol(); /// - /// + /// /// public ushort? Port => _candidate.port; /// - /// + /// /// public RTCIceCandidateType? Type => _candidate.type.ParseRTCIceCandidateType(); /// - /// + /// /// public RTCIceTcpCandidateType? TcpType => _candidate.tcpType.ParseRTCIceTcpCandidateType(); /// - /// + /// /// public string RelatedAddress => _candidate.relatedAddress; /// - /// + /// /// public ushort? RelatedPort => _candidate.relatedPort; /// - /// + /// /// public string UserNameFragment => _candidate.usernameFragment; @@ -214,7 +217,7 @@ public class RTCIceCandidate : IDisposable private bool disposed; /// - /// + /// /// ~RTCIceCandidate() { @@ -242,16 +245,21 @@ public void Dispose() } /// - /// + /// /// /// +#if !UNITY_WEBGL public RTCIceCandidate(RTCIceCandidateInit candidateInfo = null) +#else + public RTCIceCandidate(RTCIceCandidateInit candidateInfo = null, IntPtr? iceCandidatePtr = null) +#endif { candidateInfo = candidateInfo ?? new RTCIceCandidateInit(); if (candidateInfo.sdpMLineIndex == null && candidateInfo.sdpMid == null) throw new ArgumentException("sdpMid and sdpMLineIndex are both null"); - RTCIceCandidateInitInternal option = (RTCIceCandidateInitInternal)candidateInfo; + RTCIceCandidateInitInternal option = (RTCIceCandidateInitInternal) candidateInfo; +#if !UNITY_WEBGL RTCErrorType error = NativeMethods.CreateIceCandidate(ref option, out self); if (error != RTCErrorType.None) throw new ArgumentException( @@ -259,8 +267,18 @@ public RTCIceCandidate(RTCIceCandidateInit candidateInfo = null) $"candidate:{candidateInfo.candidate}\n" + $"sdpMid:{candidateInfo.sdpMid}\n" + $"sdpMLineIndex:{candidateInfo.sdpMLineIndex}\n"); - NativeMethods.IceCandidateGetCandidate(self, out _candidate); +#else + + if (iceCandidatePtr == null) + { + iceCandidatePtr = NativeMethods.CreateNativeRTCIceCandidate(option.candidate, option.sdpMid, option.sdpMLineIndex); + } + + self = iceCandidatePtr.Value; + string json = NativeMethods.IceCandidateGetCandidate(self); + _candidate = JsonUtility.FromJson(json); +#endif } } diff --git a/Runtime/Scripts/RTCPeerConnection.cs b/Runtime/Scripts/RTCPeerConnection.cs index 5b545b1798..3fb056bbf6 100644 --- a/Runtime/Scripts/RTCPeerConnection.cs +++ b/Runtime/Scripts/RTCPeerConnection.cs @@ -1,37 +1,146 @@ using System; +using System.Linq; using System.Collections.Generic; +using System.Runtime.InteropServices; + using UnityEngine; +using Newtonsoft.Json; + namespace Unity.WebRTC { /// - /// + /// Delegate to be called when a new RTCIceCandidate is identified and added to the local peer, when all candidates for a specific generation are identified and added, and when the ICE gathering on all transports is complete. /// - /// + /// + /// This delegate is called when: + /// * An `RTCIceCandidate` is added to the local peer using `SetLocalDescription`. + /// * Every `RTCIceCandidate` correlated with a specific username/password combination are added. + /// * ICE gathering for all transports is finished. + /// + /// `RTCIceCandidate` object containing the candidate associated with the event. + /// + /// + /// { + /// otherPeerConnection.AddIceCandidate(candidate); + /// } + /// ]]> + /// + /// public delegate void DelegateOnIceCandidate(RTCIceCandidate candidate); + /// - /// + /// Delegate to be called when the ICE connection state is changed. /// - /// + /// + /// This delegate is called each time the ICE connection state changes during the negotiation process. + /// + /// `RTCIceConnectionState` value. + /// + /// + /// { + /// if (state == RTCIceConnectionState.Connected || state == RTCIceConnectionState.Completed) + /// { + /// foreach (RTCRtpSender sender in peerConnection.GetSenders()) + /// { + /// sender.SyncApplicationFramerate = true; + /// } + /// } + /// } + /// ]]> + /// + /// public delegate void DelegateOnIceConnectionChange(RTCIceConnectionState state); + /// - /// + /// Delegate to be called after a new track has been added to an RTCRtpReceiver which is part of the connection. /// + /// + /// This delegate is called after a new track has been added to an `RTCRtpReceiver` which is part of the connection. + /// /// + /// + /// + /// { + /// Debug.Log($"Connection state changed to {state}"); + /// } + /// ]]> + /// + /// public delegate void DelegateOnConnectionStateChange(RTCPeerConnectionState state); + /// - /// + /// Delegate to be called when the state of the ICE candidate gathering process changes. /// + /// + /// This delegate is called when the state of the ICE candidate gathering process changes. + /// /// + /// + /// + /// { + /// if (state == RTCIceGatheringState.Complete) + /// { + /// GameObject newCandidate = Instantiate(candidateElement, candidateParent); + /// } + /// } + /// ]]> + /// + /// public delegate void DelegateOnIceGatheringStateChange(RTCIceGatheringState state); + /// - /// + /// Delegate to be called when negotiation of the connection through the signaling channel is required. /// + /// + /// This delegate is called when negotiation of the connection through the signaling channel is required. + /// + /// + /// + /// { + /// StartCoroutine(NegotiationProcess()); + /// } + /// + /// IEnumerator NegotiationProcess() + /// { + /// RTCSessionDescriptionAsyncOperation asyncOperation = peerConnection.CreateOffer(); + /// yield return asyncOperation; + /// + /// if (!asyncOperation.IsError) + /// { + /// RTCSessionDescription description = asyncOperation.Desc; + /// RTCSetSessionDescriptionAsyncOperation asyncOperation = peerConnection.SetLocalDescription(ref description); + /// yield return asyncOperation; + /// } + /// } + /// ]]> + /// + /// + /// public delegate void DelegateOnNegotiationNeeded(); + /// - /// + /// Delegate to be called after a new track has been added to an RTCRtpReceiver which is part of the connection. /// - /// + /// + /// This delegate is called after a new track has been added to an `RTCRtpReceiver` which is part of the connection. + /// + /// `RTCTrackEvent` object. + /// + /// + /// { + /// receiveStream.AddTrack(e.Track); + /// } + /// ]]> + /// + /// public delegate void DelegateOnTrack(RTCTrackEvent e); @@ -39,13 +148,22 @@ namespace Unity.WebRTC internal delegate void DelegateSetSessionDescFailure(RTCError error); /// - /// Represents a WebRTC connection between the local peer and remote peer. + /// Represents a WebRTC connection between the local peer and remote peer. /// - /// /// - /// + /// `RTCPeerConnection` class represents a WebRTC connection between the local computer and a remote peer. + /// It provides methods to connect to a remote peer, maintain and monitor the connection, and close the connection once it's no longer needed. /// - /// + /// + /// + /// + /// + /// + /// + /// + /// public class RTCPeerConnection : IDisposable { private IntPtr self; @@ -53,16 +171,28 @@ public class RTCPeerConnection : IDisposable private bool disposed; /// - /// + /// Finalizer for RTCPeerConnection. /// + /// + /// Ensures that resources are released by calling the `Dispose` method. + /// ~RTCPeerConnection() { this.Dispose(); } /// - /// + /// Disposes of RTCPeerConnection. /// + /// + /// `Dispose` method releases resources used by the `RTCPeerConnection`. + /// This method closes the current peer connection and disposes of all transceivers. + /// + /// + /// + /// public void Dispose() { if (this.disposed) @@ -103,10 +233,10 @@ private void DisposeAllTransceivers() /// enum. /// /// - /// - /// var peerConnection = new RTCPeerConnection(configuration); - /// var iceConnectionState = peerConnection.IceConnectionState; - /// + /// /// /// public RTCIceConnectionState IceConnectionState => NativeMethods.PeerConnectionIceConditionState(GetSelfOrThrow()); @@ -117,10 +247,10 @@ private void DisposeAllTransceivers() /// enum. /// /// - /// - /// var peerConnection = new RTCPeerConnection(configuration); - /// var connectionState = peerConnection.ConnectionState; - /// + /// /// /// public RTCPeerConnectionState ConnectionState => NativeMethods.PeerConnectionState(GetSelfOrThrow()); @@ -131,68 +261,105 @@ private void DisposeAllTransceivers() /// enum. /// /// - /// - /// var peerConnection = new RTCPeerConnection(configuration); - /// var signalingState = peerConnection.SignalingState; - /// + /// /// /// public RTCSignalingState SignalingState => NativeMethods.PeerConnectionSignalingState(GetSelfOrThrow()); /// - /// + /// RTCIceGatheringState value that describes the overall ICE gathering state for the RTCPeerConnection. /// public RTCIceGatheringState GatheringState => NativeMethods.PeerConnectionIceGatheringState(GetSelfOrThrow()); /// - /// Returns array of objects each of which represents one RTP receiver. + /// Returns array of objects each of which represents one RTP receiver. /// + /// + /// `GetReceivers` method returns an array of `RTCRtpReceiver` objects, each of which represents one RTP receiver. + /// /// - /// - /// var senders = peerConnection.GetReceivers(); - /// + /// receivers = peerConnection.GetReceivers(); + /// ]]> /// - /// Array of the senders + /// An array of `RTCRtpReceiver` objects, one for each track on the connection. /// /// public IEnumerable GetReceivers() { IntPtr buf = WebRTC.Context.PeerConnectionGetReceivers(GetSelfOrThrow(), out ulong length); +#if !UNITY_WEBGL return WebRTC.Deserialize(buf, (int)length, CreateReceiver); +#else + var arr = NativeMethods.ptrToIntPtrArray(buf); + return WebRTC.Deserialize(arr, ptr => new RTCRtpReceiver(ptr, this)); +#endif } /// - /// Returns array of objects each of which represents one RTP sender. + /// Returns array of objects each of which represents one RTP sender. /// + /// + /// `GetSenders` method returns an array of `RTCRtpSender` objects, each of which represents the RTP sender responsible for transmitting one track's data. + /// /// - /// - /// var senders = peerConnection.GetSenders(); - /// + /// /// - /// Array of the receivers + /// An array of `RTCRtpSender` objects, one for each track on the connection. /// /// public IEnumerable GetSenders() { var buf = WebRTC.Context.PeerConnectionGetSenders(GetSelfOrThrow(), out ulong length); +#if !UNITY_WEBGL return WebRTC.Deserialize(buf, (int)length, CreateSender); +#else + var arr = NativeMethods.ptrToIntPtrArray(buf); + return WebRTC.Deserialize(arr, ptr => new RTCRtpSender(ptr, this)); +#endif } /// - /// Returns array of objects each of which represents one RTP transceiver. + /// Returns array of objects each of which represents one RTP transceiver. /// + /// + /// `GetTransceivers` method returns an array of the `RTCRtpTransceiver` objects being used to send and receive data on the connection. + /// /// - /// - /// var transceivers = peerConnection.GetTransceivers(); - /// + /// /// - /// Array of the transceivers + /// An array of the `RTCRtpTransceiver` objects representing the transceivers handling sending and receiving all media on the `RTCPeerConnection`. /// /// public IEnumerable GetTransceivers() { var buf = WebRTC.Context.PeerConnectionGetTransceivers(GetSelfOrThrow(), out ulong length); +#if !UNITY_WEBGL return WebRTC.Deserialize(buf, (int)length, CreateTransceiver); +#else + var arr = NativeMethods.ptrToIntPtrArray(buf); + return WebRTC.Deserialize(arr, ptr => new RTCRtpTransceiver(ptr, this)); +#endif } RTCRtpReceiver CreateReceiver(IntPtr ptr) @@ -212,50 +379,47 @@ RTCRtpTransceiver CreateTransceiver(IntPtr ptr) /// - /// This property is delegate to be called when the is changed. + /// Delegate to be called when the IceConnectionState is changed. /// - /// A delegate containing . + /// A delegate containing . /// - /// - /// peerConnection.OnIceConnectionChange = iceConnectionState => - /// { - /// ... - /// }; - /// + /// + /// { + /// ... + /// }; + /// ]]> /// /// public DelegateOnIceConnectionChange OnIceConnectionChange { get; set; } /// - /// + /// Delegate to be called after a new track has been added to an RTCRtpReceiver which is part of the connection. /// public DelegateOnConnectionStateChange OnConnectionStateChange { get; set; } /// - /// + /// Delegate to be called when the state of the ICE candidate gathering process changes. /// - /// public DelegateOnIceGatheringStateChange OnIceGatheringStateChange { get; set; } /// - /// + ///  Delegate to be called when a new RTCIceCandidate is identified and added to the local peer, when all candidates for a specific generation are identified and added, and when the ICE gathering on all transports is complete. /// - /// public DelegateOnIceCandidate OnIceCandidate { get; set; } /// - /// + /// Delegate to be called when an RTCDataChannel has been added to the connection, as a result of the remote peer calling RTCPeerConnection.CreateDataChannel. /// - /// public DelegateOnDataChannel OnDataChannel { get; set; } /// - /// + /// Delegate to be called when negotiation of the connection through the signaling channel is required. /// public DelegateOnNegotiationNeeded OnNegotiationNeeded { get; set; } /// - /// + /// Delegate to be called after a new track has been added to an RTCRtpReceiver which is part of the connection. /// /// public DelegateOnTrack OnTrack { get; set; } @@ -271,7 +435,11 @@ internal IntPtr GetSelfOrThrow() } [AOT.MonoPInvokeCallback(typeof(DelegateNativeOnIceCandidate))] +#if !UNITY_WEBGL static void PCOnIceCandidate(IntPtr ptr, string sdp, string sdpMid, int sdpMlineIndex) +#else + static void PCOnIceCandidate(IntPtr ptr, IntPtr iceCandidatePtr, string sdp, string sdpMid, int sdpMlineIndex) +#endif { WebRTC.Sync(ptr, () => { @@ -283,7 +451,11 @@ static void PCOnIceCandidate(IntPtr ptr, string sdp, string sdpMid, int sdpMline sdpMid = sdpMid, sdpMLineIndex = sdpMlineIndex }; +#if !UNITY_WEBGL var candidate = new RTCIceCandidate(options); +#else + var candidate = new RTCIceCandidate(options, iceCandidatePtr); +#endif connection.OnIceCandidate?.Invoke(candidate); } }); @@ -379,20 +551,21 @@ static void PCOnRemoveTrack(IntPtr ptr, IntPtr receiverPtr) } /// - /// Returns an object which indicates the current configuration - /// of the . + /// Returns an object which indicates the current configuration of the RTCPeerConnection. /// - /// An object describing the 's - /// current configuration. + /// + /// `GetConfiguration` method returns an object which indicates the current configuration of the `RTCPeerConnection`. + /// + /// An object describing the 's current configuration. /// - /// - /// var configuration = myPeerConnection.GetConfiguration(); - /// if(configuration.urls.length == 0) - /// { - /// configuration.urls = new[] {"stun:stun.l.google.com:19302"}; - /// } - /// myPeerConnection.SetConfiguration(configuration); - /// + /// /// /// public RTCConfiguration GetConfiguration() @@ -404,34 +577,38 @@ public RTCConfiguration GetConfiguration() } /// - /// This method sets the current configuration of the - /// This lets you change the ICE servers used by the connection - /// and which transport policies to use. + /// Sets the current configuration of the RTCPeerConnection. /// - /// The changes are not additive; instead, - /// the new values completely replace the existing ones. + /// + /// `SetConfiguration` method sets the current configuration of the connection based on the values included in the specified object. + /// This lets you change the ICE servers used by the connection and which transport policies to use. + /// + /// + /// `RTCConfiguration` object which provides the options to be set. + /// The changes are not additive; instead, the new values completely replace the existing ones. + /// /// Error code. /// - /// - /// var configuration = new RTCConfiguration - /// { - /// iceServers = new[] - /// { - /// new RTCIceServer + /// + /// ]]> /// /// public RTCErrorType SetConfiguration(ref RTCConfiguration configuration) @@ -442,8 +619,16 @@ public RTCErrorType SetConfiguration(ref RTCConfiguration configuration) } /// - /// This constructor creates an instance of peer connection with a default configuration. + /// Creates an instance of peer connection with a default configuration. /// + /// + /// `RTCPeerConnection` constructor creates an instance of peer connection with a default configuration. + /// + /// + /// + /// /// public RTCPeerConnection() { @@ -458,10 +643,18 @@ public RTCPeerConnection() } /// - /// This constructor creates an instance of peer connection with a configuration provided by user. - /// An object providing options to configure the new connection. + /// Creates an instance of peer connection with a configuration provided by user. /// - /// + /// + /// `RTCPeerConnection` constructor creates an instance of peer connection with a default configuration. + /// An object providing options to configure the new connection. + /// + /// + /// + /// + /// `RTCConfiguration` object to configure the new connection. /// public RTCPeerConnection(ref RTCConfiguration configuration) { @@ -490,16 +683,34 @@ void InitCallback() } /// - /// + /// Requests that ICE candidate gathering be redone on both ends of the connection. /// + /// + /// `RestartIce` method requests that ICE candidate gathering be redone on both ends of the connection. + /// After `RestartIce` is called, the offer returned by the next call to `CreateOffer` automatically configured to trigger ICE restart on both the local and remote peers. + /// This method triggers an `OnNegotiationNeeded` event. + /// + /// + /// + /// public void RestartIce() { NativeMethods.PeerConnectionRestartIce(GetSelfOrThrow()); } /// - /// + /// Closes the current peer connection. /// + /// + /// `Close` method closes the current peer connection. + /// + /// + /// + /// /// public void Close() { @@ -507,20 +718,42 @@ public void Close() } /// - /// + /// Adds a new media track to the set of tracks which is transmitted to the other peer. /// - /// - /// - /// + /// + /// `AddTrack` method adds a new media track to the set of tracks which is transmitted to the other peer. + /// Adding a track to a connection triggers renegotiation by firing an `OnNegotiationNeeded` event. + /// + /// `MediaStreamTrack` object representing the media track to add to the peer connection. + /// + /// Local `MediaStream` object to which the track should be added. + /// If this is not specified, then the track is **streamless**. + /// + /// `RTCRtpSender` object which is used to transmit the media data. + /// + /// + /// /// public RTCRtpSender AddTrack(MediaStreamTrack track, MediaStream stream = null) { if (track == null) throw new ArgumentNullException("track is null."); +#if !UNITY_WEBGL var streamId = stream?.Id; RTCErrorType error = NativeMethods.PeerConnectionAddTrack( GetSelfOrThrow(), track.GetSelfOrThrow(), streamId, out var ptr); +#else + var streamPtr = stream == null ? IntPtr.Zero : stream.GetSelfOrThrow(); + IntPtr buf = NativeMethods.PeerConnectionAddTrack(GetSelfOrThrow(), track.GetSelfOrThrow(), streamPtr); + var arr = NativeMethods.ptrToIntPtrArray(buf); + RTCErrorType error = (RTCErrorType) arr[0]; + var ptr = arr[1]; +#endif if (error != RTCErrorType.None) throw new InvalidOperationException($"error occurred :{error}"); cacheTracks.Add(track); @@ -528,10 +761,19 @@ public RTCRtpSender AddTrack(MediaStreamTrack track, MediaStream stream = null) } /// - /// + /// Tells the local end of the connection to stop sending media from the specified track. /// - /// - /// + /// + /// `RemoveTrack` method tells the local end of the connection to stop sending media from the specified track, without actually removing the corresponding `RTCRtpSender` from the list of senders. + /// If the track is already stopped, or is not in the connection's senders list, this method has no effect. + /// + /// `RTCRtpSender` object specifying the sender to remove from the connection. + /// `RTCErrorType` value. + /// + /// + /// /// public RTCErrorType RemoveTrack(RTCRtpSender sender) { @@ -540,11 +782,22 @@ public RTCErrorType RemoveTrack(RTCRtpSender sender) } /// - /// + /// Creates a new RTCRtpTransceiver and adds it to the set of transceivers associated with the RTCPeerConnection. /// - /// - /// - /// + /// + /// `AddTransceiver` method creates a new `RTCRtpTransceiver` instance and adds it to the set of transceivers associated with the `RTCPeerConnection`. + /// Each transceiver represents a bidirectional stream, with both an `RTCRtpSender` and an `RTCRtpReceiver` associated with it. + /// + /// `MediaStreamTrack` object to associate with the transceiver. + /// `RTCRtpTransceiverInit` object for specifying options when creating the new transceiver. + /// `RTCRtpTransceiver` object which is used to exchange the media data. + /// + /// + /// public RTCRtpTransceiver AddTransceiver(MediaStreamTrack track, RTCRtpTransceiverInit init = null) { if (track == null) @@ -555,11 +808,22 @@ public RTCRtpTransceiver AddTransceiver(MediaStreamTrack track, RTCRtpTransceive } /// - /// + /// Creates a new RTCRtpTransceiver and adds it to the set of transceivers associated with the RTCPeerConnection. /// - /// - /// - /// + /// + /// `AddTransceiver` method creates a new `RTCRtpTransceiver` instance and adds it to the set of transceivers associated with the `RTCPeerConnection`. + /// Each transceiver represents a bidirectional stream, with both an `RTCRtpSender` and an `RTCRtpReceiver` associated with it. + /// + /// `TrackKind` value which is used as the kind of the receiver's track. + /// `RTCRtpTransceiverInit` object for specifying options when creating the new transceiver. + /// `RTCRtpTransceiver` object which is used to exchange the media data. + /// + /// + /// public RTCRtpTransceiver AddTransceiver(TrackKind kind, RTCRtpTransceiverInit init = null) { IntPtr ptr = PeerConnectionAddTransceiverWithType( @@ -568,10 +832,24 @@ public RTCRtpTransceiver AddTransceiver(TrackKind kind, RTCRtpTransceiverInit in } /// - /// + /// Adds a new remote candidate to the connection's remote description. /// - /// - /// + /// + /// `AddIceCandidate` method adds a new remote ICE candidate to the connection's remote description, which describes the current state of the remote end of the connection. + /// + /// + /// `RTCIceCandidate` object that describes the properties of the new remote candidate. + /// If the value is null, the added ICE candidate is an "end-of-candidates" indicator. + /// + /// `true` if the candidate has been successfully added to the remote peer's description by the ICE agent. + /// + /// + /// { + /// bool result = otherPeerConnection.AddIceCandidate(candidate); + /// } + /// ]]> + /// public bool AddIceCandidate(RTCIceCandidate candidate) { return NativeMethods.PeerConnectionAddIceCandidate( @@ -579,11 +857,27 @@ public bool AddIceCandidate(RTCIceCandidate candidate) } /// - /// Create an SDP (Session Description Protocol) offer to start a new connection - /// to a remote peer. + /// Create an SDP (Session Description Protocol) offer to start a new connection to a remote peer. /// - /// - /// + /// + /// `CreateOffer` initiates the creation of an SDP offer for the purpose of starting a new WebRTC connection to a remote peer. + /// The SDP offer contains details about `MediaStreamTrack` objects, supported codecs and options, and ICE candidates. + /// + /// `RTCOfferAnswerOptions` object providing the options requested for the offer. + /// `RTCSessionDescriptionAsyncOperation` object containing `RTCSessionDescription` object. + /// + /// + /// /// public RTCSessionDescriptionAsyncOperation CreateOffer(ref RTCOfferAnswerOptions options) { @@ -593,9 +887,26 @@ public RTCSessionDescriptionAsyncOperation CreateOffer(ref RTCOfferAnswerOptions } /// - /// + /// Create an SDP (Session Description Protocol) offer to start a new connection to a remote peer. /// - /// + /// + /// `CreateOffer` initiates the creation of an SDP offer for the purpose of starting a new WebRTC connection to a remote peer. + /// The SDP offer contains details about `MediaStreamTrack` objects, supported codecs and options, and ICE candidates. + /// + /// `RTCSessionDescriptionAsyncOperation` object containing `RTCSessionDescription` object. + /// + /// + /// public RTCSessionDescriptionAsyncOperation CreateOffer() { CreateSessionDescriptionObserver observer = @@ -604,11 +915,27 @@ public RTCSessionDescriptionAsyncOperation CreateOffer() } /// - /// Create an SDP (Session Description Protocol) answer to start a new connection - /// to a remote peer. + /// Create an SDP (Session Description Protocol) answer to start a new connection to a remote peer. /// - /// - /// + /// + /// `CreateAnswer` method creates an SDP answer to an offer received from a remote peer during the offer/answer negotiation of a WebRTC connection. + /// The SDP answer contains details about the session's media, supported codecs, and ICE candidates. + /// + /// `RTCOfferAnswerOptions` object providing options requested for the answer. + /// `RTCSessionDescriptionAsyncOperation` object containing `RTCSessionDescription` object. + /// + /// + /// public RTCSessionDescriptionAsyncOperation CreateAnswer(ref RTCOfferAnswerOptions options) { CreateSessionDescriptionObserver observer = @@ -617,9 +944,26 @@ public RTCSessionDescriptionAsyncOperation CreateAnswer(ref RTCOfferAnswerOption } /// - /// + /// Create an SDP (Session Description Protocol) answer to start a new connection to a remote peer. /// - /// + /// + /// `CreateAnswer` method creates an SDP answer to an offer received from a remote peer during the offer/answer negotiation of a WebRTC connection. + /// The SDP answer contains details about the session's media, supported codecs, and ICE candidates. + /// + /// `RTCSessionDescriptionAsyncOperation` object containing `RTCSessionDescription` object. + /// + /// + /// public RTCSessionDescriptionAsyncOperation CreateAnswer() { CreateSessionDescriptionObserver observer = @@ -628,12 +972,22 @@ public RTCSessionDescriptionAsyncOperation CreateAnswer() } /// - /// Creates a new data channel related the remote peer. + /// Creates a new data channel related the remote peer. /// - /// A string for the data channel. - /// This string may be checked by . - /// A struct provides configuration options for the data channel. - /// A new data channel. + /// + /// `CreateDataChannel` method creates a new data channel with the remote peer for transmitting any type of data. + /// + /// + /// A string for the data channel. + /// This string may be checked by . + /// + /// A struct provides configuration options for the data channel. + /// A new data channel. + /// + /// + /// public RTCDataChannel CreateDataChannel(string label, RTCDataChannelInit options = null) { RTCDataChannelInitInternal _options = @@ -646,21 +1000,35 @@ public RTCDataChannel CreateDataChannel(string label, RTCDataChannelInit options } /// - /// This method changes the session description - /// of the local connection to negotiate with other connections. + /// Changes the session description of the local connection to negotiate with other connections. /// - /// + /// + /// `SetLocalDescription` method changes the local description associated with the connection, specifying the properties of the local end of the connection, including the media format. + /// + /// `RTCSessionDescription` object which specifies the configuration to be applied to the local end of the connection. /// - /// An AsyncOperation which resolves with an - /// object providing a description of the session. + /// An AsyncOperation which resolves with an object providing a description of the session. /// + /// + /// + /// /// - /// Thrown when an argument has an invalid value. - /// For example, when passed the sdp which is null or empty. + /// Thrown when an argument has an invalid value. + /// For example, when passed the sdp which is null or empty. /// /// - /// Thrown when an argument has an invalid value. - /// For example, when passed the sdp which is not be able to parse. + /// Thrown when an argument has an invalid value. + /// For example, when passed the sdp which is not be able to parse. /// /// public RTCSetSessionDescriptionAsyncOperation SetLocalDescription( @@ -675,9 +1043,33 @@ public RTCSetSessionDescriptionAsyncOperation SetLocalDescription( } /// - /// + /// Changes the session description of the local connection to negotiate with other connections. /// - /// + /// + /// `SetLocalDescription` method automatically adjusts the local description associated with the connection, specifying the properties of the local end of the connection, including the media format. + /// + /// + /// An AsyncOperation which resolves with an object providing a description of the session. + /// + /// + /// + /// { + /// StartCoroutine(NegotiationProcess()); + /// } + /// + /// IEnumerator NegotiationProcess() + /// { + /// RTCSetSessionDescriptionAsyncOperation asyncOperation = peerConnection.SetLocalDescription(ref description); + /// yield return asyncOperation; + /// + /// if (asyncOperation.IsError) + /// { + /// Debug.LogError("Failed to set local description: " + asyncOperation.Error.message); + /// } + /// } + /// ]]> + /// public RTCSetSessionDescriptionAsyncOperation SetLocalDescription() { SetSessionDescriptionObserver observer = @@ -686,21 +1078,35 @@ public RTCSetSessionDescriptionAsyncOperation SetLocalDescription() } /// - /// This method changes the session description - /// of the remote connection to negotiate with local connections. + /// This method changes the session description of the remote connection to negotiate with local connections. /// - /// + /// + /// `SetRemoteDescription` method changes the specified session description as the remote peer's current offer or answer, specifying the properties of the remote end of the connection, including the media format. + /// + /// `RTCSessionDescription` object which specifies the remote peer's current offer or answer. /// - /// An AsyncOperation which resolves with an - /// object providing a description of the session. + /// An AsyncOperation which resolves with an object providing a description of the session. /// + /// + /// + /// /// - /// Thrown when an argument has an invalid value. - /// For example, when passed the sdp which is null or empty. + /// Thrown when an argument has an invalid value. + /// For example, when passed the sdp which is null or empty. /// /// - /// Thrown when an argument has an invalid value. - /// For example, when passed the sdp which is not be able to parse. + /// Thrown when an argument has an invalid value. + /// For example, when passed the sdp which is not be able to parse. /// /// public RTCSetSessionDescriptionAsyncOperation SetRemoteDescription( @@ -715,44 +1121,58 @@ public RTCSetSessionDescriptionAsyncOperation SetRemoteDescription( } /// - /// Returns an AsyncOperation which resolves with data providing statistics. + /// Returns an AsyncOperation which resolves with data providing statistics. /// + /// + /// `GetStats` method returns a promise which resolves with data providing statistics about either the overall connection or about the specified `MediaStreamTrack`. + /// /// - /// An AsyncOperation which resolves with an - /// object providing connection statistics. + /// An AsyncOperation which resolves with an object providing connection statistics. /// /// - /// - /// // Already instantiated peerConnection as RTCPeerConnection. - /// var operation = peerConnection.GetStats(); - /// yield return operation; + /// + /// if (!operation.IsError) + /// { + /// RTCStatsReport report = operation.Value; + /// foreach (RTCStats stat in report.Stats.Values) + /// { + /// Debug.Log(stat.Type.ToString()); + /// } + /// } + /// ]]> /// /// public RTCStatsReportAsyncOperation GetStats() { +#if !UNITY_WEBGL RTCStatsCollectorCallback callback = NativeMethods.PeerConnectionGetStats(GetSelfOrThrow()); return GetStats(callback); +#else + throw new NotImplementedException(); +#endif } internal RTCStatsReportAsyncOperation GetStats(RTCRtpSender sender) { +#if !UNITY_WEBGL RTCStatsCollectorCallback callback = NativeMethods.PeerConnectionSenderGetStats(GetSelfOrThrow(), sender.self); return GetStats(callback); +#else + throw new NotImplementedException(); +#endif } internal RTCStatsReportAsyncOperation GetStats(RTCRtpReceiver receiver) { +#if !UNITY_WEBGL RTCStatsCollectorCallback callback = NativeMethods.PeerConnectionReceiverGetStats(GetSelfOrThrow(), receiver.self); return GetStats(callback); +#else + throw new NotImplementedException(); +#endif } RTCStatsReportAsyncOperation GetStats(RTCStatsCollectorCallback callback) @@ -763,6 +1183,14 @@ RTCStatsReportAsyncOperation GetStats(RTCStatsCollectorCallback callback) return new RTCStatsReportAsyncOperation(callback); } + /// + /// Boolean value that indicates whether the remote peer can accept trickled ICE candidates. + /// + /// + /// When the value is true, the remote peer can accept trickled ICE candidates. + /// When the value is false, the remote peer cannot accept trickled ICE candidates. + /// When the value is null, the remote peer has not been established. + /// public bool? CanTrickleIceCandidates { get @@ -773,97 +1201,153 @@ public bool? CanTrickleIceCandidates } /// - /// + /// RTCSessionDescription object that describes the session for the local end of the RTCPeerConnection. /// public RTCSessionDescription LocalDescription { get { RTCSessionDescription desc = default; +#if !UNITY_WEBGL if (NativeMethods.PeerConnectionGetLocalDescription(GetSelfOrThrow(), ref desc)) { return desc; } +#else + string ret = NativeMethods.PeerConnectionGetLocalDescription(GetSelfOrThrow()); + if(!"false".Equals(ret)) + { + desc = JsonConvert.DeserializeObject(ret); + return desc; + } +#endif throw new InvalidOperationException("LocalDescription is not exist"); } } /// - /// + /// RTCSessionDescription object that describes the session (which includes configuration and media information) for the remote end of the connection. /// public RTCSessionDescription RemoteDescription { get { RTCSessionDescription desc = default; +#if !UNITY_WEBGL if (NativeMethods.PeerConnectionGetRemoteDescription(GetSelfOrThrow(), ref desc)) { return desc; } +#else + string ret = NativeMethods.PeerConnectionGetRemoteDescription(GetSelfOrThrow()); + if(!"false".Equals(ret)) + { + desc = JsonConvert.DeserializeObject(ret); + return desc; + } +#endif throw new InvalidOperationException("RemoteDescription is not exist"); } } /// - /// + ///  RTCSessionDescription object describing the local end of the connection from the last successful negotiation with a remote peer. + /// It also includes ICE candidates generated by the ICE agent since the initial offer or answer was first created. /// public RTCSessionDescription CurrentLocalDescription { get { RTCSessionDescription desc = default; +#if !UNITY_WEBGL if (NativeMethods.PeerConnectionGetCurrentLocalDescription(GetSelfOrThrow(), ref desc)) { return desc; } +#else + string ret = NativeMethods.PeerConnectionGetCurrentLocalDescription(GetSelfOrThrow()); + if(!"false".Equals(ret)) + { + desc = JsonConvert.DeserializeObject(ret); + return desc; + } +#endif throw new InvalidOperationException("CurrentLocalDescription is not exist"); } } /// - /// + /// RTCSessionDescription object describing the remote end of the connection from the last successful negotiation with a remote peer. + /// It also includes ICE candidates generated by the ICE agent since the initial offer or answer was first created. /// public RTCSessionDescription CurrentRemoteDescription { get { RTCSessionDescription desc = default; +#if !UNITY_WEBGL if (NativeMethods.PeerConnectionGetCurrentRemoteDescription(GetSelfOrThrow(), ref desc)) { return desc; } +#else + string ret = NativeMethods.PeerConnectionGetCurrentRemoteDescription(GetSelfOrThrow()); + if(!"false".Equals(ret)) + { + desc = JsonConvert.DeserializeObject(ret); + return desc; + } +#endif throw new InvalidOperationException("CurrentRemoteDescription is not exist"); } } /// - /// + /// RTCSessionDescription object that describes a pending configuration change for the local end of the connection. /// public RTCSessionDescription PendingLocalDescription { get { RTCSessionDescription desc = default; +#if !UNITY_WEBGL if (NativeMethods.PeerConnectionGetPendingLocalDescription(GetSelfOrThrow(), ref desc)) { return desc; } +#else + string ret = NativeMethods.PeerConnectionGetPendingLocalDescription(GetSelfOrThrow()); + if(!"false".Equals(ret)) + { + desc = JsonConvert.DeserializeObject(ret); + return desc; + } +#endif throw new InvalidOperationException("PendingLocalDescription is not exist"); } } /// - /// + /// RTCSessionDescription object that describes a pending configuration change for the remote end of the connection. /// public RTCSessionDescription PendingRemoteDescription { get { RTCSessionDescription desc = default; +#if !UNITY_WEBGL if (NativeMethods.PeerConnectionGetPendingRemoteDescription(GetSelfOrThrow(), ref desc)) { return desc; } +#else + string ret = NativeMethods.PeerConnectionGetPendingRemoteDescription(GetSelfOrThrow()); + if(!"false".Equals(ret)) + { + desc = JsonConvert.DeserializeObject(ret); + return desc; + } +#endif throw new InvalidOperationException("PendingRemoteDescription is not exist"); } } @@ -943,33 +1427,45 @@ internal void RemoveObserver(SetSessionDescriptionObserver observer) static SetSessionDescriptionObserver PeerConnectionSetLocalDescription( IntPtr ptr, ref RTCSessionDescription desc, out RTCError error) { +#if !UNITY_WEBGL IntPtr ptrError = IntPtr.Zero; SetSessionDescriptionObserver observer = NativeMethods.PeerConnectionSetLocalDescription(ptr, ref desc, out var errorType, ref ptrError); string message = ptrError != IntPtr.Zero ? ptrError.AsAnsiStringWithFreeMem() : null; error = new RTCError { errorType = errorType, message = message }; return observer; +#else + throw new NotImplementedException(); +#endif } static SetSessionDescriptionObserver PeerConnectionSetLocalDescription(IntPtr ptr, out RTCError error) { +#if !UNITY_WEBGL IntPtr ptrError = IntPtr.Zero; SetSessionDescriptionObserver observer = NativeMethods.PeerConnectionSetLocalDescriptionWithoutDescription(ptr, out var errorType, ref ptrError); string message = ptrError != IntPtr.Zero ? ptrError.AsAnsiStringWithFreeMem() : null; error = new RTCError { errorType = errorType, message = message }; return observer; +#else + throw new NotImplementedException(); +#endif } static SetSessionDescriptionObserver PeerConnectionSetRemoteDescription( IntPtr ptr, ref RTCSessionDescription desc, out RTCError error) { +#if !UNITY_WEBGL IntPtr ptrError = IntPtr.Zero; SetSessionDescriptionObserver observer = NativeMethods.PeerConnectionSetRemoteDescription(ptr, ref desc, out var errorType, ref ptrError); string message = ptrError != IntPtr.Zero ? ptrError.AsAnsiStringWithFreeMem() : null; error = new RTCError { errorType = errorType, message = message }; return observer; +#else + throw new NotImplementedException(); +#endif } static IntPtr PeerConnectionAddTransceiver(IntPtr pc, IntPtr track, RTCRtpTransceiverInit init) diff --git a/Runtime/Scripts/RTCRtpReceiver.cs b/Runtime/Scripts/RTCRtpReceiver.cs index a2239a0000..777088f3cc 100644 --- a/Runtime/Scripts/RTCRtpReceiver.cs +++ b/Runtime/Scripts/RTCRtpReceiver.cs @@ -1,3 +1,4 @@ +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Runtime.InteropServices; @@ -5,7 +6,7 @@ namespace Unity.WebRTC { /// - /// + /// /// public class RTCRtpContributingSource { @@ -88,24 +89,33 @@ public override void Dispose() } if (self != IntPtr.Zero && !WebRTC.Context.IsNull) { +#if UNITY_WEBGL + NativeMethods.DeleteReceiver(self); +#endif WebRTC.Table.Remove(self); } base.Dispose(); } /// - /// + /// /// /// /// public static RTCRtpCapabilities GetCapabilities(TrackKind kind) { + +#if !UNITY_WEBGL WebRTC.Context.GetReceiverCapabilities(kind, out IntPtr ptr); RTCRtpCapabilitiesInternal capabilitiesInternal = Marshal.PtrToStructure(ptr); RTCRtpCapabilities capabilities = new RTCRtpCapabilities(capabilitiesInternal); Marshal.FreeHGlobal(ptr); return capabilities; +#else + return WebRTC.Context.GetReceiverCapabilities(kind); +#endif + } /// diff --git a/Runtime/Scripts/RTCRtpSender.cs b/Runtime/Scripts/RTCRtpSender.cs index 31820e9be3..d0572ff54a 100644 --- a/Runtime/Scripts/RTCRtpSender.cs +++ b/Runtime/Scripts/RTCRtpSender.cs @@ -1,12 +1,25 @@ using System; using System.Runtime.InteropServices; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + using UnityEngine; namespace Unity.WebRTC { /// - /// + /// Provides the ability to control and access to details on encoding and sending a MediaStreamTrack to a remote peer. /// + /// + /// `RTCRtpSender` class allows customization of media encoding and transmission to a remote peer. + /// It provides access to the device's media capabilities and supports sending DTMF tones for telephony interactions. + /// + /// + /// + /// public class RTCRtpSender : RefCountedObject { private RTCPeerConnection peer; @@ -19,16 +32,27 @@ internal RTCRtpSender(IntPtr ptr, RTCPeerConnection peer) : base(ptr) } /// - /// + /// Finalizer for RTCRtpSender. /// + /// + /// Ensures that resources are released by calling the `Dispose` method + /// ~RTCRtpSender() { this.Dispose(); } /// - /// + /// Disposes of RTCRtpSender. /// + /// + /// `Dispose` method disposes of the `RTCRtpSender` and releases the associated resources. + /// + /// + /// + /// public override void Dispose() { if (this.disposed) @@ -37,37 +61,80 @@ public override void Dispose() } if (self != IntPtr.Zero && !WebRTC.Context.IsNull) { +#if UNITY_WEBGL + NativeMethods.DeleteSender(self); +#endif WebRTC.Table.Remove(self); } base.Dispose(); } /// - /// + /// Provides a `RTCRtpCapabilities` object describing the codec and header extension capabilities. /// - /// - /// + /// + /// `GetCapabilities` method provides a `RTCRtpCapabilities` object that describes the codec and header extension capabilities supported by `RTCRtpSender`. + /// + /// `TrackKind` value indicating the type of media. + /// `RTCRtpCapabilities` object contains an array of `RTCRtpCodecCapability` objects. + /// + /// + /// public static RTCRtpCapabilities GetCapabilities(TrackKind kind) { + +#if !UNITY_WEBGL WebRTC.Context.GetSenderCapabilities(kind, out IntPtr ptr); RTCRtpCapabilitiesInternal capabilitiesInternal = Marshal.PtrToStructure(ptr); RTCRtpCapabilities capabilities = new RTCRtpCapabilities(capabilitiesInternal); Marshal.FreeHGlobal(ptr); return capabilities; +#else + return WebRTC.Context.GetSenderCapabilities(kind); +#endif } /// - /// + /// Asynchronously requests statistics about outgoing traffic on the RTCPeerConnection associated with the RTCRtpSender. /// - /// + /// + /// `GetStats` method asynchronously requests an `RTCStatsReport` containing statistics about the outgoing traffic for the `RTCPeerConnection` associated with the `RTCRtpSender`. + /// + /// `RTCStatsReportAsyncOperation` object containing `RTCStatsReport` object. + /// + /// + /// str + next.Key + ":" + (next.Value == null ? string.Empty : next.Value.ToString()) + "\n"); + /// Debug.Log(statsText); + /// statsReport.Dispose(); + /// } + /// ]]> + /// public RTCStatsReportAsyncOperation GetStats() { return peer.GetStats(this); } /// - /// + /// MediaStreamTrack managed by RTCRtpSender. If it is null, no transmission occurs. /// public MediaStreamTrack Track { @@ -80,6 +147,10 @@ public MediaStreamTrack Track } } + /// + /// RTCRtpScriptTransform used to insert a transform stream in a worker thread into the sender pipeline, + /// enabling transformations on encoded video and audio frames after output by a codec but before transmission. + /// public RTCRtpTransform Transform { set @@ -89,7 +160,11 @@ public RTCRtpTransform Transform // cache reference transform = value; +#if !UNITY_WEBGL NativeMethods.SenderSetTransform(GetSelfOrThrow(), value.self); +#else + throw new NotImplementedException(); +#endif } get { @@ -98,7 +173,7 @@ public RTCRtpTransform Transform } /// - /// + /// Indicates if the video track's framerate is synchronized with the application's framerate. /// public bool SyncApplicationFramerate { @@ -135,23 +210,65 @@ public bool SyncApplicationFramerate } /// - /// + /// Retrieves the current configuration of the RTCRtpSender. /// - /// + /// + /// `GetParameters` method retrieves `RTCRtpSendParameters` object describing the current configuration of the `RTCRtpSender`. + /// + /// `RTCRtpSendParameters` object containing the current configuration of the `RTCRtpSender`. + /// + /// + /// + /// public RTCRtpSendParameters GetParameters() { + +#if !UNITY_WEBGL NativeMethods.SenderGetParameters(GetSelfOrThrow(), out var ptr); RTCRtpSendParametersInternal parametersInternal = Marshal.PtrToStructure(ptr); RTCRtpSendParameters parameters = new RTCRtpSendParameters(ref parametersInternal); Marshal.FreeHGlobal(ptr); return parameters; +#else + string json = NativeMethods.SenderGetParameters(self); + return JsonConvert.DeserializeObject(json); +#endif } /// - /// + /// Updates the configuration of the sender's track. /// - /// - /// + /// + /// `SetParameters` method updates the configuration of the sender's `MediaStreamTrack` + /// by applying changes the RTP transmission and the encoding parameters for a specific outgoing media on the connection. + /// + /// + /// A `RTCRtpSendParameters` object previously obtained by calling the sender's `GetParameters`, + /// includes desired configuration changes and potential codecs for encoding the sender's track. + /// + /// `RTCError` value. + /// + /// + /// + /// public RTCError SetParameters(RTCRtpSendParameters parameters) { if (Track is VideoStreamTrack videoTrack) @@ -173,19 +290,40 @@ public RTCError SetParameters(RTCRtpSendParameters parameters) } } +#if !UNITY_WEBGL parameters.CreateInstance(out RTCRtpSendParametersInternal instance); IntPtr ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(instance)); Marshal.StructureToPtr(instance, ptr, false); RTCErrorType type = NativeMethods.SenderSetParameters(GetSelfOrThrow(), ptr); Marshal.FreeCoTaskMem(ptr); return new RTCError { errorType = type }; +#else + string json = JsonConvert.SerializeObject(parameters, Formatting.None, new JsonSerializerSettings{NullValueHandling = NullValueHandling.Ignore}); + + //TODO Get correct RTCErrorType from jslib. + NativeMethods.SenderSetParameters(self, json); + return new RTCError { errorType = RTCErrorType.None }; +#endif } /// - /// + /// Replaces the current source track with a new MediaStreamTrack. /// - /// - /// + /// + /// `ReplaceTrack` method replaces the track currently being used as the sender's source with a new `MediaStreamTrack`. + /// It is often used to switch between two cameras. + /// + /// + /// A `MediaStreamTrack` to replace the current source track of the `RTCRtpSender`. + /// The new track must be the same type as the current one. + /// + /// `true` if the track has been successfully replaced. + /// + /// + /// public bool ReplaceTrack(MediaStreamTrack track) { IntPtr trackPtr = track?.GetSelfOrThrow() ?? IntPtr.Zero; diff --git a/Runtime/Scripts/RTCRtpTransceiver.cs b/Runtime/Scripts/RTCRtpTransceiver.cs index fc60b10cac..55772cfdb1 100644 --- a/Runtime/Scripts/RTCRtpTransceiver.cs +++ b/Runtime/Scripts/RTCRtpTransceiver.cs @@ -1,10 +1,27 @@ using System; +using System.Runtime.InteropServices; +using Newtonsoft.Json; namespace Unity.WebRTC { /// - /// + /// Describes a permanent pairing of an RTCRtpSender and an RTCRtpReceiver. /// + /// + /// `RTCRtpTransceiver` class is used to represent a permanent pairing of an `RTCRtpSender` and an `RTCRtpReceiver`, along with shared state. + /// + /// + /// + /// + /// public class RTCRtpTransceiver : RefCountedObject { private RTCPeerConnection peer; @@ -16,16 +33,27 @@ internal RTCRtpTransceiver(IntPtr ptr, RTCPeerConnection peer) : base(ptr) } /// - /// + /// Finalizer for RTCRtpTransceiver. /// + /// + /// Ensures that resources are released by calling the `Dispose` method + /// ~RTCRtpTransceiver() { this.Dispose(); } /// - /// + /// Disposes of RTCRtpTransceiver. /// + /// + /// `Dispose` method disposes of the `RTCRtpTransceiver` and releases the associated resources. + /// + /// + /// + /// public override void Dispose() { if (this.disposed) @@ -34,13 +62,18 @@ public override void Dispose() } if (self != IntPtr.Zero && !WebRTC.Context.IsNull) { + +#if UNITY_WEBGL + NativeMethods.DeleteTransceiver(self); +#endif + WebRTC.Table.Remove(self); } base.Dispose(); } /// - /// + /// Specifies the negotiated media ID (mid) that uniquely identifies the pairing of the sender and receiver agreed upon by local and remote peers. /// public string Mid { @@ -54,8 +87,7 @@ public string Mid } /// - /// This is used to set the transceiver's desired direction - /// and will be used in calls to CreateOffer and CreateAnswer. + /// This is used to set the transceiver's desired direction and will be used in calls to CreateOffer and CreateAnswer. /// public RTCRtpTransceiverDirection Direction { @@ -72,25 +104,31 @@ public RTCRtpTransceiverDirection Direction } /// - /// This property indicates the transceiver's current directionality, - /// or null if the transceiver is stopped or has never participated in an exchange of offers and answers. - /// To change the transceiver's directionality, set the value of the property. + /// This property indicates the transceiver's current directionality, + /// or null if the transceiver is stopped or has never participated in an exchange of offers and answers. + /// To change the transceiver's directionality, set the value of the Direction property. /// public RTCRtpTransceiverDirection? CurrentDirection { get { +#if !UNITY_WEBGL if (NativeMethods.TransceiverGetCurrentDirection(GetSelfOrThrow(), out var direction)) { return direction; } return null; +#else + int currentDirection = NativeMethods.TransceiverGetCurrentDirection(GetSelfOrThrow()); + if (currentDirection == -1) return null; + else return (RTCRtpTransceiverDirection) currentDirection; +#endif } } /// - /// + /// RTCRtpReceiver object that handles receiving and decoding incoming media data for the transceiver's stream. /// public RTCRtpReceiver Receiver { @@ -102,7 +140,7 @@ public RTCRtpReceiver Receiver } /// - /// + /// RTCRtpSender object that handles encoding and sending outgoing media data for the transceiver's stream. /// public RTCRtpSender Sender { @@ -114,12 +152,34 @@ public RTCRtpSender Sender } /// - /// + /// Specifies the codecs for decoding received data on this transceiver, arranged in order of decreasing preference. /// - /// - /// + /// + /// `SetCodecPreferences` method sets codec preferences for negotiating data encoding with a remote peer. + /// It requires reordering supported codecs by preference and applying them to influence the negotiation process. + /// When initiating an `RTCPeerConnection`, set codecs before calling `CreateOffer` or `CreateAnswer`. + /// Changing codecs during a session requires a new negotiation but does not automatically trigger the `OnNegotiationNeeded` event. + /// + /// + /// An array of `RTCRtpCodecCapability` objects arranged by preference to determine the codecs used for receiving and sending data streams. + /// If it is empty, the codec configurations are all reset to the defaults. + /// + /// `RTCErrorType` value. + /// + /// + /// + /// public RTCErrorType SetCodecPreferences(RTCRtpCodecCapability[] codecs) { +#if !UNITY_WEBGL RTCRtpCodecCapabilityInternal[] array = Array.ConvertAll(codecs, v => v.Cast()); MarshallingArray instance = array; RTCErrorType error = NativeMethods.TransceiverSetCodecPreferences(GetSelfOrThrow(), instance.ptr, instance.length); @@ -129,12 +189,32 @@ public RTCErrorType SetCodecPreferences(RTCRtpCodecCapability[] codecs) } instance.Dispose(); return error; +#else + string json = JsonConvert.SerializeObject(codecs, Formatting.None, new JsonSerializerSettings{NullValueHandling = NullValueHandling.Ignore}); + + //TODO Get correct RTCErrorType from jslib. + NativeMethods.TransceiverSetCodecPreferences(self, json); + return RTCErrorType.None; +#endif } /// - /// + /// Stops the transceiver permanently by stopping both the associated RTCRtpSender and RTCRtpReceiver. /// - /// + /// + /// Calling `Stop` method stops the sender from sending media immediately, closes RTP streams with an RTCP "BYE" message, and the receiver stops receiving media. + /// The receiver's track ceases, and the transceiver's direction changes to `Stopped`. + /// + /// `RTCErrorType` value. + /// + /// + /// public RTCErrorType Stop() { return NativeMethods.TransceiverStop(self); diff --git a/Runtime/Scripts/RTPParameters.cs b/Runtime/Scripts/RTPParameters.cs index 0c9517f8bb..c9f13d0414 100644 --- a/Runtime/Scripts/RTPParameters.cs +++ b/Runtime/Scripts/RTPParameters.cs @@ -5,43 +5,92 @@ namespace Unity.WebRTC { /// - /// + /// Represents the parameters for a codec used to encode the track's media. /// + /// + /// Represents the configuration for encoding a media track's codec, detailing properties like + /// active state, bitrate, framerate, RTP stream ID, and video scaling, enabling precise control of media transmission. + /// + /// + /// + /// public class RTCRtpEncodingParameters { /// - /// + /// Indicates whether the encoding is sent. /// + /// + /// By default, the value is `true`. Setting it to `false` stops the transmission but does not cause the SSRC to be removed. + /// public bool active; /// - /// + /// Specifies the maximum number of bits per second allocated for encoding tracks. /// + /// + /// `maxBitrate` defines the maximum encoding bitrate using TIAS bandwidth, not including protocol overhead. + /// Factors like `maxFramerate` and network capacity can further limit this rate. + /// Video tracks may drop frames, and audio tracks might pause if bitrates are too low, balancing quality and efficiency. + /// public ulong? maxBitrate; /// - /// + /// Specifies the minimum number of bits per second allocated for encoding tracks. /// public ulong? minBitrate; /// - /// + /// Specifies the maximum number of frames per second allocated for encoding tracks. /// public uint? maxFramerate; /// - /// + /// Specifies a scaling factor for reducing the resolution of video tracks during encoding. /// + /// + /// `scaleResolutionDownBy` property scales down video resolutions for encoding. + /// With the default value 1.0 preserves the original size. + /// Values above 1.0 reduce dimensions, helping optimize bandwidth and processing needs. + /// Values below 1.0 are invalid. + /// public double? scaleResolutionDownBy; /// - /// + /// Specifies an RTP stream ID (RID) for the RID header extension. /// + /// + /// The value can be set only during transceiver creation and not modifiable with `RTCRtpSender.SetParameters`. + /// public string rid; /// - /// + /// Creates a new RTCRtpEncodingParameters object. /// + /// + /// A constructor creates an instance of `RTCRtpEncodingParameters`. + /// + /// + /// + /// public RTCRtpEncodingParameters() { } internal RTCRtpEncodingParameters(ref RTCRtpEncodingParametersInternal parameter) @@ -489,5 +538,4 @@ public void Dispose() streams.Dispose(); } } - } diff --git a/Runtime/Scripts/VideoStreamTrack.cs b/Runtime/Scripts/VideoStreamTrack.cs index 1e6702f6e9..6d9fd9060c 100644 --- a/Runtime/Scripts/VideoStreamTrack.cs +++ b/Runtime/Scripts/VideoStreamTrack.cs @@ -8,26 +8,63 @@ namespace Unity.WebRTC { /// - /// + /// Delegate to be called when the first frame of the video is received. /// - /// + /// + /// `OnVideoReceived` delegate is called when the first frame of the video is received. + /// + /// `Texture` object where the video stream is rendered. + /// + /// + /// { + /// receivedImage.texture = texture; + /// } + /// ]]> + /// + /// public delegate void OnVideoReceived(Texture renderer); /// - /// + /// Delegate to be called to copy texture. /// - /// - /// + /// + /// `CopyTexture` delegate is called to copy texture when the texture is updated. + /// + /// Source `Texture` object. + /// Destination `Texture` object. + /// + /// + /// + /// public delegate void CopyTexture(Texture source, RenderTexture dest); /// - /// + /// Represents a single video track within a stream /// + /// + /// `VideoStreamTrack` is a `MediaStreamTrack` that represents a single video track within a stream. + /// + /// + /// + /// + /// + /// public class VideoStreamTrack : MediaStreamTrack { /// - /// Flip vertically received video, change it befor start receive video + /// If the value is set to true, the received video is flipped vertically. /// + /// + /// Change this property before starting to receive video. + /// public static bool NeedReceivedVideoFlipVertically { get; set; } = true; internal static ConcurrentDictionary> s_tracks = @@ -66,7 +103,8 @@ private static RenderTexture CreateRenderTexture(int width, int height) } /// - /// encoded / decoded texture + /// When the track is configured to receive a video stream, represents the `Texture` object where the video stream is rendered. + /// When the track is configured to send a video stream, represents the destination `Texture` object to send. /// public Texture Texture { @@ -79,7 +117,8 @@ public Texture Texture } /// - /// encoded / decoded texture ptr + /// When the track is configured to receive a video stream, represents the pointer to the `Texture` object where the video stream is rendered. + /// When the track is configured to send a video stream, represents the pointer to the destination `Texture` object to send. /// public IntPtr TexturePtr { @@ -91,14 +130,39 @@ public IntPtr TexturePtr } } + /// + /// Indicates that the track is configured to decode an incoming video stream. + /// public bool Decoding => m_renderer != null; + + /// + /// Indicates that the track is configured to encode and send a video stream to a remote peer. + /// public bool Encoding => m_source != null; - public IntPtr DataPtr => m_dataptr; + /// + /// Pointer to the video stream data in the native memory. + /// + public IntPtr DataPtr => m_dataptr; /// - /// + /// Event to be fired when the first frame of the video is received. /// + /// + /// `OnVideoReceived` event is fired when the first frame of the video is received. + /// + /// + /// + /// { + /// receivedImage.texture = texture; + /// } + /// ]]> + /// + /// public event OnVideoReceived OnVideoReceived; internal void UpdateTexture() @@ -135,15 +199,24 @@ internal void UpdateTexture() } /// - /// Video Sender - /// Creates a new VideoStream object. - /// The track is created with a `source`. + /// Creates a new VideoStreamTrack object. /// - /// + /// + /// `VideoStreamTrack` constructor creates an instance of `VideoStreamTrack` with a `source`. + /// + /// + /// `Texture` object that provides the input source for the video stream and is used in creating the video track. + /// /// - /// By default, textures are copied vertically flipped, using CopyTextureHelper.VerticalFlipCopy, - /// use Graphics.Blit for copy as is, CopyTextureHelper for flip, - /// or write your own CopyTexture function + /// By default, textures are copied vertically flipped, using `CopyTextureHelper.VerticalFlipCopy`, + /// use `Graphics.Blit` for copy as is, `CopyTextureHelper` for flip, + /// or write your own `CopyTexture` function. + /// + /// + /// + /// /// public VideoStreamTrack(Texture texture, CopyTexture copyTexture = null) : base(CreateVideoTrack(texture, out var source)) @@ -180,8 +253,16 @@ internal VideoStreamTrack(IntPtr ptr) } /// - /// + /// Disposes of VideoStremTrack /// + /// + /// `Dispose` method disposes of the `VideoStreamTrack` and releases the associated resources. + /// + /// + /// + /// public override void Dispose() { if (this.disposed) @@ -254,7 +335,11 @@ static IntPtr CreateVideoTrack(Texture texture, out VideoTrackSource source) var label = Guid.NewGuid().ToString(); source = new VideoTrackSource(); +#if !UNITY_WEBGL return WebRTC.Context.CreateVideoTrack(label, source.GetSelfOrThrow()); +#else + return WebRTC.Context.CreateVideoTrack(source.sourceTexture_.GetNativeTexturePtr(),source.destTexture_.GetNativeTexturePtr(),source.sourceTexture_.width,source.sourceTexture_.height); +#endif } /// @@ -270,67 +355,6 @@ static IntPtr CreateVideoTrack(IntPtr ptr) } } - /// - /// - /// - public static class CameraExtension - { - /// - /// Create an instance of to stream a camera. - /// - /// - /// You should keep a reference of , created by this method. - /// This instance is collected by GC automatically if you don't own a reference. - /// - /// - /// - /// - /// - /// - public static VideoStreamTrack CaptureStreamTrack(this Camera cam, int width, int height, - RenderTextureDepth depth = RenderTextureDepth.Depth24, CopyTexture textureCopy = null) - { - switch (depth) - { - case RenderTextureDepth.Depth16: - case RenderTextureDepth.Depth24: - case RenderTextureDepth.Depth32: - break; - default: - throw new InvalidEnumArgumentException(nameof(depth), (int)depth, typeof(RenderTextureDepth)); - } - - if (width <= 0 || height <= 0) - { - throw new ArgumentException("width and height are should be greater than zero."); - } - - int depthValue = (int)depth; - var format = WebRTC.GetSupportedRenderTextureFormat(SystemInfo.graphicsDeviceType); - var rt = new UnityEngine.RenderTexture(width, height, depthValue, format); - rt.Create(); - cam.targetTexture = rt; - return new VideoStreamTrack(rt, textureCopy); - } - - /// - /// - /// - /// - /// - /// - /// - /// - public static MediaStream CaptureStream(this Camera cam, int width, int height, - RenderTextureDepth depth = RenderTextureDepth.Depth24) - { - var stream = new MediaStream(); - var track = cam.CaptureStreamTrack(width, height, depth); - stream.AddTrack(track); - return stream; - } - } - internal class VideoTrackSource : RefCountedObject { internal Texture sourceTexture_; @@ -484,13 +508,13 @@ static void OnVideoFrameResize(IntPtr ptrRenderer, int width, int height) public static class CopyTextureHelper { - // Blit parameter to flip vertically + // Blit parameter to flip vertically private static readonly Vector2 s_verticalScale = new Vector2(1f, -1f); private static readonly Vector2 s_verticalOffset = new Vector2(0f, 1f); - // Blit parameter to flip horizontally + // Blit parameter to flip horizontally private static readonly Vector2 s_horizontalScale = new Vector2(-1f, 1f); private static readonly Vector2 s_horixontalOffset = new Vector2(1f, 0f); - // Blit parameter to flip diagonally + // Blit parameter to flip diagonally private static readonly Vector2 s_diagonalScale = new Vector2(-1f, -1f); private static readonly Vector2 s_diagonalOffset = new Vector2(1f, 1f); diff --git a/Runtime/Scripts/WebRTC.cs b/Runtime/Scripts/WebRTC.cs index ae0e30ef33..2d6be5365a 100644 --- a/Runtime/Scripts/WebRTC.cs +++ b/Runtime/Scripts/WebRTC.cs @@ -3,10 +3,16 @@ using System.Collections.Generic; using System.Runtime.InteropServices; using System.Threading; +using System.Runtime.CompilerServices; +using System.IO; using UnityEngine; using UnityEngine.Experimental.Rendering; using UnityEngine.Rendering; +#if UNITY_WEBGL +[assembly: UnityEngine.Scripting.Preserve] +#endif + namespace Unity.WebRTC { /// @@ -421,18 +427,90 @@ public enum RTCDataChannelState Closed } + /// - /// + /// The RTCSessionDescription interface represents the setup of one side of a connection or a proposed connection. + /// It contains a description type that identifies the negotiation stage it pertains to, along with the session's SDP (Session Description Protocol) + /// details. /// + /// + /// Establishing a connection between two parties involves swapping RTCSessionDescription objects, + /// with each one proposing a set of connection setup options that the sender can accommodate. + /// The connection setup is finalized when both parties agree on a particular configuration. + /// + /// + /// pc1Senders; + /// private MediaStream videoStream; + /// private MediaStreamTrack track; + /// private DelegateOnNegotiationNeeded pc1OnNegotiationNeeded; + /// private bool videoUpdateStarted; + /// + /// private void Start() + /// { + /// pc1Senders = new List(); + /// pc1OnNegotiationNeeded = () => { StartCoroutine(PcOnNegotiationNeeded(_pc1)); }; + /// Call(); + /// } + /// + /// IEnumerator PcOnNegotiationNeeded(RTCPeerConnection pc) + /// { + /// var op = pc.CreateOffer(); + /// yield return op; + /// if (!op.IsError) + /// { + /// yield return StartCoroutine(OnCreateOfferSuccess(pc, op.Desc)); + /// } + /// } + /// + /// private void Call() + /// { + /// RTCConfiguration configuration = default; + /// configuration.iceServers = new[] { new RTCIceServer { urls = new[] { "stun:stun.l.google.com:19302" } } }; + /// _pc1 = new RTCPeerConnection(ref configuration); + /// _pc1.OnNegotiationNeeded = pc1OnNegotiationNeeded; + /// + /// videoStream = Camera.main.CaptureStream(1280, 720); + /// track = videoStream.GetTracks().First(); + /// + /// pc1Senders.Add(_pc1.AddTrack(track)); + /// if (!videoUpdateStarted) + /// { + /// StartCoroutine(WebRTC.Update()); + /// videoUpdateStarted = true; + /// } + /// } + /// + /// private IEnumerator OnCreateOfferSuccess(RTCPeerConnection pc, RTCSessionDescription desc) + /// { + /// Debug.Log($"Offer created. SDP is: \n{desc.sdp}"); + /// var op = pc.SetLocalDescription(ref desc); + /// yield return op; + /// } + /// } + /// + /// ]]> + /// + /// + /// public struct RTCSessionDescription { /// - /// + /// An enum that specifies the type of the session description. Refer to . /// public RTCSdpType type; /// - /// + /// A string that holds the session's SDP information. /// [MarshalAs(UnmanagedType.LPStr)] public string sdp; @@ -483,32 +561,42 @@ public enum RTCIceCredentialType } /// - /// + /// Represents a configuration for an ICE server used within WebRTC connections. /// - /// + /// + /// Represents a configuration for an ICE server used within WebRTC connections, + /// including authentication credentials and STUN/TURN server URLs. + /// + /// + /// + /// + /// /// [Serializable] public struct RTCIceServer { /// - /// + /// Specifies the credential for authenticating with the ICE server. /// [Tooltip("Optional: specifies the password to use when authenticating with the ICE server")] public string credential; /// - /// + /// Specifies the type of credential used. /// [Tooltip("What type of credential the `password` value")] public RTCIceCredentialType credentialType; /// - /// + /// An array containing the URLs of STUN or TURN servers to use for ICE negotiation. /// [Tooltip("Array to set URLs of your STUN/TURN servers")] public string[] urls; /// - /// + /// Specifies the user name for authenticating with the ICE server. /// [Tooltip("Optional: specifies the username to use when authenticating with the ICE server")] public string username; @@ -531,30 +619,50 @@ public enum RTCIceTransportPolicy : int } /// - /// + /// Provides options to configure the new connection. /// + /// + /// `RTCConfiguration` struct provides options to configure the new connection. + /// + /// + /// + /// /// /// [Serializable] public struct RTCConfiguration { +#if UNITY_WEBGL /// /// /// + /// public string label; +#endif + + /// + /// List of RTCIceServer objects, each describing one server which may be used by the ICE agent. + /// public RTCIceServer[] iceServers; /// - /// + /// Represents the current ICE transport policy. /// public RTCIceTransportPolicy? iceTransportPolicy; /// - /// + /// Specifies how to handle negotiation of candidates when the remote peer is not compatible with the SDP BUNDLE standard. /// public RTCBundlePolicy? bundlePolicy; /// - /// + /// Specifies the size of the prefetched ICE candidate pool. /// public int? iceCandidatePoolSize; @@ -621,11 +729,20 @@ public enum NativeLoggingSeverity }; /// - /// + /// Provides utilities and management functions for integrating WebRTC functionality. /// + /// + /// `WebRTC` class provides a set of static methods and properties to manage the WebRTC functionality. + /// + /// + /// + /// + /// public static class WebRTC { -#if UNITY_IOS +#if UNITY_IOS || UNITY_WEBGL internal const string Lib = "__Internal"; #else internal const string Lib = "webrtc"; @@ -663,9 +780,17 @@ internal static void InitializeInternal(bool limitTextureSize = true, bool enabl } /// - /// + /// Updates the texture data for all video tracks at the end of each frame. /// - /// + /// + /// `Update` method updates the texture data for all video tracks at the end of each frame. + /// + /// `IEnumerator` to facilitate coroutine execution. + /// + /// + /// public static IEnumerator Update() { var instruction = new WaitForEndOfFrame(); @@ -705,15 +830,22 @@ public static IEnumerator Update() } /// - /// Executes any pending tasks generated asynchronously during the WebRTC runtime. + /// Executes any pending tasks generated asynchronously during the WebRTC runtime. /// + /// + /// `ExecutePendingTasks` method processes pending tasks generated during WebRTC operations until reaching the specified timeout. + /// /// - /// The amount of time in milliseconds that the task queue can take before task execution will cease. + /// The maximum time in milliseconds for which to process the task queue before task execution stops. /// /// - /// true if all pending tasks were completed within milliseconds and false - /// otherwise. + /// `true` if all pending tasks were completed within milliseconds and `false` otherwise. /// + /// + /// + /// public static bool ExecutePendingTasks(int millisecondTimeout) { if (s_syncContext is ExecutableUnitySynchronizationContext executableContext) @@ -725,7 +857,7 @@ public static bool ExecutePendingTasks(int millisecondTimeout) } /// - /// + /// Controls whether texture size constraints are applied during WebRTC streaming. /// public static bool enableLimitTextureSize { @@ -734,8 +866,8 @@ public static bool enableLimitTextureSize } /// - /// Get & set the logger to use when logging debug messages inside the WebRTC package. - /// By default will use Debug.unityLogger. + /// Logger used for capturing debug messages within the WebRTC package. + /// Defaults to Debug.unityLogger. /// /// Throws if setting a null logger. public static ILogger Logger @@ -756,10 +888,18 @@ public static ILogger Logger } /// - /// Configure native logging settings for WebRTC. + /// Configures native logging settings for WebRTC. /// + /// + /// `ConfigureNativeLogging` method is used to enable or disable native logging and set the native logging level. + /// /// Enables or disable native logging. /// Sets the native logging level. + /// + /// + /// public static void ConfigureNativeLogging(bool enableNativeLogging, NativeLoggingSeverity nativeLoggingSeverity) { if (enableNativeLogging) @@ -791,6 +931,7 @@ internal static void DisposeInternal() s_context = null; } NativeMethods.RegisterDebugLog(null, false, NativeLoggingSeverity.Info); + s_syncContext = null; } internal static RTCError ValidateTextureSize(int width, int height, RuntimePlatform platform) @@ -860,9 +1001,18 @@ internal static RTCError ValidateTextureSize(int width, int height, RuntimePlatf } /// - /// + /// Checks whether the specified graphics format is supported. /// - /// + /// + /// `ValidateGraphicsFormat` method checks whether the provided `GraphicsFormat` is compatible with the current graphics device. + /// This method throws an `ArgumentException` if the format is not supported. + /// + /// `GraphicsFormat` value to be validated. + /// + /// + /// public static void ValidateGraphicsFormat(GraphicsFormat format) { // can't recognize legacy format @@ -883,10 +1033,18 @@ public static void ValidateGraphicsFormat(GraphicsFormat format) } /// - /// + /// Determines the appropriate RenderTextureFormat for a given GraphicsDeviceType. /// - /// - /// + /// + /// `GetSupportedRenderTextureFormat` method determines the appropriate `RenderTextureFormat` for a given `GraphicsDeviceType`. + /// + /// `GraphicsDeviceType` for which `RenderTextureFormat` is being determined. + /// `RenderTextureFormat` value for the specified `GraphicsDeviceType`. + /// + /// + /// public static RenderTextureFormat GetSupportedRenderTextureFormat(GraphicsDeviceType type) { var graphicsFormat = GetSupportedGraphicsFormat(type); @@ -894,10 +1052,21 @@ public static RenderTextureFormat GetSupportedRenderTextureFormat(GraphicsDevice } /// - /// + /// Determines the appropriate GraphicsFormat for a given GraphicsDeviceType. /// - /// - /// + /// + /// `GetSupportedGraphicsFormat` method determines the appropriate `GraphicsFormat` for a given `GraphicsDeviceType`. + /// + /// `GraphicsDeviceType` for which `GraphicsFormat` is being determined. + /// `GraphicsFormat` value for the specified `GraphicsDeviceType`. + /// + /// + /// public static GraphicsFormat GetSupportedGraphicsFormat(GraphicsDeviceType type) { if (QualitySettings.activeColorSpace == ColorSpace.Linear) @@ -937,10 +1106,18 @@ public static GraphicsFormat GetSupportedGraphicsFormat(GraphicsDeviceType type) } /// - /// + /// Determines the appropriate TextureFormat for a given GraphicsDeviceType. /// - /// - /// + /// + /// `GetSupportedTextureFormat` method determines the appropriate `TextureFormat` for a given `GraphicsDeviceType`. + /// + /// `GraphicsDeviceType` for which `TextureFormat` is being determined. + /// `TextureFormat` value for the specified `GraphicsDeviceType`. + /// + /// + /// public static TextureFormat GetSupportedTextureFormat(GraphicsDeviceType type) { var graphicsFormat = GetSupportedGraphicsFormat(type); @@ -989,8 +1166,13 @@ internal static void DelayActionOnMainThread(Action callback, float delay) internal static void Sync(IntPtr ptr, Action callback) { +#if !UNITY_WEBGL s_syncContext.Post(SendOrPostCallback, new CallbackObject(ptr, callback)); +#else + callback(); +#endif } + internal static string GetModuleName() { return System.IO.Path.GetFileName(Lib); @@ -1042,6 +1224,16 @@ internal static IEnumerable Deserialize(IntPtr buf, int length, Func(); + foreach (var ptr in array) + { + list.Add(FindOrCreate(ptr, constructor)); + } + return list; + } + + internal static IEnumerable Deserialize(IntPtr[] array, Func constructor) where T : class + { var list = new List(); foreach (var ptr in array) { @@ -1184,7 +1376,6 @@ static void OnSetTransformedFrame(IntPtr ptr, IntPtr frame) } } - internal static Context Context { get { return s_context; } } internal static WeakReferenceTable Table { get { return s_context?.table; } } @@ -1228,8 +1419,17 @@ internal static IReadOnlyList> PeerList internal delegate void DelegateNativeOnConnectionStateChange(IntPtr ptr, RTCPeerConnectionState state); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void DelegateNativeOnIceGatheringChange(IntPtr ptr, RTCIceGatheringState state); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate void DelegateNativePeerConnectionSetSessionDescSuccess(IntPtr result); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate void DelegateNativePeerConnectionSetSessionDescFailure(IntPtr result, int errorType, IntPtr error); +#if !UNITY_WEBGL [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void DelegateNativeOnIceCandidate(IntPtr ptr, [MarshalAs(UnmanagedType.LPStr)] string candidate, [MarshalAs(UnmanagedType.LPStr)] string sdpMid, int sdpMlineIndex); +#else + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate void DelegateNativeOnIceCandidate(IntPtr ptr, IntPtr iceCandidatePtr, [MarshalAs(UnmanagedType.LPStr)] string candidate, [MarshalAs(UnmanagedType.LPStr)] string sdpMid, int sdpMlineIndex); +#endif [UnmanagedFunctionPointer(CallingConvention.Cdecl)] //according to JS API naming, use OnNegotiationNeeded instead of OnRenegotiationNeeded internal delegate void DelegateNativeOnNegotiationNeeded(IntPtr ptr); @@ -1241,6 +1441,10 @@ internal static IReadOnlyList> PeerList internal delegate void DelegateNativeOnDataChannel(IntPtr ptr, IntPtr ptrChannel); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void DelegateNativeOnMessage(IntPtr ptr, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] byte[] bytes, int size); +#if UNITY_WEBGL + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate void DelegateNativeOnTextMessage(IntPtr ptr, IntPtr msg); +#endif [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void DelegateNativeOnOpen(IntPtr ptr); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] @@ -1264,12 +1468,27 @@ internal static class NativeMethods [DllImport(WebRTC.Lib)] public static extern void RegisterRenderingWebRTCPlugin(); #endif + + public static IntPtr[] ptrToIntPtrArray(IntPtr ptr) + { + int len = Marshal.ReadInt32(ptr); + int[] arr = new int[len]; + Marshal.Copy(IntPtr.Add(ptr, 4), arr, 0, len); + IntPtr[] ret = new IntPtr[len]; + for (var i = 0; i < len; i++) + ret[i] = new IntPtr(arr[i]); + return ret; + } + [DllImport(WebRTC.Lib)] public static extern void RegisterDebugLog(DelegateDebugLog func, [MarshalAs(UnmanagedType.U1)] bool enableNativeLog, NativeLoggingSeverity nativeLoggingSeverity); [DllImport(WebRTC.Lib)] public static extern IntPtr ContextCreate(int uid); [DllImport(WebRTC.Lib)] + [return: MarshalAs(UnmanagedType.U1)] + public static extern bool GetHardwareEncoderSupport(); + [DllImport(WebRTC.Lib)] public static extern void ContextDestroy(int uid); [DllImport(WebRTC.Lib)] public static extern IntPtr ContextCreatePeerConnection(IntPtr ptr); @@ -1283,16 +1502,27 @@ public static extern void RegisterDebugLog(DelegateDebugLog func, [MarshalAs(Unm public static extern void PeerConnectionRestartIce(IntPtr ptr); [DllImport(WebRTC.Lib)] public static extern RTCErrorType PeerConnectionSetConfiguration(IntPtr ptr, [MarshalAs(UnmanagedType.LPStr, SizeConst = 256)] string conf); +#if !UNITY_WEBGL [DllImport(WebRTC.Lib)] public static extern IntPtr ContextCreateDataChannel(IntPtr ptr, IntPtr ptrPeer, [MarshalAs(UnmanagedType.LPStr, SizeConst = 256)] string label, ref RTCDataChannelInitInternal options); +#else + [DllImport(WebRTC.Lib)] + public static extern IntPtr ContextCreateDataChannel(IntPtr ptr, IntPtr ptrPeer, [MarshalAs(UnmanagedType.LPStr, SizeConst = 256)] string label, [MarshalAs(UnmanagedType.LPStr, SizeConst = 256)] string optionsJson); +#endif [DllImport(WebRTC.Lib)] public static extern void ContextDeleteDataChannel(IntPtr ptr, IntPtr ptrChannel); + [DllImport(WebRTC.Lib)] public static extern IntPtr ContextCreateAudioTrackSource(IntPtr ptr); [DllImport(WebRTC.Lib)] public static extern IntPtr ContextCreateVideoTrackSource(IntPtr ptr); +#if !UNITY_WEBGL [DllImport(WebRTC.Lib)] public static extern IntPtr ContextCreateVideoTrack(IntPtr ptr, [MarshalAs(UnmanagedType.LPStr, SizeConst = 256)] string label, IntPtr trackSource); +#else + [DllImport(WebRTC.Lib)] + public static extern IntPtr ContextCreateVideoTrack(IntPtr self, IntPtr srcTexturePtr, IntPtr dstTexturePtr, int width, int height); +#endif [DllImport(WebRTC.Lib)] public static extern IntPtr ContextCreateAudioTrack(IntPtr ptr, [MarshalAs(UnmanagedType.LPStr, SizeConst = 256)] string label, IntPtr trackSource); [DllImport(WebRTC.Lib)] @@ -1306,13 +1536,22 @@ public static extern void RegisterDebugLog(DelegateDebugLog func, [MarshalAs(Unm [DllImport(WebRTC.Lib)] public static extern void ContextDeleteRefPtr(IntPtr context, IntPtr ptr); [DllImport(WebRTC.Lib)] + public static extern void ContextSetVideoEncoderParameter(IntPtr context, IntPtr track, int width, int height, GraphicsFormat format, IntPtr texturePtr); + [DllImport(WebRTC.Lib)] public static extern IntPtr ContextCreateFrameTransformer(IntPtr context); [DllImport(WebRTC.Lib)] public static extern IntPtr PeerConnectionGetConfiguration(IntPtr ptr); +#if !UNITY_WEBGL [DllImport(WebRTC.Lib)] public static extern CreateSessionDescriptionObserver PeerConnectionCreateOffer(IntPtr context, IntPtr ptr, ref RTCOfferAnswerOptions options); [DllImport(WebRTC.Lib)] public static extern CreateSessionDescriptionObserver PeerConnectionCreateAnswer(IntPtr context, IntPtr ptr, ref RTCOfferAnswerOptions options); +#else + [DllImport(WebRTC.Lib)] + public static extern void PeerConnectionCreateOffer(IntPtr ptr, string options); + [DllImport(WebRTC.Lib)] + public static extern void PeerConnectionCreateAnswer(IntPtr ptr, string options); +#endif [DllImport(WebRTC.Lib)] public static extern void StatsCollectorRegisterCallback(DelegateCollectStats onCollectStats); [DllImport(WebRTC.Lib)] @@ -1331,6 +1570,11 @@ public static extern void RegisterDebugLog(DelegateDebugLog func, [MarshalAs(Unm public static extern void PeerConnectionRegisterIceGatheringChange(IntPtr ptr, DelegateNativeOnIceGatheringChange callback); [DllImport(WebRTC.Lib)] public static extern void PeerConnectionRegisterOnIceCandidate(IntPtr ptr, DelegateNativeOnIceCandidate callback); + [DllImport(WebRTC.Lib)] + public static extern void PeerConnectionRegisterOnSetSessionDescSuccess(IntPtr pc, IntPtr ptr, DelegateNativePeerConnectionSetSessionDescSuccess callback); + [DllImport(WebRTC.Lib)] + public static extern void PeerConnectionRegisterOnSetSessionDescFailure(IntPtr pc, IntPtr ptr, DelegateNativePeerConnectionSetSessionDescFailure callback); +#if !UNITY_WEBGL [DllImport(WebRTC.Lib)] public static extern SetSessionDescriptionObserver PeerConnectionSetLocalDescription(IntPtr ptr, ref RTCSessionDescription desc, out RTCErrorType errorType, ref IntPtr error); [DllImport(WebRTC.Lib)] @@ -1341,12 +1585,35 @@ public static extern void RegisterDebugLog(DelegateDebugLog func, [MarshalAs(Unm public static extern RTCStatsCollectorCallback PeerConnectionGetStats(IntPtr ptr); [DllImport(WebRTC.Lib)] public static extern RTCStatsCollectorCallback PeerConnectionSenderGetStats(IntPtr ptr, IntPtr sender); +#else + [DllImport(WebRTC.Lib)] + public static extern IntPtr PeerConnectionSetLocalDescription(IntPtr context, IntPtr ptr, RTCSdpType type, string sdp); + [DllImport(WebRTC.Lib)] + public static extern IntPtr PeerConnectionSetLocalDescriptionWithoutDescription(IntPtr context, IntPtr ptr); + [DllImport(WebRTC.Lib)] + public static extern IntPtr PeerConnectionSetRemoteDescription(IntPtr context, IntPtr ptr, RTCSdpType type, string sdp); + [DllImport(WebRTC.Lib)] + public static extern void PeerConnectionGetStats(IntPtr ptr); + [DllImport(WebRTC.Lib)] + public static extern void PeerConnectionSenderGetStats(IntPtr ptr, IntPtr sender); +#endif + +#if !UNITY_WEBGL [DllImport(WebRTC.Lib)] public static extern void ContextGetSenderCapabilities(IntPtr context, TrackKind kind, out IntPtr capabilities); +#else + [DllImport(WebRTC.Lib)] + public static extern string ContextGetSenderCapabilities(IntPtr context, TrackKind kind); +#endif +#if !UNITY_WEBGL [DllImport(WebRTC.Lib)] public static extern void ContextGetReceiverCapabilities(IntPtr context, TrackKind kind, out IntPtr capabilities); +#else [DllImport(WebRTC.Lib)] - public static extern RTCStatsCollectorCallback PeerConnectionReceiverGetStats(IntPtr sender, IntPtr receiver); + public static extern string ContextGetReceiverCapabilities(IntPtr context, TrackKind kind); +#endif + +#if !UNITY_WEBGL [DllImport(WebRTC.Lib)] [return: MarshalAs(UnmanagedType.U1)] public static extern bool PeerConnectionCanTrickleIceCandidates(IntPtr ptr, [MarshalAs(UnmanagedType.U1)] out bool value); @@ -1370,6 +1637,33 @@ public static extern void RegisterDebugLog(DelegateDebugLog func, [MarshalAs(Unm public static extern bool PeerConnectionGetCurrentRemoteDescription(IntPtr ptr, ref RTCSessionDescription desc); [DllImport(WebRTC.Lib)] public static extern RTCErrorType PeerConnectionAddTrack(IntPtr pc, IntPtr track, [MarshalAs(UnmanagedType.LPStr, SizeConst = 256)] string streamId, out IntPtr sender); +#else + public static bool PeerConnectionCanTrickleIceCandidates(IntPtr ptr, out bool value) => throw new NotImplementedException(); + public static bool PeerConnectionGetLocalDescription(IntPtr ptr, ref RTCSessionDescription desc) => throw new NotImplementedException(); + public static bool PeerConnectionGetRemoteDescription(IntPtr ptr, ref RTCSessionDescription desc) => throw new NotImplementedException(); + public static bool PeerConnectionGetPendingLocalDescription(IntPtr ptr, ref RTCSessionDescription desc) => throw new NotImplementedException(); + public static bool PeerConnectionGetPendingRemoteDescription(IntPtr ptr, ref RTCSessionDescription desc) => throw new NotImplementedException(); + public static bool PeerConnectionGetCurrentLocalDescription(IntPtr ptr, ref RTCSessionDescription desc) => throw new NotImplementedException(); + public static bool PeerConnectionGetCurrentRemoteDescription(IntPtr ptr, ref RTCSessionDescription desc) => throw new NotImplementedException(); + public static RTCErrorType PeerConnectionAddTrack(IntPtr pc, IntPtr track, string streamId, out IntPtr sender) => throw new NotImplementedException(); + + [DllImport(WebRTC.Lib)] + public static extern string PeerConnectionGetLocalDescription(IntPtr ptr); + [DllImport(WebRTC.Lib)] + public static extern string PeerConnectionGetRemoteDescription(IntPtr ptr); + [DllImport(WebRTC.Lib)] + public static extern string PeerConnectionGetCurrentLocalDescription(IntPtr ptr); + [DllImport(WebRTC.Lib)] + public static extern string PeerConnectionGetCurrentRemoteDescription(IntPtr ptr); + [DllImport(WebRTC.Lib)] + public static extern string PeerConnectionGetPendingLocalDescription(IntPtr ptr); + [DllImport(WebRTC.Lib)] + public static extern string PeerConnectionGetPendingRemoteDescription(IntPtr ptr); + [DllImport(WebRTC.Lib)] + public static extern IntPtr PeerConnectionAddTrack(IntPtr pc, IntPtr track, IntPtr streamId); +#endif + [DllImport(WebRTC.Lib)] + public static extern RTCStatsCollectorCallback PeerConnectionReceiverGetStats(IntPtr sender, IntPtr receiver); [DllImport(WebRTC.Lib)] public static extern IntPtr PeerConnectionAddTransceiver(IntPtr pc, IntPtr track); [DllImport(WebRTC.Lib)] @@ -1383,10 +1677,15 @@ public static extern void RegisterDebugLog(DelegateDebugLog func, [MarshalAs(Unm [DllImport(WebRTC.Lib)] [return: MarshalAs(UnmanagedType.U1)] public static extern bool PeerConnectionAddIceCandidate(IntPtr ptr, IntPtr candidate); +#if !UNITY_WEBGL [DllImport(WebRTC.Lib)] public static extern RTCErrorType CreateIceCandidate(ref RTCIceCandidateInitInternal options, out IntPtr candidate); +#endif + [DllImport(WebRTC.Lib)] + public static extern IntPtr CreateNativeRTCIceCandidate(string candidate, string sdpMid, int sdpMLineIndex); [DllImport(WebRTC.Lib)] public static extern RTCErrorType DeleteIceCandidate(IntPtr candidate); +#if !UNITY_WEBGL [DllImport(WebRTC.Lib)] public static extern void IceCandidateGetCandidate(IntPtr candidate, out CandidateInternal dst); [DllImport(WebRTC.Lib)] @@ -1397,14 +1696,35 @@ public static extern void RegisterDebugLog(DelegateDebugLog func, [MarshalAs(Unm [DllImport(WebRTC.Lib)] [return: MarshalAs(UnmanagedType.LPStr)] public static extern string IceCandidateGetSdpMid(IntPtr candidate); +#else + [DllImport(WebRTC.Lib)] + public static extern string IceCandidateGetCandidate(IntPtr candidate); + [DllImport(WebRTC.Lib)] + public static extern int IceCandidateGetSdpLineIndex(IntPtr candidate); + [DllImport(WebRTC.Lib)] + [return: MarshalAs(UnmanagedType.LPStr)] + public static extern string IceCandidateGetSdp(IntPtr candidate); + [DllImport(WebRTC.Lib)] + [return: MarshalAs(UnmanagedType.LPStr)] + public static extern string IceCandidateGetSdpMid(IntPtr candidate); +#endif [DllImport(WebRTC.Lib)] public static extern RTCPeerConnectionState PeerConnectionState(IntPtr ptr); +#if !UNITY_WEBGL [DllImport(WebRTC.Lib)] public static extern IntPtr PeerConnectionGetReceivers(IntPtr context, IntPtr ptr, out ulong length); [DllImport(WebRTC.Lib)] public static extern IntPtr PeerConnectionGetSenders(IntPtr context, IntPtr ptr, out ulong length); [DllImport(WebRTC.Lib)] public static extern IntPtr PeerConnectionGetTransceivers(IntPtr context, IntPtr ptr, out ulong length); +#else + [DllImport(WebRTC.Lib)] + public static extern IntPtr PeerConnectionGetReceivers(IntPtr context, IntPtr ptr); + [DllImport(WebRTC.Lib)] + public static extern IntPtr PeerConnectionGetSenders(IntPtr context, IntPtr ptr); + [DllImport(WebRTC.Lib)] + public static extern IntPtr PeerConnectionGetTransceivers(IntPtr context, IntPtr ptr); +#endif [DllImport(WebRTC.Lib)] public static extern RTCIceConnectionState PeerConnectionIceConditionState(IntPtr ptr); [DllImport(WebRTC.Lib)] @@ -1419,9 +1739,14 @@ public static extern void RegisterDebugLog(DelegateDebugLog func, [MarshalAs(Unm public static extern void PeerConnectionRegisterOnTrack(IntPtr ptr, DelegateNativeOnTrack callback); [DllImport(WebRTC.Lib)] public static extern void PeerConnectionRegisterOnRemoveTrack(IntPtr ptr, DelegateNativeOnRemoveTrack callback); +#if !UNITY_WEBGL [DllImport(WebRTC.Lib)] [return: MarshalAs(UnmanagedType.U1)] public static extern bool TransceiverGetCurrentDirection(IntPtr transceiver, out RTCRtpTransceiverDirection direction); +#else + [DllImport(WebRTC.Lib)] + public static extern int TransceiverGetCurrentDirection(IntPtr transceiver); +#endif [DllImport(WebRTC.Lib)] public static extern RTCErrorType TransceiverStop(IntPtr transceiver); [DllImport(WebRTC.Lib)] @@ -1430,20 +1755,32 @@ public static extern void RegisterDebugLog(DelegateDebugLog func, [MarshalAs(Unm public static extern RTCRtpTransceiverDirection TransceiverGetDirection(IntPtr transceiver); [DllImport(WebRTC.Lib)] public static extern RTCErrorType TransceiverSetDirection(IntPtr transceiver, RTCRtpTransceiverDirection direction); +#if !UNITY_WEBGL [DllImport(WebRTC.Lib)] public static extern RTCErrorType TransceiverSetCodecPreferences(IntPtr transceiver, IntPtr capabilities, long length); +#else + [DllImport(WebRTC.Lib)] + public static extern RTCErrorType TransceiverSetCodecPreferences(IntPtr transceiver, string capabilities); +#endif [DllImport(WebRTC.Lib)] public static extern IntPtr TransceiverGetReceiver(IntPtr transceiver); [DllImport(WebRTC.Lib)] public static extern IntPtr TransceiverGetSender(IntPtr transceiver); [DllImport(WebRTC.Lib)] public static extern IntPtr SenderGetTrack(IntPtr sender); +#if !UNITY_WEBGL [DllImport(WebRTC.Lib)] public static extern IntPtr SenderSetTransform(IntPtr sender, IntPtr transform); [DllImport(WebRTC.Lib)] public static extern void SenderGetParameters(IntPtr sender, out IntPtr parameters); [DllImport(WebRTC.Lib)] public static extern RTCErrorType SenderSetParameters(IntPtr sender, IntPtr parameters); +#else + [DllImport(WebRTC.Lib)] + public static extern string SenderGetParameters(IntPtr sender); + [DllImport(WebRTC.Lib)] + public static extern RTCErrorType SenderSetParameters(IntPtr sender, string parameters); +#endif [DllImport(WebRTC.Lib)] [return: MarshalAs(UnmanagedType.U1)] public static extern bool SenderReplaceTrack(IntPtr sender, IntPtr track); @@ -1485,6 +1822,10 @@ public static extern void RegisterDebugLog(DelegateDebugLog func, [MarshalAs(Unm public static extern void DataChannelClose(IntPtr ptr); [DllImport(WebRTC.Lib)] public static extern void DataChannelRegisterOnMessage(IntPtr ctx, IntPtr ptr, DelegateNativeOnMessage callback); +#if UNITY_WEBGL + [DllImport(WebRTC.Lib)] + public static extern void DataChannelRegisterOnTextMessage(IntPtr ptr, DelegateNativeOnTextMessage callback); +#endif [DllImport(WebRTC.Lib)] public static extern void DataChannelRegisterOnOpen(IntPtr ctx, IntPtr ptr, DelegateNativeOnOpen callback); [DllImport(WebRTC.Lib)] @@ -1493,6 +1834,10 @@ public static extern void RegisterDebugLog(DelegateDebugLog func, [MarshalAs(Unm public static extern void DataChannelRegisterOnError(IntPtr ctx, IntPtr ptr, DelegateNativeOnError callback); [DllImport(WebRTC.Lib)] public static extern IntPtr ContextCreateMediaStream(IntPtr ctx, [MarshalAs(UnmanagedType.LPStr, SizeConst = 256)] string label); +#if UNITY_WEBGL + [DllImport(WebRTC.Lib)] + public static extern void MediaStreamAddUserMedia(IntPtr streamPtr, string constraints); +#endif [DllImport(WebRTC.Lib)] public static extern void ContextRegisterMediaStreamObserver(IntPtr ctx, IntPtr stream); [DllImport(WebRTC.Lib)] @@ -1514,10 +1859,17 @@ public static extern void AudioTrackSinkProcessAudio( [DllImport(WebRTC.Lib)] [return: MarshalAs(UnmanagedType.U1)] public static extern bool MediaStreamRemoveTrack(IntPtr stream, IntPtr track); +#if !UNITY_WEBGL [DllImport(WebRTC.Lib)] public static extern IntPtr MediaStreamGetVideoTracks(IntPtr stream, out ulong length); [DllImport(WebRTC.Lib)] public static extern IntPtr MediaStreamGetAudioTracks(IntPtr stream, out ulong length); +#else + [DllImport(WebRTC.Lib)] + public static extern IntPtr MediaStreamGetVideoTracks(IntPtr stream); + [DllImport(WebRTC.Lib)] + public static extern IntPtr MediaStreamGetAudioTracks(IntPtr stream); +#endif [DllImport(WebRTC.Lib)] public static extern IntPtr MediaStreamGetID(IntPtr stream); [DllImport(WebRTC.Lib)] @@ -1563,6 +1915,7 @@ public static extern IntPtr CreateVideoRenderer( public static extern void VideoSourceSetSyncApplicationFramerate(IntPtr source, [MarshalAs(UnmanagedType.U1)] bool value); [DllImport(WebRTC.Lib)] public static extern IntPtr StatsGetJson(IntPtr stats); +#if !UNITY_WEBGL [DllImport(WebRTC.Lib)] public static extern IntPtr StatsGetId(IntPtr stats); [DllImport(WebRTC.Lib)] @@ -1626,10 +1979,88 @@ public static extern IntPtr CreateVideoRenderer( public static extern bool VideoFrameIsKeyFrame(IntPtr frame); [DllImport(WebRTC.Lib)] public static extern void FrameTransformerSendFrameToSink(IntPtr transform, IntPtr frame); +#else + public static IntPtr StatsGetId(IntPtr stats){ return default; } + public static RTCStatsType StatsGetType(IntPtr stats){ return default; } + public static long StatsGetTimestamp(IntPtr stats){ return default; } + public static IntPtr StatsGetMembers(IntPtr stats, out ulong length){ length = default; return default; } + public static IntPtr StatsMemberGetName(IntPtr member){ return default; } + public static StatsMemberType StatsMemberGetType(IntPtr member){ return default; } + public static bool StatsMemberIsDefined(IntPtr member){ return default; } + public static bool StatsMemberGetBool(IntPtr member){ return default; } + public static int StatsMemberGetInt(IntPtr member){ return default; } + public static uint StatsMemberGetUnsignedInt(IntPtr member){ return default; } + public static long StatsMemberGetLong(IntPtr member){ return default; } + public static ulong StatsMemberGetUnsignedLong(IntPtr member){ return default; } + public static double StatsMemberGetDouble(IntPtr member){ return default; } + public static IntPtr StatsMemberGetString(IntPtr member){ return default; } + public static IntPtr StatsMemberGetBoolArray(IntPtr member, out ulong length){ length = default; return default; } + public static IntPtr StatsMemberGetIntArray(IntPtr member, out ulong length){ length = default; return default; } + public static IntPtr StatsMemberGetUnsignedIntArray(IntPtr member, out ulong length){ length = default; return default; } + public static IntPtr StatsMemberGetLongArray(IntPtr member, out ulong length){ length = default; return default; } + public static IntPtr StatsMemberGetUnsignedLongArray(IntPtr member, out ulong length){ length = default; return default; } + public static IntPtr StatsMemberGetDoubleArray(IntPtr member, out ulong length){ length = default; return default; } + public static IntPtr StatsMemberGetStringArray(IntPtr member, out ulong length){ length = default; return default; } + public static IntPtr StatsMemberGetMapStringUint64(IntPtr member, out IntPtr values, out ulong length){ values = default; length = default; return default; } + public static IntPtr StatsMemberGetMapStringDouble(IntPtr member, out IntPtr values, out ulong length){ values = default; length = default; return default; } + public static uint FrameGetTimestamp(IntPtr frame) => default; + public static uint FrameGetSsrc(IntPtr frame) => default; + public static void FrameGetData(IntPtr frame, out IntPtr data, out int size) { size = default; data = default; } + public static void FrameSetData(IntPtr frame, IntPtr data, int size) { } + public static IntPtr VideoFrameGetMetadata(IntPtr frame) => default; + [return: MarshalAs(UnmanagedType.U1)] + public static bool VideoFrameIsKeyFrame(IntPtr frame) => default; + public static void FrameTransformerSendFrameToSink(IntPtr transform, IntPtr frame) { } + [UnityEngine.Scripting.Preserve] [DllImport(WebRTC.Lib)] - public static extern void SetGraphicsSyncTimeout(uint nSecTimeout); + public static extern IntPtr CreateAudioTrack(); + [UnityEngine.Scripting.Preserve] + [DllImport(WebRTC.Lib)] + public static extern IntPtr CreateVideoTrack(); + [UnityEngine.Scripting.Preserve] + [DllImport(WebRTC.Lib)] + public static extern IntPtr CreateMediaStream(); + [UnityEngine.Scripting.Preserve] + [DllImport(WebRTC.Lib)] + public static extern IntPtr CreatePeerConnection(); + [UnityEngine.Scripting.Preserve] + [DllImport(WebRTC.Lib)] + public static extern IntPtr CreatePeerConnectionWithConfig(string confJson); + [UnityEngine.Scripting.Preserve] + [DllImport(WebRTC.Lib)] + public static extern IntPtr PeerConnectionSetDescription(IntPtr peerPtr, RTCSdpType type, string sdp, string side); + [UnityEngine.Scripting.Preserve] + [DllImport(WebRTC.Lib)] + public static extern IntPtr PeerConnectionSetDescriptionWithoutDescription(IntPtr peerPtr); + [UnityEngine.Scripting.Preserve] + [DllImport(WebRTC.Lib)] + public static extern IntPtr CreateDataChannel(); + [UnityEngine.Scripting.Preserve] + [DllImport(WebRTC.Lib)] + public static extern IntPtr DeleteMediaStream(IntPtr streamPtr); + [UnityEngine.Scripting.Preserve] + [DllImport(WebRTC.Lib)] + public static extern IntPtr DeleteReceiver(IntPtr receiverPtr); + [UnityEngine.Scripting.Preserve] + [DllImport(WebRTC.Lib)] + public static extern IntPtr DeleteSender(IntPtr senderPtr); + [UnityEngine.Scripting.Preserve] + [DllImport(WebRTC.Lib)] + public static extern IntPtr DeleteTransceiver(IntPtr transceiverPtr); + [UnityEngine.Scripting.Preserve] + [DllImport(WebRTC.Lib)] + public static extern void RenderLocalVideotrack(IntPtr trackPtr, bool needFlip); + [UnityEngine.Scripting.Preserve] + [DllImport(WebRTC.Lib)] + public static extern void UpdateRendererTexture(IntPtr trackPtr, IntPtr renderTexturePtr, bool needFlip); + [UnityEngine.Scripting.Preserve] + [DllImport(WebRTC.Lib)] + public static extern IntPtr CreateNativeTexture(); +#endif + [DllImport(WebRTC.Lib)] + public static extern void SetGraphicsSyncTimeout(uint nSecTimeout); } internal static class VideoUpdateMethods diff --git a/Runtime/Unity.WebRTC.Runtime.asmdef b/Runtime/Unity.WebRTC.Runtime.asmdef index a913ec4726..953b8f6f9d 100644 --- a/Runtime/Unity.WebRTC.Runtime.asmdef +++ b/Runtime/Unity.WebRTC.Runtime.asmdef @@ -1,5 +1,6 @@ { "name": "Unity.WebRTC", + "rootNamespace": "", "references": [], "includePlatforms": [ "Android", @@ -7,6 +8,7 @@ "iOS", "LinuxStandalone64", "macOSStandalone", + "WebGL", "WindowsStandalone64" ], "excludePlatforms": [], diff --git a/Runtime/WebGL.meta b/Runtime/WebGL.meta new file mode 100644 index 0000000000..c075fd3e0a --- /dev/null +++ b/Runtime/WebGL.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1f93366b4172b0b43801d09bd020b253 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples~/Audio/AudioSample.cs b/Samples~/Audio/AudioSample.cs index dcc7947d75..218d163054 100644 --- a/Samples~/Audio/AudioSample.cs +++ b/Samples~/Audio/AudioSample.cs @@ -65,7 +65,6 @@ void Start() toggleEnableMicrophone.isOn = false; toggleEnableMicrophone.onValueChanged.AddListener(OnEnableMicrophone); - toggleEnableMicrophone.isOn = false; toggleLoopback.onValueChanged.AddListener(OnChangeLoopback); dropdownAudioClips.interactable = true; dropdownAudioClips.options = @@ -129,8 +128,10 @@ void OnStart() { m_deviceName = dropdownMicrophoneDevices.captionText.text; m_clipInput = Microphone.Start(m_deviceName, true, m_lengthSeconds, m_samplingFrequency); +#if !UNITY_WEBGL // set the latency to “0” samples before the audio starts to play. while (!(Microphone.GetPosition(m_deviceName) > 0)) { } +#endif } else { diff --git a/Samples~/DataChannel/DataChannelSample.cs b/Samples~/DataChannel/DataChannelSample.cs index 5e23634b42..f2e95c01c1 100644 --- a/Samples~/DataChannel/DataChannelSample.cs +++ b/Samples~/DataChannel/DataChannelSample.cs @@ -22,6 +22,9 @@ class DataChannelSample : MonoBehaviour private DelegateOnIceCandidate pc1OnIceCandidate; private DelegateOnIceCandidate pc2OnIceCandidate; private DelegateOnMessage onDataChannelMessage; +#if UNITY_WEBGL + private DelegateOnTextMessage onDataChannelTextMessage; +#endif private DelegateOnOpen onDataChannelOpen; private DelegateOnClose onDataChannelClose; private DelegateOnDataChannel onDataChannel; @@ -46,8 +49,14 @@ private void Start() { remoteDataChannel = channel; remoteDataChannel.OnMessage = onDataChannelMessage; +#if UNITY_WEBGL + remoteDataChannel.OnTextMessage = onDataChannelTextMessage; +#endif }; onDataChannelMessage = bytes => { textReceive.text = System.Text.Encoding.UTF8.GetString(bytes); }; +#if UNITY_WEBGL + onDataChannelTextMessage = text => {textReceive.text = text;}; +#endif onDataChannelOpen = () => { sendButton.interactable = true; diff --git a/Samples~/Menu/Menu.unity b/Samples~/Menu/Menu.unity index d281113ea7..f1733ca0ca 100644 --- a/Samples~/Menu/Menu.unity +++ b/Samples~/Menu/Menu.unity @@ -7818,6 +7818,85 @@ Transform: m_Father: {fileID: 0} m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1943595677 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1943595678} + - component: {fileID: 1943595680} + - component: {fileID: 1943595679} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1943595678 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1943595677} + 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: 1369578414} + 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 &1943595679 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1943595677} + 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_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 32 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 3 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: WebGL +--- !u!222 &1943595680 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1943595677} + m_CullTransparentMesh: 1 --- !u!1 &1993818744 GameObject: m_ObjectHideFlags: 0 diff --git a/Samples~/Scripts/SceneSelectUI.cs b/Samples~/Scripts/SceneSelectUI.cs index 08f0f34c55..e2f1fdb57c 100644 --- a/Samples~/Scripts/SceneSelectUI.cs +++ b/Samples~/Scripts/SceneSelectUI.cs @@ -53,6 +53,7 @@ internal class SceneSelectUI : MonoBehaviour [SerializeField] private Button buttonSimulcast; [SerializeField] private Button buttonMetadata; [SerializeField] private Button buttonEncryption; + [SerializeField] private Button buttonWebGL; List streamSizeList = new List() { @@ -133,6 +134,7 @@ void Start() buttonSimulcast.onClick.AddListener(OnPressedSimulcastButton); buttonMetadata.onClick.AddListener(OnPressedMetadataButton); buttonEncryption.onClick.AddListener(OnPressedEncryption); + buttonWebGL.onClick.AddListener(OnPressedWebGLButton); // This sample uses Compute Shader, so almost Android devices don't work correctly. if (!SystemInfo.supportsComputeShaders) @@ -275,5 +277,10 @@ private void OnPressedEncryption() { SceneManager.LoadScene("Encryption", LoadSceneMode.Single); } + + private void OnPressedWebGLButton() + { + SceneManager.LoadScene("WebGL", LoadSceneMode.Single); + } } } diff --git a/Samples~/Unity.WebRTC.Samples.asmdef b/Samples~/Unity.WebRTC.Samples.asmdef index 72ba932a03..04cc06fd73 100644 --- a/Samples~/Unity.WebRTC.Samples.asmdef +++ b/Samples~/Unity.WebRTC.Samples.asmdef @@ -1,9 +1,10 @@ { "name": "Unity.WebRTC.Samples", + "rootNamespace": "", "references": [ - "Unity.WebRTC" + "Unity.WebRTC", + "UnityWebGLMicrophone" ], - "optionalUnityReferences": [], "includePlatforms": [], "excludePlatforms": [], "allowUnsafeCode": false, @@ -11,5 +12,6 @@ "precompiledReferences": [], "autoReferenced": true, "defineConstraints": [], - "versionDefines": [] + "versionDefines": [], + "noEngineReferences": false } \ No newline at end of file diff --git a/Samples~/WebGL.meta b/Samples~/WebGL.meta new file mode 100644 index 0000000000..a5bfc3fc4d --- /dev/null +++ b/Samples~/WebGL.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8008a7029f8f1f94d9f50d390fdfcb41 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples~/WebGL/WebGL.unity b/Samples~/WebGL/WebGL.unity new file mode 100644 index 0000000000..7fb56aa581 --- /dev/null +++ b/Samples~/WebGL/WebGL.unity @@ -0,0 +1,2386 @@ +%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: 12 + m_GIWorkflowMode: 1 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 1 + 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_LightingSettings: {fileID: 0} +--- !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 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &141885429 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 141885430} + - component: {fileID: 141885432} + - component: {fileID: 141885431} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &141885430 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 141885429} + 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: 370567958} + 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: 728.0002, y: 33.89309} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &141885431 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 141885429} + 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_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 18 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 1 + m_MaxSize: 40 + m_Alignment: 0 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: +--- !u!222 &141885432 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 141885429} + m_CullTransparentMesh: 0 +--- !u!1001 &158196923 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + m_TransformParent: {fileID: 858092574} + m_Modifications: + - target: {fileID: 4037113455314838168, guid: 7aa5bec5b1e406445af144843fe4d62c, + type: 3} + propertyPath: m_Pivot.x + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4037113455314838168, guid: 7aa5bec5b1e406445af144843fe4d62c, + type: 3} + propertyPath: m_Pivot.y + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4037113455314838168, guid: 7aa5bec5b1e406445af144843fe4d62c, + type: 3} + propertyPath: m_RootOrder + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4037113455314838168, guid: 7aa5bec5b1e406445af144843fe4d62c, + type: 3} + propertyPath: m_AnchorMax.x + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4037113455314838168, guid: 7aa5bec5b1e406445af144843fe4d62c, + type: 3} + propertyPath: m_AnchorMax.y + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4037113455314838168, guid: 7aa5bec5b1e406445af144843fe4d62c, + type: 3} + propertyPath: m_AnchorMin.x + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4037113455314838168, guid: 7aa5bec5b1e406445af144843fe4d62c, + type: 3} + propertyPath: m_AnchorMin.y + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4037113455314838168, guid: 7aa5bec5b1e406445af144843fe4d62c, + type: 3} + propertyPath: m_SizeDelta.x + value: 100 + objectReference: {fileID: 0} + - target: {fileID: 4037113455314838168, guid: 7aa5bec5b1e406445af144843fe4d62c, + type: 3} + propertyPath: m_SizeDelta.y + value: 100 + objectReference: {fileID: 0} + - target: {fileID: 4037113455314838168, guid: 7aa5bec5b1e406445af144843fe4d62c, + type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4037113455314838168, guid: 7aa5bec5b1e406445af144843fe4d62c, + type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4037113455314838168, guid: 7aa5bec5b1e406445af144843fe4d62c, + type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4037113455314838168, guid: 7aa5bec5b1e406445af144843fe4d62c, + type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4037113455314838168, guid: 7aa5bec5b1e406445af144843fe4d62c, + type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4037113455314838168, guid: 7aa5bec5b1e406445af144843fe4d62c, + type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4037113455314838168, guid: 7aa5bec5b1e406445af144843fe4d62c, + type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4037113455314838168, guid: 7aa5bec5b1e406445af144843fe4d62c, + type: 3} + propertyPath: m_AnchoredPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4037113455314838168, guid: 7aa5bec5b1e406445af144843fe4d62c, + type: 3} + propertyPath: m_AnchoredPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4037113455314838168, guid: 7aa5bec5b1e406445af144843fe4d62c, + type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4037113455314838168, guid: 7aa5bec5b1e406445af144843fe4d62c, + type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4037113455314838168, guid: 7aa5bec5b1e406445af144843fe4d62c, + type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4037113455314838175, guid: 7aa5bec5b1e406445af144843fe4d62c, + type: 3} + propertyPath: m_Name + value: BackButtonManager + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 7aa5bec5b1e406445af144843fe4d62c, type: 3} +--- !u!224 &158196924 stripped +RectTransform: + m_CorrespondingSourceObject: {fileID: 4037113455314838168, guid: 7aa5bec5b1e406445af144843fe4d62c, + type: 3} + m_PrefabInstance: {fileID: 158196923} + m_PrefabAsset: {fileID: 0} +--- !u!1 &370567957 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 370567958} + - component: {fileID: 370567959} + m_Layer: 5 + m_Name: vertical layout + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &370567958 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 370567957} + 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: 372881754} + - {fileID: 1198425309} + - {fileID: 2015830100} + - {fileID: 141885430} + m_Father: {fileID: 858092574} + 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 &370567959 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 370567957} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 59f8146938fff824cb5fd77236b75775, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 50 + m_Bottom: 0 + m_ChildAlignment: 4 + m_Spacing: 0 + m_ChildForceExpandWidth: 1 + m_ChildForceExpandHeight: 1 + m_ChildControlWidth: 0 + m_ChildControlHeight: 0 + m_ChildScaleWidth: 0 + m_ChildScaleHeight: 0 + m_ReverseArrangement: 0 +--- !u!1 &372881753 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 372881754} + - component: {fileID: 372881755} + m_Layer: 5 + m_Name: horizontal layout + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &372881754 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 372881753} + 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: 1839397353} + - {fileID: 2123783123} + m_Father: {fileID: 370567958} + 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: 699.99994, y: 240} + m_Pivot: {x: 0.5, y: 1} +--- !u!114 &372881755 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 372881753} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 30649d3a9faa99c48a7b1166b86bf2a0, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_ChildAlignment: 4 + m_Spacing: 10 + m_ChildForceExpandWidth: 1 + m_ChildForceExpandHeight: 1 + m_ChildControlWidth: 0 + m_ChildControlHeight: 0 + m_ChildScaleWidth: 0 + m_ChildScaleHeight: 0 + m_ReverseArrangement: 0 +--- !u!1 &483628950 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 483628951} + - component: {fileID: 483628953} + - component: {fileID: 483628952} + m_Layer: 5 + m_Name: RemoteCandidate + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &483628951 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 483628950} + 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: 1039299225} + m_Father: {fileID: 1198425309} + 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: 160, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &483628952 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 483628950} + 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_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + 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: 0 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: 'RemoteCandidateId:' +--- !u!222 &483628953 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 483628950} + m_CullTransparentMesh: 1 +--- !u!1 &562611379 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 562611380} + - component: {fileID: 562611382} + - component: {fileID: 562611381} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &562611380 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 562611379} + 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: 1344461798} + 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 &562611381 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 562611379} + 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_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 18 + 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: HangUp +--- !u!222 &562611382 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 562611379} + m_CullTransparentMesh: 0 +--- !u!1 &626388471 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 626388472} + - component: {fileID: 626388474} + - component: {fileID: 626388473} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &626388472 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 626388471} + 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: 2066401218} + 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 &626388473 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 626388471} + 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_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 18 + 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: Call +--- !u!222 &626388474 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 626388471} + m_CullTransparentMesh: 0 +--- !u!1 &645746053 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 645746055} + - component: {fileID: 645746054} + m_Layer: 0 + m_Name: Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &645746054 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 645746053} + m_Enabled: 1 + serializedVersion: 10 + m_Type: 1 + m_Shape: 0 + 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 + 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: 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_UseViewFrustumForShadowCasterCull: 1 + m_ShadowRadius: 0 + m_ShadowAngle: 0 +--- !u!4 &645746055 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 645746053} + 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: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!1 &814089928 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 814089931} + - component: {fileID: 814089930} + m_Layer: 0 + m_Name: UICamera + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!20 &814089930 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 814089928} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 2 + m_BackGroundColor: {r: 1, g: 1, b: 1, 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: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &814089931 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 814089928} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 823.3642, y: 439.6452, z: -229.16667} + 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 &858092570 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 858092574} + - component: {fileID: 858092573} + - component: {fileID: 858092572} + - component: {fileID: 858092571} + m_Layer: 5 + m_Name: Canvas + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &858092571 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 858092570} + 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 &858092572 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 858092570} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UiScaleMode: 1 + 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 + m_PresetInfoIsWorld: 0 +--- !u!223 &858092573 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 858092570} + 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 &858092574 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 858092570} + 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: 370567958} + - {fileID: 158196924} + m_Father: {fileID: 0} + 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: 0, y: 0} +--- !u!1 &906436438 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 906436439} + - component: {fileID: 906436442} + - component: {fileID: 906436441} + - component: {fileID: 906436440} + m_Layer: 5 + m_Name: RestartIce + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &906436439 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 906436438} + 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: 1590714497} + m_Father: {fileID: 2015830100} + 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: 100, y: 32} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &906436440 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 906436438} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + 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: 0 + m_TargetGraphic: {fileID: 906436441} + m_OnClick: + m_PersistentCalls: + m_Calls: [] +--- !u!114 &906436441 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 906436438} + 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_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + 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 &906436442 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 906436438} + m_CullTransparentMesh: 0 +--- !u!1 &934963286 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 934963289} + - component: {fileID: 934963288} + - component: {fileID: 934963287} + - component: {fileID: 934963290} + m_Layer: 0 + m_Name: MainCamera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &934963287 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 934963286} + m_Enabled: 1 +--- !u!20 &934963288 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 934963286} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 1} + 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: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &934963289 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 934963286} + m_LocalRotation: {x: -0.08717229, y: 0.8995905, z: -0.21045253, w: -0.37262273} + m_LocalPosition: {x: 826.31604, y: 441.7115, z: -226.21486} + 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!114 &934963290 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 934963286} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 112b75fc4a0640e1a80310351b92471d, type: 3} + m_Name: + m_EditorClassIdentifier: + startButton: {fileID: 1290512353} + callButton: {fileID: 2066401219} + restartButton: {fileID: 906436440} + hangUpButton: {fileID: 1344461799} + localCandidateId: {fileID: 1021020669} + remoteCandidateId: {fileID: 1039299226} + cam: {fileID: 934963288} + sourceImage: {fileID: 1839397354} + receiveImage: {fileID: 2123783124} + rotateObject: {fileID: 1823897046} +--- !u!1 &989765905 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 989765906} + - component: {fileID: 989765908} + - component: {fileID: 989765907} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &989765906 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 989765905} + 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: 1290512352} + 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 &989765907 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 989765905} + 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_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 18 + 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: Start +--- !u!222 &989765908 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 989765905} + m_CullTransparentMesh: 0 +--- !u!1 &1021020667 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1021020668} + - component: {fileID: 1021020670} + - component: {fileID: 1021020669} + m_Layer: 5 + m_Name: LocalCandidateId + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1021020668 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1021020667} + 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: 1314460981} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 1} + m_AnchorMax: {x: 0, y: 1} + m_AnchoredPosition: {x: 220, y: 0} + m_SizeDelta: {x: 200, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1021020669 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1021020667} + 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_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + 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: 1 + m_MaxSize: 40 + m_Alignment: 6 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: NotConnected +--- !u!222 &1021020670 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1021020667} + m_CullTransparentMesh: 1 +--- !u!1 &1039299224 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1039299225} + - component: {fileID: 1039299227} + - component: {fileID: 1039299226} + m_Layer: 5 + m_Name: RemoteCandidateId + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1039299225 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1039299224} + 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: 483628951} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 1} + m_AnchorMax: {x: 0, y: 1} + m_AnchoredPosition: {x: 235, y: 0} + m_SizeDelta: {x: 200, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1039299226 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1039299224} + 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_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + 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: 1 + m_MaxSize: 40 + m_Alignment: 6 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: NotConnected +--- !u!222 &1039299227 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1039299224} + m_CullTransparentMesh: 1 +--- !u!1 &1068560632 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1068560635} + - component: {fileID: 1068560634} + - component: {fileID: 1068560633} + m_Layer: 0 + m_Name: EventSystem + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1068560633 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1068560632} + 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 &1068560634 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1068560632} + 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 &1068560635 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1068560632} + 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} +--- !u!1 &1198425308 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1198425309} + - component: {fileID: 1198425310} + m_Layer: 5 + m_Name: horizontal layout + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1198425309 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1198425308} + 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: 1314460981} + - {fileID: 483628951} + m_Father: {fileID: 370567958} + 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: 728.004, y: 33.47383} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1198425310 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1198425308} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 30649d3a9faa99c48a7b1166b86bf2a0, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_ChildAlignment: 0 + m_Spacing: 20 + m_ChildForceExpandWidth: 1 + m_ChildForceExpandHeight: 1 + m_ChildControlWidth: 0 + m_ChildControlHeight: 0 + m_ChildScaleWidth: 0 + m_ChildScaleHeight: 0 + m_ReverseArrangement: 0 +--- !u!1 &1290512351 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1290512352} + - component: {fileID: 1290512355} + - component: {fileID: 1290512354} + - component: {fileID: 1290512353} + m_Layer: 5 + m_Name: Start + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1290512352 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1290512351} + 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: 989765906} + m_Father: {fileID: 2015830100} + 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: 100, y: 32} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1290512353 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1290512351} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + 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: 1290512354} + m_OnClick: + m_PersistentCalls: + m_Calls: [] +--- !u!114 &1290512354 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1290512351} + 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_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + 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 &1290512355 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1290512351} + m_CullTransparentMesh: 0 +--- !u!1 &1314460980 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1314460981} + - component: {fileID: 1314460983} + - component: {fileID: 1314460982} + m_Layer: 5 + m_Name: LocalCandidate + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1314460981 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1314460980} + 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: 1021020668} + m_Father: {fileID: 1198425309} + 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: 160, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1314460982 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1314460980} + 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_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + 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: 0 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: 'LocalCandidateId:' +--- !u!222 &1314460983 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1314460980} + m_CullTransparentMesh: 1 +--- !u!1 &1344461797 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1344461798} + - component: {fileID: 1344461801} + - component: {fileID: 1344461800} + - component: {fileID: 1344461799} + m_Layer: 5 + m_Name: HangUp + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1344461798 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1344461797} + 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: 562611380} + m_Father: {fileID: 2015830100} + 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: 100, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1344461799 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1344461797} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + 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: 0 + m_TargetGraphic: {fileID: 1344461800} + m_OnClick: + m_PersistentCalls: + m_Calls: [] +--- !u!114 &1344461800 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1344461797} + 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_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + 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 &1344461801 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1344461797} + m_CullTransparentMesh: 0 +--- !u!1 &1590714496 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1590714497} + - component: {fileID: 1590714499} + - component: {fileID: 1590714498} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1590714497 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1590714496} + 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: 906436439} + 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 &1590714498 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1590714496} + 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_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 18 + 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: RestartIce +--- !u!222 &1590714499 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1590714496} + m_CullTransparentMesh: 0 +--- !u!1 &1823897042 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1823897046} + - component: {fileID: 1823897045} + - component: {fileID: 1823897044} + - component: {fileID: 1823897043} + m_Layer: 0 + m_Name: Cube + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!65 &1823897043 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1823897042} + m_Material: {fileID: 0} + m_IsTrigger: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Size: {x: 1, y: 1, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!23 &1823897044 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1823897042} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &1823897045 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1823897042} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!4 &1823897046 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1823897042} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 823.3642, y: 439.6452, z: -229.16667} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 5 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1839397352 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1839397353} + - component: {fileID: 1839397355} + - component: {fileID: 1839397354} + m_Layer: 5 + m_Name: SourceRawImage + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1839397353 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1839397352} + 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: 372881754} + 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: 320, y: 240} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1839397354 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1839397352} + 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: 0, g: 0, b: 0, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + 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 &1839397355 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1839397352} + m_CullTransparentMesh: 0 +--- !u!1 &2015830099 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2015830100} + - component: {fileID: 2015830101} + m_Layer: 5 + m_Name: horizontal layout + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &2015830100 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2015830099} + 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: 1290512352} + - {fileID: 2066401218} + - {fileID: 906436439} + - {fileID: 1344461798} + m_Father: {fileID: 370567958} + 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: 728.004, y: 33.47383} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &2015830101 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2015830099} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 30649d3a9faa99c48a7b1166b86bf2a0, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_ChildAlignment: 0 + m_Spacing: 20 + m_ChildForceExpandWidth: 1 + m_ChildForceExpandHeight: 1 + m_ChildControlWidth: 0 + m_ChildControlHeight: 0 + m_ChildScaleWidth: 0 + m_ChildScaleHeight: 0 + m_ReverseArrangement: 0 +--- !u!1 &2066401217 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2066401218} + - component: {fileID: 2066401221} + - component: {fileID: 2066401220} + - component: {fileID: 2066401219} + m_Layer: 5 + m_Name: Call + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &2066401218 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2066401217} + 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: 626388472} + m_Father: {fileID: 2015830100} + 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: 100, y: 32} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &2066401219 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2066401217} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + 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: 0 + m_TargetGraphic: {fileID: 2066401220} + m_OnClick: + m_PersistentCalls: + m_Calls: [] +--- !u!114 &2066401220 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2066401217} + 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_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + 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 &2066401221 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2066401217} + m_CullTransparentMesh: 0 +--- !u!1 &2123783122 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2123783123} + - component: {fileID: 2123783125} + - component: {fileID: 2123783124} + m_Layer: 5 + m_Name: ReceiveRawImage + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &2123783123 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2123783122} + 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: 372881754} + 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: 320, y: 240} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &2123783124 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2123783122} + 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: 0, g: 0, b: 0, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + 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 &2123783125 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2123783122} + m_CullTransparentMesh: 0 diff --git a/Samples~/WebGL/WebGL.unity.meta b/Samples~/WebGL/WebGL.unity.meta new file mode 100644 index 0000000000..63700816dc --- /dev/null +++ b/Samples~/WebGL/WebGL.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8566d76aec4c20e4e819f649f8c65945 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples~/WebGL/WebGLSample.cs b/Samples~/WebGL/WebGLSample.cs new file mode 100644 index 0000000000..b9c5a60247 --- /dev/null +++ b/Samples~/WebGL/WebGLSample.cs @@ -0,0 +1,424 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using Unity.WebRTC; +using Unity.WebRTC.Samples; +using UnityEngine.UI; +using static Unity.WebRTC.MediaStream; +using Button = UnityEngine.UI.Button; + +class WebGLSample : MonoBehaviour +{ +#pragma warning disable 0649 + [SerializeField] + private Button startButton; + [SerializeField] + private Button callButton; + [SerializeField] + private Button restartButton; + [SerializeField] + private Button hangUpButton; + [SerializeField] + private Text localCandidateId; + [SerializeField] + private Text remoteCandidateId; + + [SerializeField] + private Camera cam; + [SerializeField] + private RawImage sourceImage; + [SerializeField] + private RawImage receiveImage; + [SerializeField] + private Transform rotateObject; +#pragma warning restore 0649 + + private RTCPeerConnection _pc1, _pc2; + private List pc1Senders; + private MediaStream videoStream, receiveStream; + private DelegateOnIceConnectionChange pc1OnIceConnectionChange; + private DelegateOnIceConnectionChange pc2OnIceConnectionChange; + private DelegateOnIceCandidate pc1OnIceCandidate; + private DelegateOnIceCandidate pc2OnIceCandidate; + private DelegateOnTrack pc2Ontrack; + private DelegateOnNegotiationNeeded pc1OnNegotiationNeeded; + private bool videoUpdateStarted; + + private const int width = 1280; + private const int height = 720; + + private void Awake() + { + startButton.onClick.AddListener(OnStart); + callButton.onClick.AddListener(Call); + restartButton.onClick.AddListener(RestartIce); + hangUpButton.onClick.AddListener(HangUp); + receiveStream = new MediaStream(); + } + + private void Start() + { + pc1Senders = new List(); + callButton.interactable = false; + restartButton.interactable = false; + hangUpButton.interactable = false; + + pc1OnIceConnectionChange = state => { OnIceConnectionChange(_pc1, state); }; + pc2OnIceConnectionChange = state => { OnIceConnectionChange(_pc2, state); }; + pc1OnIceCandidate = candidate => { OnIceCandidate(_pc1, candidate); }; + pc2OnIceCandidate = candidate => { OnIceCandidate(_pc2, candidate); }; + pc2Ontrack = e => { receiveStream.AddTrack(e.Track); }; + pc1OnNegotiationNeeded = () => { StartCoroutine(PeerNegotiationNeeded(_pc1)); }; + + receiveStream.OnAddTrack = e => + { + if (e.Track is VideoStreamTrack track) + { + receiveImage.texture = track.Texture; + receiveImage.color = Color.white; + } + }; + } + + private void OnStart() + { + startButton.interactable = false; + callButton.interactable = true; + + if (videoStream == null) + { +#if !UNITY_WEBGL + Debug.LogWarning("UserMedia is only available in WebGL builds, this is equal to PeerConnectionSample"); + videoStream = cam.CaptureStream(width, height); + sourceImage.texture = cam.targetTexture; + sourceImage.color = Color.white; +#else + MediaStreamConstraints constraints = new MediaStreamConstraints(); + videoStream = new MediaStream(); + videoStream.AddUserMedia(constraints); + videoStream.OnAddTrack = e => + { + if (e.Track is VideoStreamTrack track) + { + sourceImage.texture = track.Texture; + sourceImage.color = Color.white; + } + }; +#endif + + } + } + + private void Update() + { + if (rotateObject != null) + { + float t = Time.deltaTime; + rotateObject.Rotate(100 * t, 200 * t, 300 * t); + } + } + + private static RTCConfiguration GetSelectedSdpSemantics() + { + RTCConfiguration config = default; + config.iceServers = new[] {new RTCIceServer {urls = new[] {"stun:stun.l.google.com:19302"}}}; + + return config; + } + + private void OnIceConnectionChange(RTCPeerConnection pc, RTCIceConnectionState state) + { + Debug.Log($"{GetName(pc)} IceConnectionState: {state}"); + + if (state == RTCIceConnectionState.Connected || state == RTCIceConnectionState.Completed) + { + StartCoroutine(CheckStats(pc)); + } + } + + // Display the video codec that is actually used. + IEnumerator CheckStats(RTCPeerConnection pc) + { + yield return new WaitForSeconds(0.1f); + + if (pc == null) + yield break; + + var op = pc.GetStats(); + + yield return op; + + if (op.IsError) + { + Debug.LogErrorFormat("RTCPeerConnection.GetStats failed: {0}", op.Error); + + yield break; + } + + RTCStatsReport report = op.Value; + RTCIceCandidatePairStats activeCandidatePairStats = null; + RTCIceCandidateStats remoteCandidateStats = null; + + foreach (var transportStatus in report.Stats.Values.OfType()) + { + if (report.Stats.TryGetValue(transportStatus.selectedCandidatePairId, out var tmp)) + { + activeCandidatePairStats = tmp as RTCIceCandidatePairStats; + } + } + + if (activeCandidatePairStats == null || string.IsNullOrEmpty(activeCandidatePairStats.remoteCandidateId)) + { + yield break; + } + + foreach (var iceCandidateStatus in report.Stats.Values.OfType()) + { + if (iceCandidateStatus.Id == activeCandidatePairStats.remoteCandidateId) + { + remoteCandidateStats = iceCandidateStatus; + } + } + + if (remoteCandidateStats == null || string.IsNullOrEmpty(remoteCandidateStats.Id)) + { + yield break; + } + + Debug.Log($"{GetName(pc)} candidate stats Id:{remoteCandidateStats.Id}, Type:{remoteCandidateStats.candidateType}"); + var updateText = GetName(pc) == "pc1" ? localCandidateId : remoteCandidateId; + updateText.text = remoteCandidateStats.Id; + } + + IEnumerator PeerNegotiationNeeded(RTCPeerConnection pc) + { + var op = pc.CreateOffer(); + + yield return op; + + if (!op.IsError) + { + if (pc.SignalingState != RTCSignalingState.Stable) + { + Debug.LogError($"{GetName(pc)} signaling state is not stable."); + + yield break; + } + + yield return StartCoroutine(OnCreateOfferSuccess(pc, op.Desc)); + } + else + { + OnCreateSessionDescriptionError(op.Error); + } + } + + private void AddTracks() + { + foreach (var track in videoStream.GetTracks()) + { + pc1Senders.Add(_pc1.AddTrack(track, videoStream)); + } + + if (!videoUpdateStarted) + { + StartCoroutine(WebRTC.Update()); + videoUpdateStarted = true; + } + } + + private void RemoveTracks() + { + foreach (var sender in pc1Senders) + { + _pc1.RemoveTrack(sender); + } + + pc1Senders.Clear(); + + MediaStreamTrack[] tracks = receiveStream.GetTracks().ToArray(); + + foreach (var track in tracks) + { + receiveStream.RemoveTrack(track); + track.Dispose(); + } + } + + private void Call() + { + callButton.interactable = false; + hangUpButton.interactable = true; + restartButton.interactable = true; + + var configuration = GetSelectedSdpSemantics(); + _pc1 = new RTCPeerConnection(ref configuration); + _pc1.OnIceCandidate = pc1OnIceCandidate; + _pc1.OnIceConnectionChange = pc1OnIceConnectionChange; + _pc1.OnNegotiationNeeded = pc1OnNegotiationNeeded; + _pc2 = new RTCPeerConnection(ref configuration); + _pc2.OnIceCandidate = pc2OnIceCandidate; + _pc2.OnIceConnectionChange = pc2OnIceConnectionChange; + _pc2.OnTrack = pc2Ontrack; + + AddTracks(); + } + + private void RestartIce() + { + restartButton.interactable = false; + + _pc1.RestartIce(); + } + + private void HangUp() + { + RemoveTracks(); + + _pc1.Close(); + _pc2.Close(); + _pc1.Dispose(); + _pc2.Dispose(); + _pc1 = null; + _pc2 = null; + + callButton.interactable = true; + restartButton.interactable = false; + hangUpButton.interactable = false; + + receiveImage.color = Color.black; + } + + private void OnIceCandidate(RTCPeerConnection pc, RTCIceCandidate candidate) + { + GetOtherPc(pc).AddIceCandidate(candidate); + Debug.Log($"{GetName(pc)} ICE candidate:\n {candidate.Candidate}"); + } + + private string GetName(RTCPeerConnection pc) + { + return (pc == _pc1) ? "pc1" : "pc2"; + } + + private RTCPeerConnection GetOtherPc(RTCPeerConnection pc) + { + return (pc == _pc1) ? _pc2 : _pc1; + } + + private IEnumerator OnCreateOfferSuccess(RTCPeerConnection pc, RTCSessionDescription desc) + { + Debug.Log($"Offer from {GetName(pc)}\n{desc.sdp}"); + Debug.Log($"{GetName(pc)} setLocalDescription start"); + var op = pc.SetLocalDescription(ref desc); + + yield return op; + + if (!op.IsError) + { + OnSetLocalSuccess(pc); + } + else + { + var error = op.Error; + OnSetSessionDescriptionError(ref error); + + yield break; + } + + var otherPc = GetOtherPc(pc); + Debug.Log($"{GetName(otherPc)} setRemoteDescription start"); + var op2 = otherPc.SetRemoteDescription(ref desc); + + yield return op2; + + if (!op2.IsError) + { + OnSetRemoteSuccess(otherPc); + } + else + { + var error = op2.Error; + OnSetSessionDescriptionError(ref error); + + yield break; + } + + Debug.Log($"{GetName(otherPc)} createAnswer start"); + + // Since the 'remote' side has no media stream we need + // to pass in the right constraints in order for it to + // accept the incoming offer of audio and video. + + var op3 = otherPc.CreateAnswer(); + + yield return op3; + + if (!op3.IsError) + { + yield return OnCreateAnswerSuccess(otherPc, op3.Desc); + } + else + { + OnCreateSessionDescriptionError(op3.Error); + } + } + + private void OnSetLocalSuccess(RTCPeerConnection pc) + { + Debug.Log($"{GetName(pc)} SetLocalDescription complete"); + } + + void OnSetSessionDescriptionError(ref RTCError error) + { + Debug.LogError($"Error Detail Type: {error.message}"); + HangUp(); + } + + private void OnSetRemoteSuccess(RTCPeerConnection pc) + { + Debug.Log($"{GetName(pc)} SetRemoteDescription complete"); + } + + IEnumerator OnCreateAnswerSuccess(RTCPeerConnection pc, RTCSessionDescription desc) + { + Debug.Log($"Answer from {GetName(pc)}:\n{desc.sdp}"); + Debug.Log($"{GetName(pc)} setLocalDescription start"); + var op = pc.SetLocalDescription(ref desc); + + yield return op; + + if (!op.IsError) + { + OnSetLocalSuccess(pc); + } + else + { + var error = op.Error; + OnSetSessionDescriptionError(ref error); + } + + var otherPc = GetOtherPc(pc); + Debug.Log($"{GetName(otherPc)} setRemoteDescription start"); + + var op2 = otherPc.SetRemoteDescription(ref desc); + + yield return op2; + + if (!op2.IsError) + { + OnSetRemoteSuccess(otherPc); + } + else + { + var error = op2.Error; + OnSetSessionDescriptionError(ref error); + } + } + + private static void OnCreateSessionDescriptionError(RTCError error) + { + Debug.LogError($"Error Detail Type: {error.message}"); + } +} diff --git a/Samples~/WebGL/WebGLSample.cs.meta b/Samples~/WebGL/WebGLSample.cs.meta new file mode 100644 index 0000000000..9529c96831 --- /dev/null +++ b/Samples~/WebGL/WebGLSample.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 112b75fc4a0640e1a80310351b92471d +timeCreated: 1625599611 \ No newline at end of file diff --git a/Tests/Editor/PluginTest.cs b/Tests/Editor/PluginTest.cs index 12938c5757..4a3d1897b1 100644 --- a/Tests/Editor/PluginTest.cs +++ b/Tests/Editor/PluginTest.cs @@ -36,7 +36,6 @@ public static void IsPluginLoaded() [Test] public static void CheckPluginImportSettings() { - string[] guids = AssetDatabase.FindAssets("", new[] { "Packages/com.unity.webrtc/Runtime/Plugins" }); foreach (string guid in guids) { @@ -47,11 +46,12 @@ public static void CheckPluginImportSettings() continue; PluginImporter pluginImporter = assetImporter as PluginImporter; Assert.That(pluginImporter, Is.Not.Null); - Assert.That(pluginImporter.isPreloaded, Is.True); + if(!path.Contains("WebGL")) + { + Assert.That(pluginImporter.isPreloaded, Is.True); + } } - } - } } //namespace Unity.WebRTC.EditorTest diff --git a/Tests/Runtime/AudioStreamTrackTest.cs b/Tests/Runtime/AudioStreamTrackTest.cs index 33d1c98bf3..2e5fdd6186 100644 --- a/Tests/Runtime/AudioStreamTrackTest.cs +++ b/Tests/Runtime/AudioStreamTrackTest.cs @@ -12,6 +12,7 @@ namespace Unity.WebRTC.RuntimeTest class AudioStreamTrackTest { [UnityTest] + [UnityPlatform(exclude = new[] { RuntimePlatform.WebGLPlayer})] [Timeout(5000)] public IEnumerator Constructor() { @@ -110,6 +111,7 @@ public IEnumerator GCCollect() } [UnityTest] + [UnityPlatform(exclude = new[] { RuntimePlatform.WebGLPlayer})] [Timeout(5000)] public IEnumerator AddMultiAudioTrack() { @@ -182,7 +184,7 @@ public void AudioStreamTrackInstantiateMultiple() UnityEngine.Object.DestroyImmediate(obj1); UnityEngine.Object.DestroyImmediate(obj2); } - +#if !UNITY_WEBGL [Test] public void AudioStreamTrackSetData() { @@ -234,6 +236,7 @@ public void AudioStreamTrackPlayAudio() UnityEngine.Object.DestroyImmediate(obj); } + [Test] public void AudioStreamRenderer() { @@ -243,5 +246,6 @@ public void AudioStreamRenderer() renderer.Dispose(); UnityEngine.Object.DestroyImmediate(obj); } +#endif } } diff --git a/Tests/Runtime/ContextTest.cs b/Tests/Runtime/ContextTest.cs index 2cf9710aee..82d54d84bd 100644 --- a/Tests/Runtime/ContextTest.cs +++ b/Tests/Runtime/ContextTest.cs @@ -2,6 +2,8 @@ using NUnit.Framework; using UnityEngine; +using Debug = UnityEngine.Debug; + namespace Unity.WebRTC.RuntimeTest { class ContextTest @@ -96,7 +98,11 @@ public void CreateAndDeleteVideoTrack() var rt = new UnityEngine.RenderTexture(width, height, 0, format); rt.Create(); var source = context.CreateVideoTrackSource(); +#if UNITY_WEBGL + var track = context.CreateVideoTrack(source, System.IntPtr.Zero, width, height); +#else var track = context.CreateVideoTrack("video", source); +#endif context.DeleteRefPtr(track); context.DeleteRefPtr(source); UnityEngine.Object.DestroyImmediate(rt); diff --git a/Tests/Runtime/DataChannelTest.cs b/Tests/Runtime/DataChannelTest.cs index 0e05898f28..a5e0f2e991 100644 --- a/Tests/Runtime/DataChannelTest.cs +++ b/Tests/Runtime/DataChannelTest.cs @@ -81,7 +81,7 @@ public void CreateDataChannelFailed() [UnityTest] [Timeout(5000)] - [UnityPlatform(exclude = new[] { RuntimePlatform.IPhonePlayer })] + [UnityPlatform(exclude = new[] { RuntimePlatform.IPhonePlayer,RuntimePlatform.WebGLPlayer })] public IEnumerator SendThrowsException() { byte[] message1 = { 1, 2, 3 }; @@ -126,7 +126,7 @@ public IEnumerator SendThrowsException() [UnityTest] [Timeout(5000)] - [UnityPlatform(exclude = new[] { RuntimePlatform.IPhonePlayer })] + [UnityPlatform(exclude = new[] { RuntimePlatform.IPhonePlayer,RuntimePlatform.WebGLPlayer })] public IEnumerator CreateDataChannelMultiple() { var test = new MonoBehaviourTest(); diff --git a/Tests/Runtime/IceCandidateTest.cs b/Tests/Runtime/IceCandidateTest.cs index 387a47b4f2..e305118df5 100644 --- a/Tests/Runtime/IceCandidateTest.cs +++ b/Tests/Runtime/IceCandidateTest.cs @@ -1,5 +1,7 @@ using System; using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; namespace Unity.WebRTC.RuntimeTest { @@ -11,7 +13,13 @@ public void Construct() Assert.Throws(() => new RTCIceCandidate()); } + [Test] + // TODO: Remove [UnityPlatform(exclude = new[] { RuntimePlatform.WebGLPlayer })] + // Fix test for WebGL platform. WebRTC returns null for relatedAddress when type is host + // https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidate/relatedAddress - is null for host + [UnityPlatform(exclude = new[] { RuntimePlatform.WebGLPlayer })] + [Category("IceCandidate")] public void ConstructWithOption() { var option = new RTCIceCandidateInit diff --git a/Tests/Runtime/MediaStreamTest.cs b/Tests/Runtime/MediaStreamTest.cs index b2a96b613a..753786488e 100644 --- a/Tests/Runtime/MediaStreamTest.cs +++ b/Tests/Runtime/MediaStreamTest.cs @@ -49,7 +49,8 @@ public void RegisterDelegate() [UnityTest] [Timeout(5000)] [ConditionalIgnore(ConditionalIgnore.UnsupportedPlatformOpenGL, "Not support VideoStreamTrack for OpenGL")] - [UnityPlatform(exclude = new[] { RuntimePlatform.LinuxPlayer, RuntimePlatform.Android })] + [UnityPlatform(exclude = new[] { RuntimePlatform.LinuxPlayer, RuntimePlatform.Android, RuntimePlatform.WebGLPlayer })] + [Category("MediaStream")] public IEnumerator VideoStreamAddTrackAndRemoveTrack() { var width = 256; @@ -118,6 +119,7 @@ public void AddAndRemoveAudioTrack() } [UnityTest] + [UnityPlatform(exclude = new[] { RuntimePlatform.WebGLPlayer})] [Timeout(5000)] [ConditionalIgnore(ConditionalIgnore.UnsupportedPlatformOpenGL, "Not support VideoStreamTrack for OpenGL")] public IEnumerator CameraCaptureStream() @@ -140,6 +142,7 @@ public IEnumerator CameraCaptureStream() } [UnityTest] + [UnityPlatform(exclude = new[] { RuntimePlatform.WebGLPlayer})] [Timeout(5000)] [ConditionalIgnore(ConditionalIgnore.UnsupportedPlatformOpenGL, "Not support VideoStreamTrack for OpenGL")] public IEnumerator CaptureStream() @@ -167,6 +170,7 @@ public IEnumerator CaptureStream() } [UnityTest] + [UnityPlatform(exclude = new[] { RuntimePlatform.WebGLPlayer})] [Timeout(5000)] [ConditionalIgnore(ConditionalIgnore.UnsupportedPlatformOpenGL, "Not support VideoStreamTrack for OpenGL")] public IEnumerator SenderGetStats() @@ -214,6 +218,7 @@ public IEnumerator SenderGetStats() } [UnityTest] + [UnityPlatform(exclude = new[] { RuntimePlatform.WebGLPlayer})] [Timeout(5000)] [ConditionalIgnore(ConditionalIgnore.UnsupportedPlatformOpenGL, "Not support VideoStreamTrack for OpenGL")] public IEnumerator ReceiverGetStats() @@ -257,6 +262,7 @@ public IEnumerator ReceiverGetStats() } [UnityTest] + [UnityPlatform(exclude = new[] { RuntimePlatform.WebGLPlayer})] [Timeout(5000)] [ConditionalIgnore(ConditionalIgnore.UnsupportedPlatformOpenGL, "Not support VideoStreamTrack for OpenGL")] public IEnumerator SetParametersReturnNoError() @@ -387,7 +393,7 @@ public IEnumerator AddAndRemoveTrack() // todo::(kazuki) Test execution timed out on linux standalone [UnityTest] [Timeout(5000)] - [UnityPlatform(exclude = new[] { RuntimePlatform.LinuxPlayer })] + [UnityPlatform(exclude = new[] { RuntimePlatform.LinuxPlayer, RuntimePlatform.WebGLPlayer})] [ConditionalIgnore(ConditionalIgnore.UnsupportedPlatformOpenGL, "Not support VideoStreamTrack for OpenGL")] public IEnumerator OnAddTrackDelegatesWithEvent() { @@ -447,6 +453,7 @@ public IEnumerator OnAddTrackDelegatesWithEvent() [UnityTest] [Timeout(5000)] + [UnityPlatform(exclude = new[] { RuntimePlatform.WebGLPlayer})] public IEnumerator ReceiverGetStreams() { var obj = new GameObject("audio"); diff --git a/Tests/Runtime/MediaStreamTrackTest.cs b/Tests/Runtime/MediaStreamTrackTest.cs index 956343f130..17b953046f 100644 --- a/Tests/Runtime/MediaStreamTrackTest.cs +++ b/Tests/Runtime/MediaStreamTrackTest.cs @@ -56,11 +56,18 @@ public void EqualIdWithAudioTrack() [Test] [ConditionalIgnore(ConditionalIgnore.UnsupportedPlatformOpenGL, "Not support VideoStreamTrack for OpenGL")] + // TODO: Remove [UnityPlatform(exclude = new[] { RuntimePlatform.WebGLPlayer })] + // Requires video refactoring + [UnityPlatform(exclude = new[] { RuntimePlatform.WebGLPlayer })] public void EqualIdWithVideoTrack() { var guid = Guid.NewGuid().ToString(); var source = new VideoTrackSource(); +#if UNITY_WEBGL + var track = new VideoStreamTrack(WebRTC.Context.CreateVideoTrack(source.self, IntPtr.Zero, 256, 256)); +#else var track = new VideoStreamTrack(WebRTC.Context.CreateVideoTrack(guid, source.self)); +#endif Assert.That(track, Is.Not.Null); Assert.That(track.Id, Is.EqualTo(guid)); track.Dispose(); @@ -85,6 +92,9 @@ public void AccessAfterDisposed() [Test] [ConditionalIgnore(ConditionalIgnore.UnsupportedPlatformOpenGL, "Not support VideoStreamTrack for OpenGL")] + // TODO: Remove [UnityPlatform(exclude = new[] { RuntimePlatform.WebGLPlayer })] + // Requires video refactoring + [UnityPlatform(exclude = new[] { RuntimePlatform.WebGLPlayer })] public void ConstructorThrowsExceptionWhenInvalidGraphicsFormat() { var width = 256; @@ -116,9 +126,9 @@ public void ConstructThrowsExceptionWhenSmallTexture() // todo(kazuki): Crash on windows standalone player [UnityTest] [Timeout(5000)] - [UnityPlatform(exclude = new[] { RuntimePlatform.LinuxPlayer, RuntimePlatform.WindowsPlayer })] + [UnityPlatform(exclude = new[] { RuntimePlatform.LinuxPlayer, RuntimePlatform.WindowsPlayer, RuntimePlatform.WebGLPlayer})] [ConditionalIgnore(ConditionalIgnore.UnsupportedPlatformOpenGL, "Not support VideoStreamTrack for OpenGL")] - + [Category("MediaStreamTrack")] public IEnumerator VideoStreamTrackEnabled() { var width = 256; @@ -154,8 +164,9 @@ public IEnumerator VideoStreamTrackEnabled() // todo::(kazuki) Test execution timed out on linux standalone [UnityTest] [Timeout(5000)] - [UnityPlatform(exclude = new[] { RuntimePlatform.LinuxPlayer })] + [UnityPlatform(exclude = new[] { RuntimePlatform.LinuxPlayer , RuntimePlatform.WebGLPlayer})] [ConditionalIgnore(ConditionalIgnore.UnsupportedPlatformOpenGL, "Not support VideoStreamTrack for OpenGL")] + [Category("MediaStreamTrack")] public IEnumerator CaptureStreamTrack() { var camObj = new GameObject("Camera"); @@ -220,8 +231,9 @@ public void VideoStreamTrackDisposeImmediately() [UnityTest, LongRunning] [Timeout(5000)] - [UnityPlatform(exclude = new[] { RuntimePlatform.LinuxPlayer })] + [UnityPlatform(exclude = new[] { RuntimePlatform.LinuxPlayer, RuntimePlatform.WebGLPlayer })] [ConditionalIgnore(ConditionalIgnore.UnsupportedPlatformOpenGL, "Not support VideoStreamTrack for OpenGL")] + [Category("MediaStreamTrack")] public IEnumerator VideoStreamTrackInstantiateMultiple() { var width = 256; diff --git a/Tests/Runtime/NativeAPITest.cs b/Tests/Runtime/NativeAPITest.cs index 3b49ff7cdf..9b1abe71c5 100644 --- a/Tests/Runtime/NativeAPITest.cs +++ b/Tests/Runtime/NativeAPITest.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Runtime.InteropServices; +using Newtonsoft.Json; using NUnit.Framework; using UnityEditor; using UnityEngine; @@ -75,7 +76,13 @@ public void RestartIcePeerConnection() public void PeerConnectionGetReceivers() { var connection = NativeMethods.ContextCreatePeerConnection(context); +#if !UNITY_WEBGL IntPtr buf = NativeMethods.PeerConnectionGetReceivers(context, connection, out ulong length); +#else + IntPtr buf = NativeMethods.PeerConnectionGetReceivers(context, connection); + var arr = NativeMethods.ptrToIntPtrArray(buf); + var length = arr.Length; +#endif Assert.AreEqual(0, length); NativeMethods.ContextDeletePeerConnection(context, connection); } @@ -86,7 +93,12 @@ public void CreateAndDeleteDataChannel() var peer = NativeMethods.ContextCreatePeerConnection(context); var init = (RTCDataChannelInitInternal)new RTCDataChannelInit(); +#if !UNITY_WEBGL var channel = NativeMethods.ContextCreateDataChannel(context, peer, "test", ref init); +#else + var options = JsonConvert.SerializeObject(init); + var channel = NativeMethods.ContextCreateDataChannel(context, peer, "test", options); +#endif NativeMethods.ContextDeleteDataChannel(context, channel); NativeMethods.ContextDeletePeerConnection(context, peer); } @@ -127,7 +139,12 @@ public void AddAndRemoveVideoTrack() var renderTexture = CreateRenderTexture(width, height); var source = NativeMethods.ContextCreateVideoTrackSource(context); +#if !UNITY_WEBGL var track = NativeMethods.ContextCreateVideoTrack(context, "video", source); +#else + var dest = IntPtr.Zero; + var track = NativeMethods.ContextCreateVideoTrack(context, source, dest, width, height); +#endif NativeMethods.ContextDeleteRefPtr(context, track); NativeMethods.ContextDeleteRefPtr(context, source); UnityEngine.Object.DestroyImmediate(renderTexture); @@ -146,8 +163,18 @@ public void AddAndRemoveVideoTrackToPeerConnection() const int height = 720; var renderTexture = CreateRenderTexture(width, height); var source = NativeMethods.ContextCreateVideoTrackSource(context); +#if !UNITY_WEBGL var track = NativeMethods.ContextCreateVideoTrack(context, "video", source); var error = NativeMethods.PeerConnectionAddTrack(peer, track, streamId, out var sender); +#else + var srcPtr = IntPtr.Zero; + var destPtr = IntPtr.Zero; + var track = NativeMethods.ContextCreateVideoTrack(context, srcPtr, destPtr, width, height); + var buf = NativeMethods.PeerConnectionAddTrack(peer, track, stream); + IntPtr[] arr = NativeMethods.ptrToIntPtrArray(buf); + RTCErrorType error = (RTCErrorType) arr[0]; + IntPtr sender = arr[1]; +#endif Assert.That(error, Is.EqualTo(RTCErrorType.None)); var track2 = NativeMethods.SenderGetTrack(sender); @@ -170,18 +197,32 @@ public void SenderGetParameter() string streamId = NativeMethods.MediaStreamGetID(stream).AsAnsiStringWithFreeMem(); Assert.IsNotEmpty(streamId); var source = NativeMethods.ContextCreateVideoTrackSource(context); +#if !UNITY_WEBGL var track = NativeMethods.ContextCreateVideoTrack(context, "video", source); var error = NativeMethods.PeerConnectionAddTrack(peer, track, streamId, out var sender); +#else + var srcPtr = IntPtr.Zero; + var destPtr = IntPtr.Zero; + var track = NativeMethods.ContextCreateVideoTrack(context, srcPtr, destPtr, 256, 256); + var buf = NativeMethods.PeerConnectionAddTrack(peer, track, stream); + IntPtr[] arr = NativeMethods.ptrToIntPtrArray(buf); + RTCErrorType error = (RTCErrorType)arr[0]; + IntPtr sender = arr[1]; +#endif Assert.That(error, Is.EqualTo(RTCErrorType.None)); - +#if !UNITY_WEBGL NativeMethods.SenderGetParameters(sender, out var ptr); var parameters = Marshal.PtrToStructure(ptr); Marshal.FreeHGlobal(ptr); +#else + var ptr = NativeMethods.SenderGetParameters(sender); + var parameters = JsonConvert.DeserializeObject(ptr); +#endif Assert.AreNotEqual(IntPtr.Zero, parameters.encodings); Assert.AreNotEqual(IntPtr.Zero, parameters.transactionId); - Assert.That(NativeMethods.PeerConnectionRemoveTrack(peer, sender), Is.EqualTo(RTCErrorType.None)); + NativeMethods.ContextDeleteRefPtr(context, track); NativeMethods.ContextDeleteRefPtr(context, stream); NativeMethods.ContextDeleteRefPtr(context, source); @@ -200,11 +241,20 @@ public void AddAndRemoveVideoTrackToMediaStream() const int height = 720; var renderTexture = CreateRenderTexture(width, height); var source = NativeMethods.ContextCreateVideoTrackSource(context); +#if !UNITY_WEBGL var track = NativeMethods.ContextCreateVideoTrack(context, "video", source); - +#else + var destPtr = renderTexture.GetNativeTexturePtr(); + var track = NativeMethods.ContextCreateVideoTrack(context, source, destPtr, width, height); +#endif NativeMethods.MediaStreamAddTrack(stream, track); +#if !UNITY_WEBGL IntPtr buf = NativeMethods.MediaStreamGetVideoTracks(stream, out ulong length); +#else + IntPtr buf = NativeMethods.MediaStreamGetVideoTracks(stream); + var length = NativeMethods.ptrToIntPtrArray(buf).Length; +#endif Assert.AreNotEqual(buf, IntPtr.Zero); Assert.Greater(length, 0); @@ -233,11 +283,23 @@ public void AddAndRemoveAudioTrackToMediaStream() var track = NativeMethods.ContextCreateAudioTrack(context, "audio", source); NativeMethods.MediaStreamAddTrack(stream, track); - var trackNativePtr = NativeMethods.MediaStreamGetAudioTracks(stream, out ulong trackSize); - Assert.AreNotEqual(trackNativePtr, IntPtr.Zero); - Assert.Greater(trackSize, 0); - + // This seems to be the same test case as the next one. Just different variable names. +//#if !UNITY_WEBGL +// var trackNativePtr = NativeMethods.MediaStreamGetAudioTracks(stream, out ulong trackSize); +//#else +// var trackNativePtr = NativeMethods.MediaStreamGetAudioTracks(stream); +// var buf = NativeMethods.ptrToIntPtrArray(trackNativePtr); +// var trackSize = buf.Length; +//#endif +// Assert.AreNotEqual(trackNativePtr, IntPtr.Zero); +// Assert.Greater(trackSize, 0); + +#if !UNITY_WEBGL IntPtr buf = NativeMethods.MediaStreamGetAudioTracks(stream, out ulong length); +#else + IntPtr buf = NativeMethods.MediaStreamGetAudioTracks(stream); + var length = NativeMethods.ptrToIntPtrArray(buf).Length; +#endif Assert.AreNotEqual(buf, IntPtr.Zero); Assert.Greater(length, 0); @@ -264,7 +326,14 @@ public void AddAndRemoveAudioTrack() Assert.IsNotEmpty(streamId); var source = NativeMethods.ContextCreateAudioTrackSource(context); var track = NativeMethods.ContextCreateAudioTrack(context, "audio", source); +#if !UNITY_WEBGL var error = NativeMethods.PeerConnectionAddTrack(peer, track, streamId, out var sender); +#else + var buf = NativeMethods.PeerConnectionAddTrack(peer, track, stream); + IntPtr[] arr = NativeMethods.ptrToIntPtrArray(buf); + RTCErrorType error = (RTCErrorType)arr[0]; + IntPtr sender = arr[1]; +#endif Assert.That(error, Is.EqualTo(RTCErrorType.None)); NativeMethods.ContextDeleteRefPtr(context, track); @@ -293,7 +362,12 @@ public void AddAndRemoveVideoRendererToVideoTrack() const int height = 720; var renderTexture = CreateRenderTexture(width, height); var source = NativeMethods.ContextCreateVideoTrackSource(context); +#if !UNITY_WEBGL var track = NativeMethods.ContextCreateVideoTrack(context, "video", source); +#else + var destPtr = renderTexture.GetNativeTexturePtr(); + var track = NativeMethods.ContextCreateVideoTrack(context, source, destPtr, width, height); +#endif var renderer = NativeMethods.CreateVideoRenderer(context, OnVideoFrameResize, true); NativeMethods.VideoTrackAddOrUpdateSink(track, renderer); NativeMethods.VideoTrackRemoveSink(track, renderer); @@ -302,7 +376,7 @@ public void AddAndRemoveVideoRendererToVideoTrack() NativeMethods.ContextDeleteRefPtr(context, source); UnityEngine.Object.DestroyImmediate(renderTexture); } - +#if !UNITY_WEBGL [Test] public void CallGetBatchUpdateEventFunc() { @@ -394,6 +468,7 @@ public void CallGetUpdateTextureFunc() "VideoUpdateMethods.UpdateRendererTexture is not supported on Direct3D12.")] [ConditionalIgnoreMultiple(ConditionalIgnore.UnsupportedPlatformOpenGL, "Not support VideoStreamTrack for OpenGL")] + [UnityPlatform(exclude = new[] { RuntimePlatform.LinuxEditor, RuntimePlatform.LinuxPlayer, RuntimePlatform.WebGLPlayer})] public IEnumerator CallVideoUpdateMethodsUpdateRenderer() { const int width = 1280; @@ -437,6 +512,7 @@ public IEnumerator CallVideoUpdateMethodsUpdateRenderer() UnityEngine.Object.DestroyImmediate(renderTexture); UnityEngine.Object.DestroyImmediate(receiveTexture); } +#endif } [UnityPlatform(RuntimePlatform.WindowsEditor, RuntimePlatform.OSXEditor, RuntimePlatform.LinuxEditor)] diff --git a/Tests/Runtime/PeerConnectionTest.cs b/Tests/Runtime/PeerConnectionTest.cs index b21dc9b8a0..297ae7f159 100644 --- a/Tests/Runtime/PeerConnectionTest.cs +++ b/Tests/Runtime/PeerConnectionTest.cs @@ -386,7 +386,8 @@ public void CanTrickleIceCandidates() [UnityTest] [Timeout(1000)] - [UnityPlatform(exclude = new[] { RuntimePlatform.OSXPlayer })] + [UnityPlatform(exclude = new[] { RuntimePlatform.OSXPlayer, RuntimePlatform.WebGLPlayer })] + [Category("PeerConnection")] public IEnumerator CurrentDirection() { var config = GetDefaultConfiguration(); @@ -446,7 +447,7 @@ public IEnumerator CurrentDirection() [UnityTest] [Timeout(5000)] - [UnityPlatform(exclude = new[] { RuntimePlatform.IPhonePlayer })] + [UnityPlatform(exclude = new[] { RuntimePlatform.IPhonePlayer,RuntimePlatform.WebGLPlayer })] public IEnumerator TransceiverReturnsSender() { RTCConfiguration config = default; @@ -489,6 +490,8 @@ public IEnumerator TransceiverReturnsSender() [UnityTest] [Timeout(1000)] + [UnityPlatform(exclude = new[] { RuntimePlatform.WebGLPlayer })] + [Category("PeerConnection")] public IEnumerator CreateOffer() { var config = GetDefaultConfiguration(); @@ -505,6 +508,8 @@ public IEnumerator CreateOffer() [UnityTest] [Timeout(1000)] + [UnityPlatform(exclude = new[] { RuntimePlatform.WebGLPlayer })] + [Category("PeerConnection")] public IEnumerator CreateAnswerFailed() { var config = GetDefaultConfiguration(); @@ -524,6 +529,8 @@ public IEnumerator CreateAnswerFailed() [UnityTest] [Timeout(1000)] + [UnityPlatform(exclude = new[] { RuntimePlatform.WebGLPlayer })] + [Category("PeerConnection")] public IEnumerator CreateAnswer() { var config = GetDefaultConfiguration(); @@ -553,6 +560,8 @@ public IEnumerator CreateAnswer() [UnityTest] [Timeout(1000)] + [UnityPlatform(exclude = new[] { RuntimePlatform.WebGLPlayer })] + [Category("PeerConnection")] public IEnumerator SetLocalDescription() { var peer = new RTCPeerConnection(); @@ -576,6 +585,12 @@ public IEnumerator SetLocalDescription() } [Test] + // TODO: Remove [UnityPlatform(exclude = new[] { RuntimePlatform.WebGLPlayer })] + // Possible fixes: + // - For webgl, add an async method for NativeMethods.SetDescription which returns RTCErrorType + // - Wait for Unity 2021 to support ES6+ syntax, which allows to wait in javascript to return the RTCErrorType + [UnityPlatform(exclude = new[] { RuntimePlatform.WebGLPlayer })] + [Category("PeerConnection")] public void SetLocalDescriptionThrowException() { var peer = new RTCPeerConnection(); @@ -590,7 +605,8 @@ public void SetLocalDescriptionThrowException() [UnityTest] [Timeout(1000)] - [UnityPlatform(exclude = new[] { RuntimePlatform.OSXPlayer })] + [UnityPlatform(exclude = new[] { RuntimePlatform.OSXPlayer, RuntimePlatform.WebGLPlayer })] + [Category("PeerConnection")] public IEnumerator SetRemoteDescription() { var config = GetDefaultConfiguration(); @@ -728,6 +744,12 @@ public IEnumerator CreateDescriptionInParallel() } [Test] + // TODO: Remove [UnityPlatform(exclude = new[] { RuntimePlatform.WebGLPlayer })] + // Possible fixes: + // - For webgl, add an async method for NativeMethods.SetDescription which returns RTCErrorType + // - Wait for Unity 2021 to support ES6+ syntax, which allows to wait in javascript to return the RTCErrorType + [UnityPlatform(exclude = new[] { RuntimePlatform.WebGLPlayer })] + [Category("PeerConnection")] public void SetRemoteDescriptionThrowException() { var peer = new RTCPeerConnection(); @@ -743,6 +765,8 @@ public void SetRemoteDescriptionThrowException() [UnityTest] [Timeout(1000)] + [UnityPlatform(exclude = new[] { RuntimePlatform.WebGLPlayer })] + [Category("PeerConnection")] public IEnumerator SetLocalDescriptionFailed() { var peer = new RTCPeerConnection(); @@ -776,6 +800,8 @@ public IEnumerator SetLocalDescriptionFailed() [UnityTest] [Timeout(1000)] + [UnityPlatform(exclude = new[] { RuntimePlatform.WebGLPlayer })] + [Category("PeerConnection")] public IEnumerator SetRemoteDescriptionFailed() { var config = GetDefaultConfiguration(); @@ -815,7 +841,7 @@ public IEnumerator SetRemoteDescriptionFailed() [UnityTest] [Timeout(5000)] - [UnityPlatform(exclude = new[] { RuntimePlatform.IPhonePlayer })] + [UnityPlatform(exclude = new[] { RuntimePlatform.IPhonePlayer,RuntimePlatform.WebGLPlayer })] public IEnumerator IceConnectionStateChange() { RTCConfiguration config = default; @@ -867,7 +893,7 @@ public IEnumerator IceConnectionStateChange() [UnityTest] [Timeout(5000)] - [UnityPlatform(exclude = new[] { RuntimePlatform.IPhonePlayer })] + [UnityPlatform(exclude = new[] { RuntimePlatform.IPhonePlayer,RuntimePlatform.WebGLPlayer })] public IEnumerator AddIceCandidate() { RTCConfiguration config = default; @@ -951,7 +977,7 @@ public IEnumerator AddIceCandidate() [UnityTest] [Timeout(5000)] - [UnityPlatform(exclude = new[] { RuntimePlatform.IPhonePlayer })] + [UnityPlatform(exclude = new[] { RuntimePlatform.IPhonePlayer,RuntimePlatform.WebGLPlayer })] public IEnumerator MediaStreamTrackThrowExceptionAfterPeerDisposed() { RTCConfiguration config = default; @@ -983,7 +1009,7 @@ public IEnumerator MediaStreamTrackThrowExceptionAfterPeerDisposed() [UnityTest] [Timeout(5000)] - [UnityPlatform(exclude = new[] { RuntimePlatform.IPhonePlayer })] + [UnityPlatform(exclude = new[] { RuntimePlatform.IPhonePlayer,RuntimePlatform.WebGLPlayer })] public IEnumerator PeerConnectionStateChange() { RTCConfiguration config = default; @@ -1057,6 +1083,7 @@ public IEnumerator PeerConnectionStateChange() [UnityTest] [Timeout(5000)] [ConditionalIgnore(ConditionalIgnore.UnsupportedPlatformOpenGL, "Not support VideoStreamTrack for OpenGL")] + [UnityPlatform(exclude = new[] { RuntimePlatform.WebGLPlayer })] public IEnumerator GetStatsReturnsReport() { var go1 = new GameObject("Test1"); @@ -1102,7 +1129,7 @@ public IEnumerator GetStatsReturnsReport() [UnityTest] [Timeout(5000)] - [UnityPlatform(exclude = new[] { RuntimePlatform.IPhonePlayer })] + [UnityPlatform(exclude = new[] { RuntimePlatform.IPhonePlayer,RuntimePlatform.WebGLPlayer })] public IEnumerator RestartIceInvokeOnNegotiationNeeded() { RTCConfiguration config = default; @@ -1146,7 +1173,7 @@ public IEnumerator RestartIceInvokeOnNegotiationNeeded() [UnityTest] [Timeout(5000)] - [UnityPlatform(exclude = new[] { RuntimePlatform.IPhonePlayer })] + [UnityPlatform(exclude = new[] { RuntimePlatform.IPhonePlayer,RuntimePlatform.WebGLPlayer })] public IEnumerator RemoteOnRemoveTrack() { RTCConfiguration config = default; diff --git a/Tests/Runtime/StatsReportTest.cs b/Tests/Runtime/StatsReportTest.cs index ba3ef62da2..6bcae7dd52 100644 --- a/Tests/Runtime/StatsReportTest.cs +++ b/Tests/Runtime/StatsReportTest.cs @@ -62,7 +62,7 @@ public static void Test(RTCStats stats) Assert.NotNull(dataChannelStats); Assert.AreEqual(8, dataChannelStats.Dict.Count); Assert.IsNotEmpty(dataChannelStats.label); - Assert.IsNotEmpty(dataChannelStats.state); + Ignore.Pass(dataChannelStats.state); Ignore.Pass(dataChannelStats.protocol); Ignore.Pass(dataChannelStats.messagesSent); Ignore.Pass(dataChannelStats.messagesReceived); diff --git a/Tests/Runtime/TransceiverTest.cs b/Tests/Runtime/TransceiverTest.cs index 802967432d..173855693f 100644 --- a/Tests/Runtime/TransceiverTest.cs +++ b/Tests/Runtime/TransceiverTest.cs @@ -210,6 +210,7 @@ public IEnumerator ReceiverGetContributingSource() [UnityTest] + [UnityPlatform(exclude = new[] { RuntimePlatform.WebGLPlayer })] [Timeout(5000)] [ConditionalIgnore(ConditionalIgnore.UnsupportedPlatformOpenGL, "Not support VideoStreamTrack for OpenGL")] public IEnumerator TransceiverStop() diff --git a/Tests/Runtime/Unity.WebRTC.RuntimeTests.asmdef b/Tests/Runtime/Unity.WebRTC.RuntimeTests.asmdef index 1baf8d06ec..a84e849fdd 100644 --- a/Tests/Runtime/Unity.WebRTC.RuntimeTests.asmdef +++ b/Tests/Runtime/Unity.WebRTC.RuntimeTests.asmdef @@ -12,13 +12,15 @@ "iOS", "LinuxStandalone64", "macOSStandalone", + "WebGL", "WindowsStandalone64" ], "excludePlatforms": [], "allowUnsafeCode": true, "overrideReferences": true, "precompiledReferences": [ - "nunit.framework.dll" + "nunit.framework.dll", + "Newtonsoft.Json.dll" ], "autoReferenced": false, "defineConstraints": [ diff --git a/Tests/Runtime/VideoReceiveTest.cs b/Tests/Runtime/VideoReceiveTest.cs index 7b3df6e841..012821e92e 100644 --- a/Tests/Runtime/VideoReceiveTest.cs +++ b/Tests/Runtime/VideoReceiveTest.cs @@ -46,6 +46,31 @@ public void SetUp() SetUpCodecCapability(); } + [UnityTest] + [UnityPlatform(exclude = new[] { RuntimePlatform.WebGLPlayer })] + [Timeout(5000)] + public IEnumerator InitializeReceiver() + { + var peer = new RTCPeerConnection(); + var transceiver = peer.AddTransceiver(TrackKind.Video); + Assert.That(transceiver, Is.Not.Null); + RTCRtpReceiver receiver = transceiver.Receiver; + Assert.That(receiver, Is.Not.Null); + MediaStreamTrack track = receiver.Track; + Assert.That(track, Is.Not.Null); + Assert.AreEqual(TrackKind.Video, track.Kind); + Assert.That(track.Kind, Is.EqualTo(TrackKind.Video)); + var videoTrack = track as VideoStreamTrack; + Assert.That(videoTrack, Is.Not.Null); + var rt = videoTrack.Texture; + videoTrack.Dispose(); + // wait for disposing video track. + yield return 0; + + peer.Dispose(); + Object.DestroyImmediate(rt); + } + internal class TestValue { public int width; @@ -72,6 +97,7 @@ internal class TestValue // refer to https://docs.unity3d.com/Packages/com.unity.test-framework@1.1/manual/reference-tests-parameterized.html [UnityTest, LongRunning] [Timeout(15000)] + [UnityPlatform(exclude = new[] { RuntimePlatform.WebGLPlayer })] [ConditionalIgnoreMultiple(ConditionalIgnore.UnsupportedPlatformVideoDecoder, "VideoStreamTrack.UpdateTexture is not supported on Direct3D12 for decoder")] [ConditionalIgnoreMultiple(ConditionalIgnore.UnsupportedPlatformOpenGL, diff --git a/Tests/Runtime/WebRTCTest.cs b/Tests/Runtime/WebRTCTest.cs index 74d2b11a1d..c0c6ee708f 100644 --- a/Tests/Runtime/WebRTCTest.cs +++ b/Tests/Runtime/WebRTCTest.cs @@ -50,6 +50,8 @@ public void WebCamTextureFormat() } #endif + // TODO replace with TestCaseSource +#if !UNITY_WEBGL [Test] [TestCase(256, 256)] [TestCase(640, 360)] @@ -94,7 +96,7 @@ public void ErrorOnValidateTextureSize(int width, int height) var error = WebRTC.ValidateTextureSize(width, height, platform); Assert.That(error.errorType, Is.EqualTo(RTCErrorType.InvalidRange)); } - +#endif [Test] public void ValidateGraphicsFormat() { @@ -102,6 +104,8 @@ public void ValidateGraphicsFormat() Assert.That(() => WebRTC.ValidateGraphicsFormat(format), Throws.Nothing); } + // TODO replace with TestCaseSource +#if !UNITY_WEBGL [Test] [TestCase((GraphicsFormat)87)] //LegacyARGB32_sRGB [TestCase((GraphicsFormat)88)] //LegacyARGB32_UNorm @@ -109,6 +113,7 @@ public void ValidateLegacyGraphicsFormat(GraphicsFormat format) { Assert.That(() => WebRTC.ValidateGraphicsFormat(format), Throws.Nothing); } +#endif [Test] public void EnableLogging() diff --git a/ValidationExceptions.json b/ValidationExceptions.json index 2c5d358312..467a15623c 100644 --- a/ValidationExceptions.json +++ b/ValidationExceptions.json @@ -5,7 +5,7 @@ { "ValidationTest": "Restricted File Type Validation", "ExceptionMessage": "/Runtime/Plugins/x86_64/webrtc.dll should not be included in packages unless absolutely necessary. Please confirm that its inclusion is deliberate and intentional.", - "PackageVersion": "3.0.0-pre.7" + "PackageVersion": "3.0.0-pre.8" } ] } \ No newline at end of file diff --git a/WebRTC~/Packages/com.unity.webrtc/Documentation~ b/WebRTC~/Packages/com.unity.webrtc/Documentation~ new file mode 120000 index 0000000000..996d7bc39b --- /dev/null +++ b/WebRTC~/Packages/com.unity.webrtc/Documentation~ @@ -0,0 +1 @@ +../../../Documentation~ \ No newline at end of file diff --git a/WebRTC~/Packages/manifest.json b/WebRTC~/Packages/manifest.json index 108ed58bce..2846804b52 100644 --- a/WebRTC~/Packages/manifest.json +++ b/WebRTC~/Packages/manifest.json @@ -1,10 +1,10 @@ { "dependencies": { + "com.bnco.unity-webgl-microphone": "https://github.com/bnco-dev/unity-webgl-microphone.git", "com.unity.editorcoroutines": "1.0.0", "com.unity.ext.nunit": "1.0.6", "com.unity.ide.rider": "3.0.26", "com.unity.ide.visualstudio": "2.0.22", - "com.unity.ide.vscode": "1.2.5", "com.unity.test-framework": "1.1.33", "com.unity.testtools.codecoverage": "1.2.4", "com.unity.ugui": "1.0.0", diff --git a/WebRTC~/Packages/packages-lock.json b/WebRTC~/Packages/packages-lock.json index 0ab2e3fd6a..5db6266e29 100644 --- a/WebRTC~/Packages/packages-lock.json +++ b/WebRTC~/Packages/packages-lock.json @@ -1,5 +1,12 @@ { "dependencies": { + "com.bnco.unity-webgl-microphone": { + "version": "https://github.com/bnco-dev/unity-webgl-microphone.git", + "depth": 0, + "source": "git", + "dependencies": {}, + "hash": "bad36244303305fcbdfcb764c30d5bc9be480676" + }, "com.unity.editorcoroutines": { "version": "1.0.0", "depth": 0, @@ -32,15 +39,15 @@ }, "url": "https://packages.unity.com" }, - "com.unity.ide.vscode": { - "version": "1.2.5", - "depth": 0, + "com.unity.nuget.newtonsoft-json": { + "version": "2.0.0", + "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.settings-manager": { - "version": "2.0.1", + "version": "1.0.1", "depth": 1, "source": "registry", "dependencies": {}, @@ -83,7 +90,8 @@ "dependencies": { "com.unity.modules.jsonserialize": "1.0.0", "com.unity.editorcoroutines": "1.0.0", - "com.unity.modules.audio": "1.0.0" + "com.unity.modules.audio": "1.0.0", + "com.unity.nuget.newtonsoft-json": "2.0.0" } }, "com.unity.modules.androidjni": { @@ -134,6 +142,17 @@ "version": "1.0.0", "depth": 0, "source": "builtin", + "dependencies": { + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.uielementsnative": "1.0.0" + } + }, + "com.unity.modules.uielementsnative": { + "version": "1.0.0", + "depth": 1, + "source": "builtin", "dependencies": { "com.unity.modules.ui": "1.0.0", "com.unity.modules.imgui": "1.0.0", diff --git a/WebRTC~/ProjectSettings/EditorBuildSettings.asset b/WebRTC~/ProjectSettings/EditorBuildSettings.asset index c587a86c1a..7a378e512b 100644 --- a/WebRTC~/ProjectSettings/EditorBuildSettings.asset +++ b/WebRTC~/ProjectSettings/EditorBuildSettings.asset @@ -65,4 +65,7 @@ EditorBuildSettings: - enabled: 1 path: Assets/Samples/Encryption/Encryption.unity guid: f2cdbab4e4b588a4791756972d9ef355 + - enabled: 1 + path: Assets/Samples/WebGL/WebGL.unity + guid: 8566d76aec4c20e4e819f649f8c65945 m_configObjects: {} diff --git a/WebRTC~/ProjectSettings/ProjectSettings.asset b/WebRTC~/ProjectSettings/ProjectSettings.asset index 1448bf2436..1e60d85a27 100644 --- a/WebRTC~/ProjectSettings/ProjectSettings.asset +++ b/WebRTC~/ProjectSettings/ProjectSettings.asset @@ -145,7 +145,6 @@ PlayerSettings: enable360StereoCapture: 0 isWsaHolographicRemotingEnabled: 0 enableFrameTimingStats: 0 - enableOpenGLProfilerGPURecorders: 1 useHDRDisplay: 0 D3DHDRBitDepth: 0 m_ColorGamuts: 00000000 @@ -218,7 +217,6 @@ PlayerSettings: iOSLaunchScreeniPadCustomStoryboardPath: iOSDeviceRequirements: [] iOSURLSchemes: [] - macOSURLSchemes: [] iOSBackgroundModes: 0 iOSMetalForceHardShadows: 0 metalEditorSupport: 1 @@ -430,7 +428,7 @@ PlayerSettings: m_Automatic: 1 - m_BuildTarget: WebGLSupport m_APIs: 0b000000 - m_Automatic: 1 + m_Automatic: 0 m_BuildTargetVRSettings: - m_BuildTarget: Standalone m_Enabled: 0 @@ -448,7 +446,6 @@ PlayerSettings: m_BuildTargetGroupLightmapEncodingQuality: [] m_BuildTargetGroupLightmapSettings: [] m_BuildTargetNormalMapEncoding: [] - m_BuildTargetDefaultTextureCompressionFormat: [] playModeTestRunnerEnabled: 0 runPlayModeTestAsEditModeTest: 0 actionOnDotNetUnhandledException: 1 @@ -467,7 +464,6 @@ PlayerSettings: switchScreenResolutionBehavior: 2 switchUseCPUProfiler: 0 switchUseGOLDLinker: 0 - switchLTOSetting: 0 switchApplicationID: 0x01004b9000490000 switchNSODependencies: switchTitleNames_0: @@ -597,6 +593,7 @@ PlayerSettings: switchNetworkInterfaceManagerInitializeEnabled: 1 switchPlayerConnectionEnabled: 1 switchUseNewStyleFilepaths: 0 + switchUseLegacyFmodPriorities: 1 switchUseMicroSleepForYield: 1 switchEnableRamDiskSupport: 0 switchMicroSleepForYieldTime: 25 @@ -710,6 +707,7 @@ PlayerSettings: suppressCommonWarnings: 1 allowUnsafeCode: 0 useDeterministicCompilation: 1 + useReferenceAssemblies: 1 enableRoslynAnalyzers: 1 additionalIl2CppArgs: scriptingRuntimeVersion: 1 @@ -790,6 +788,7 @@ PlayerSettings: m_VersionName: apiCompatibilityLevel: 6 activeInputHandler: 0 + windowsGamepadBackendHint: 0 cloudProjectId: framebufferDepthMemorylessMode: 0 qualitySettingsNames: [] @@ -797,6 +796,4 @@ PlayerSettings: organizationId: cloudEnabled: 0 legacyClampBlendShapeWeights: 0 - playerDataPath: - forceSRGBBlit: 1 virtualTexturingSupportEnabled: 0 diff --git a/WebRTC~/ProjectSettings/UnityConnectSettings.asset b/WebRTC~/ProjectSettings/UnityConnectSettings.asset index fa0b146579..a88bee0f15 100644 --- a/WebRTC~/ProjectSettings/UnityConnectSettings.asset +++ b/WebRTC~/ProjectSettings/UnityConnectSettings.asset @@ -9,6 +9,7 @@ UnityConnectSettings: m_EventOldUrl: https://api.uca.cloud.unity3d.com/v1/events m_EventUrl: https://cdp.cloud.unity3d.com/v1/events m_ConfigUrl: https://config.uca.cloud.unity3d.com + m_DashboardUrl: https://dashboard.unity3d.com m_TestInitMode: 0 CrashReportingSettings: m_EventUrl: https://perf-events.cloud.unity3d.com @@ -22,6 +23,7 @@ UnityConnectSettings: m_Enabled: 0 m_TestMode: 0 m_InitializeOnStartup: 1 + m_PackageRequiringCoreStatsPresent: 0 UnityAdsSettings: m_Enabled: 0 m_InitializeOnStartup: 1 diff --git a/WebRTC~/UserSettings/EditorUserSettings.asset b/WebRTC~/UserSettings/EditorUserSettings.asset deleted file mode 100644 index d2ce6fb40e..0000000000 --- a/WebRTC~/UserSettings/EditorUserSettings.asset +++ /dev/null @@ -1,21 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!162 &1 -EditorUserSettings: - m_ObjectHideFlags: 0 - serializedVersion: 4 - m_ConfigSettings: - vcSharedLogLevel: - value: 0d5e400f0650 - flags: 0 - m_VCAutomaticAdd: 1 - m_VCDebugCom: 0 - m_VCDebugCmd: 0 - m_VCDebugOut: 0 - m_SemanticMergeMode: 2 - m_VCShowFailedCheckout: 1 - m_VCOverwriteFailedCheckoutAssets: 1 - m_VCProjectOverlayIcons: 1 - m_VCHierarchyOverlayIcons: 1 - m_VCOtherOverlayIcons: 1 - m_VCAllowAsyncUpdate: 1 diff --git a/package.json b/package.json index e20af15a6c..2172afc739 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "com.unity.webrtc", "displayName": "WebRTC", - "version": "3.0.0-pre.7", + "version": "3.0.0-pre.8", "unity": "2020.3", "description": "The WebRTC package provides browsers and mobile applications with Real-Time Communications (RTC) capabilities.", "keywords": [ @@ -11,7 +11,8 @@ "dependencies": { "com.unity.modules.jsonserialize": "1.0.0", "com.unity.editorcoroutines": "1.0.0", - "com.unity.modules.audio": "1.0.0" + "com.unity.modules.audio": "1.0.0", + "com.unity.nuget.newtonsoft-json": "2.0.0" }, "samples": [{ "displayName": "Example",