diff options
69 files changed, 7701 insertions, 1506 deletions
diff --git a/.github/labeler.yaml b/.github/labeler.yaml index d31a361baf..6e03801b65 100644 --- a/.github/labeler.yaml +++ b/.github/labeler.yaml @@ -76,3 +76,6 @@ c/cpp: - '**/*.i' - '**/*.inl' - '**/*.y' + +'team:viewer': + - '*' diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 8d1c6b63e6..57faafc042 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -312,7 +312,7 @@ jobs: AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} needs: build - runs-on: windows + runs-on: windows-large steps: - name: Sign and package Windows viewer if: env.AZURE_KEY_VAULT_URI && env.AZURE_CERT_NAME && env.AZURE_CLIENT_ID && env.AZURE_CLIENT_SECRET && env.AZURE_TENANT_ID @@ -412,7 +412,7 @@ jobs: - uses: actions/download-artifact@v4 with: pattern: "*-metadata" - + - name: Rename metadata run: | cp Windows-metadata/autobuild-package.xml Windows-autobuild-package.xml @@ -441,7 +441,7 @@ jobs: append_body: true fail_on_unmatched_files: true files: | - macOS-installer/*.dmg + macOS-installer/*.dmg Windows-installer/*.exe *-autobuild-package.xml *-viewer_version.txt diff --git a/autobuild.xml b/autobuild.xml index 4c251c5d48..bed9ce997f 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -384,11 +384,11 @@ <key>archive</key> <map> <key>hash</key> - <string>e03eb77224290c875ff84f75b7fe3d0e7c162c94</string> + <string>f6835c4d7745cd1cadfbce47b40331d08affb532</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://github.com/secondlife/3p-dictionaries/releases/download/v1-a01bb6c/dictionaries-1.a01bb6c-common-a01bb6c.tar.zst</string> + <string>https://github.com/secondlife/3p-dictionaries/releases/download/v1.0.1-dev2.gf887629-f887629/dictionaries-common-None.tar.zst</string> </map> <key>name</key> <string>common</string> @@ -401,7 +401,7 @@ <key>copyright</key> <string>Copyright 2014 Apache OpenOffice software</string> <key>version</key> - <string>1.a01bb6c</string> + <string>None</string> <key>name</key> <string>dictionaries</string> <key>description</key> @@ -572,11 +572,11 @@ <key>creds</key> <string>github</string> <key>hash</key> - <string>a2074b67de7ad4c04b5ca8f8f161506add9697b2</string> + <string>fb6797ff93b6e881b060d2a8b396d8d7477834ee</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://api.github.com/repos/secondlife/3p-fmodstudio/releases/assets/149207589</string> + <string>https://api.github.com/repos/secondlife/3p-fmodstudio/releases/assets/108908444</string> </map> <key>name</key> <string>darwin64</string> @@ -588,11 +588,11 @@ <key>creds</key> <string>github</string> <key>hash</key> - <string>8c1b701648c077220dbc576c3d9aefbef47f8324</string> + <string>a378bd1604aa97ca763140911f9f4e463ced85c0</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://api.github.com/repos/secondlife/3p-fmodstudio/releases/assets/149207592</string> + <string>https://api.github.com/repos/secondlife/3p-fmodstudio/releases/assets/108908446</string> </map> <key>name</key> <string>linux64</string> @@ -604,11 +604,11 @@ <key>creds</key> <string>github</string> <key>hash</key> - <string>7e0c3d50e8b99d8735c6c9596a72ded9ee2bc1c8</string> + <string>72304491d86bd797b840999b255358f195b06609</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://api.github.com/repos/secondlife/3p-fmodstudio/releases/assets/149207594</string> + <string>https://api.github.com/repos/secondlife/3p-fmodstudio/releases/assets/108908456</string> </map> <key>name</key> <string>windows64</string> @@ -621,7 +621,7 @@ <key>copyright</key> <string>FMOD Studio by Firelight Technologies Pty Ltd.</string> <key>version</key> - <string>2.02.20.c78ef55</string> + <string>2.02.13.578928</string> <key>name</key> <string>fmodstudio</string> <key>description</key> @@ -772,11 +772,11 @@ <key>archive</key> <map> <key>hash</key> - <string>066625e7aa7f697a4b6cd461aad960c57181011f</string> + <string>6604c1cca515d287e697997a8d5593d1cae172a9</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://github.com/secondlife/3p-glh_linear/releases/download/v1.0.1-dev4-984c397/glh_linear-1.0.1-dev4-common-984c397.tar.zst</string> + <string>https://github.com/secondlife/3p-glh_linear/releases/download/v1.0.1-dev2.g3253ed7-3253ed7/glh_linear-common-None.tar.zst</string> </map> <key>name</key> <string>common</string> @@ -789,7 +789,7 @@ <key>copyright</key> <string>Copyright (c) 2000 Cass Everitt</string> <key>version</key> - <string>1.0.1-dev4</string> + <string>None</string> <key>name</key> <string>glh_linear</string> <key>description</key> @@ -908,11 +908,11 @@ <key>creds</key> <string>github</string> <key>hash</key> - <string>ae2c2a215b1bc2e3f37a67e301926dc405902d1a</string> + <string>a193ff65d6db48626d65d96c6124c6efca85e8ec</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://api.github.com/repos/secondlife/3p-havok-source/releases/assets/136778143</string> + <string>https://api.github.com/repos/secondlife/3p-havok-source/releases/assets/108912596</string> </map> <key>name</key> <string>darwin64</string> @@ -936,11 +936,11 @@ <key>creds</key> <string>github</string> <key>hash</key> - <string>0393dd75c58f7046bed47e62a8884a78cb02a5c3</string> + <string>ebfb82b6143874e7938b9d1e8a70d0a2e28aa818</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://api.github.com/repos/secondlife/3p-havok-source/releases/assets/136778145</string> + <string>https://api.github.com/repos/secondlife/3p-havok-source/releases/assets/108912599</string> </map> <key>name</key> <string>windows64</string> @@ -1146,11 +1146,11 @@ <key>creds</key> <string>github</string> <key>hash</key> - <string>ad72fa1d103df777906f0d98f3e882b9916aeada</string> + <string>bcc7e2c34896fc9cbc41828dee8a4ddf54f10453</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://api.github.com/repos/secondlife/3p-kdu/releases/assets/136774118</string> + <string>https://api.github.com/repos/secondlife/3p-kdu/releases/assets/108298968</string> </map> <key>name</key> <string>darwin64</string> @@ -1162,11 +1162,11 @@ <key>creds</key> <string>github</string> <key>hash</key> - <string>e46e4ac93a237b5c4a14183766f76ba5d58935a2</string> + <string>9de772df2ed12e9c742df6c90670c7cbbb9c93a6</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://api.github.com/repos/secondlife/3p-kdu/releases/assets/136774125</string> + <string>https://api.github.com/repos/secondlife/3p-kdu/releases/assets/108298969</string> </map> <key>name</key> <string>linux64</string> @@ -1178,31 +1178,15 @@ <key>creds</key> <string>github</string> <key>hash</key> - <string>bb37557f78c72b26580a521f8b8dabfa1b34e6e6</string> + <string>92533ff0f8c1881ad85e75800f9072c413ccf7b7</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://api.github.com/repos/secondlife/3p-kdu/releases/assets/136774126</string> + <string>https://api.github.com/repos/secondlife/3p-kdu/releases/assets/108298970</string> </map> <key>name</key> <string>windows64</string> </map> - <key>linux</key> - <map> - <key>archive</key> - <map> - <key>creds</key> - <string>github</string> - <key>hash</key> - <string>711b82f9f588d3a125af7dcd8c81f93d9c343a7d</string> - <key>hash_algorithm</key> - <string>sha1</string> - <key>url</key> - <string>https://api.github.com/repos/secondlife/3p-kdu/releases/assets/136774121</string> - </map> - <key>name</key> - <string>linux</string> - </map> </map> <key>license</key> <string>Kakadu</string> @@ -1211,7 +1195,7 @@ <key>copyright</key> <string>Kakadu software</string> <key>version</key> - <string>7.10.4.4b9ec5f</string> + <string>7.10.4.539108</string> <key>name</key> <string>kdu</string> <key>description</key> @@ -1510,11 +1494,11 @@ <key>archive</key> <map> <key>hash</key> - <string>a9503e1b4e1d9790cf29d18a3d9ab39e6a515679</string> + <string>e50ea94bbaa4ff41bf53b84b7192df1a694c5337</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://github.com/secondlife/llca/releases/download/v202402012004.0-0f5d9c3/llca-202402012004.0-common-0f5d9c3.tar.zst</string> + <string>https://github.com/secondlife/llca/releases/download/v202310121525.0-d22bd98/llca-202310121530.0-common-d22bd98.tar.zst</string> </map> <key>name</key> <string>common</string> @@ -1543,11 +1527,11 @@ <key>creds</key> <string>github</string> <key>hash</key> - <string>b037cc0b29ea70ee834cfae6dda5b7a25cd57174</string> + <string>48bca5d0233d1e724a59f649a2c6c7ac5f40ec3c</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://api.github.com/repos/secondlife/llphysicsextensions_source/releases/assets/144851460</string> + <string>https://api.github.com/repos/secondlife/llphysicsextensions_source/releases/assets/117009335</string> </map> <key>name</key> <string>darwin64</string> @@ -1559,11 +1543,11 @@ <key>creds</key> <string>github</string> <key>hash</key> - <string>bdea1fd5c4da9da5afde088d16188b45d0853e04</string> + <string>39f52d0350e130f41c5c758f7cb94e87b962c223</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://api.github.com/repos/secondlife/llphysicsextensions_source/releases/assets/144851461</string> + <string>https://api.github.com/repos/secondlife/llphysicsextensions_source/releases/assets/117009336</string> </map> <key>name</key> <string>linux64</string> @@ -1575,11 +1559,11 @@ <key>creds</key> <string>github</string> <key>hash</key> - <string>f652ce0d6aef864689f0ed44255da4d9cd65a43f</string> + <string>7b5e645fb7eb399abbea63bd21e8063bbb32a911</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://api.github.com/repos/secondlife/llphysicsextensions_source/releases/assets/144851463</string> + <string>https://api.github.com/repos/secondlife/llphysicsextensions_source/releases/assets/117009339</string> </map> <key>name</key> <string>windows64</string> @@ -1592,7 +1576,7 @@ <key>copyright</key> <string>Copyright (c) 2010, Linden Research, Inc.</string> <key>version</key> - <string>1.0.479d20a</string> + <string>1.0.565768</string> <key>name</key> <string>llphysicsextensions_source</string> </map> @@ -1776,18 +1760,6 @@ </map> <key>mikktspace</key> <map> - <key>canonical_repo</key> - <string>https://bitbucket.org/lindenlab/3p-mikktspace</string> - <key>copyright</key> - <string>Copyright (C) 2011 by Morten S. Mikkelsen, Copyright (C) 2022 Blender Authors</string> - <key>description</key> - <string>Mikktspace Tangent Generator</string> - <key>license</key> - <string>Apache 2.0</string> - <key>license_file</key> - <string>mikktspace.txt</string> - <key>name</key> - <string>mikktspace</string> <key>platforms</key> <map> <key>darwin64</key> @@ -1833,8 +1805,20 @@ <string>windows64</string> </map> </map> + <key>license</key> + <string>Apache 2.0</string> + <key>license_file</key> + <string>mikktspace.txt</string> + <key>copyright</key> + <string>Copyright (C) 2011 by Morten S. Mikkelsen, Copyright (C) 2022 Blender Authors</string> <key>version</key> <string>1</string> + <key>name</key> + <string>mikktspace</string> + <key>canonical_repo</key> + <string>https://bitbucket.org/lindenlab/3p-mikktspace</string> + <key>description</key> + <string>Mikktspace Tangent Generator</string> </map> <key>minizip-ng</key> <map> @@ -2483,11 +2467,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <key>archive</key> <map> <key>hash</key> - <string>2c47ae2d0c38c86b8c2db8d9317f0ab15edfc74f</string> - <key>hash_algorithm</key> - <string>sha1</string> + <string>4dad1c0948141e1667c01a3ee755e4dc</string> <key>url</key> - <string>https://github.com/secondlife/3p-tinygltf/releases/download/v2.5.0-1ae57fd/tinygltf-v2.5.0-common-1ae57fd.tar.zst</string> + <string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/105849/926137/tinygltf-v2.5.0-common-575729.tar.bz2</string> </map> <key>name</key> <string>common</string> @@ -2521,11 +2503,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <key>archive</key> <map> <key>hash</key> - <string>49650353442698c3e05102676fe427d0ebe02f0b</string> - <key>hash_algorithm</key> - <string>sha1</string> + <string>9b6e1a1f4b0969d38a1ca8ee00aeb548</string> <key>url</key> - <string>https://github.com/secondlife/3p-tracy/releases/download/v0.8.1-eecbf72/tracy-v0.8.1-eecbf72-darwin64-eecbf72.tar.zst</string> + <string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/110584/960613/tracy-v0.8.1.578241-darwin64-578241.tar.bz2</string> </map> <key>name</key> <string>darwin64</string> @@ -2535,11 +2515,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <key>archive</key> <map> <key>hash</key> - <string>2b80e7407e4f3e82eff3879add0e9ad63e7fcace</string> + <string>05b72ae5d733aed7d3bf142287601cc6</string> <key>hash_algorithm</key> - <string>sha1</string> + <string>md5</string> <key>url</key> - <string>https://github.com/secondlife/3p-tracy/releases/download/v0.8.1-eecbf72/tracy-v0.8.1-eecbf72-windows64-eecbf72.tar.zst</string> + <string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/110586/960637/tracy-v0.8.1.578241-windows64-578241.tar.bz2</string> </map> <key>name</key> <string>windows64</string> @@ -2552,7 +2532,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <key>copyright</key> <string>Copyright (c) 2017-2022, Bartosz Taudul (wolf@nereid.pl)</string> <key>version</key> - <string>v0.8.1-eecbf72</string> + <string>v0.8.1.578241</string> <key>name</key> <string>tracy</string> <key>canonical_repo</key> @@ -2751,6 +2731,8 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <string>LICENSE</string> <key>copyright</key> <string>Copyright (c) 2000-2012, Linden Research, Inc.</string> + <key>version</key> + <string>3.0-f14b5ec</string> <key>name</key> <string>viewer-manager</string> <key>description</key> @@ -2759,8 +2741,6 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <string>https://bitbucket.org/lindenlab/vmp-standalone</string> <key>source_type</key> <string>hg</string> - <key>version</key> - <string>3.0-f14b5ec</string> </map> <key>vlc-bin</key> <map> @@ -2870,29 +2850,51 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <map> <key>platforms</key> <map> - <key>common</key> + <key>darwin64</key> <map> <key>archive</key> <map> <key>hash</key> - <string>8e365eff8dcace48d91e2530f8b13e420849aefc</string> - <key>hash_algorithm</key> - <string>sha1</string> + <string>8cff2060843db3db788511ee34a8e8cc</string> <key>url</key> - <string>https://github.com/secondlife/3p-vulkan-gltf-pbr/releases/download/v1.0.0-d7c372f/vulkan_gltf-1.0.0-common-d7c372f.tar.zst</string> + <string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/101316/891509/vulkan_gltf-1-darwin64-572743.tar.bz2</string> </map> <key>name</key> - <string>common</string> + <string>darwin64</string> + </map> + <key>windows</key> + <map> + <key>archive</key> + <map> + <key>hash</key> + <string>58eea384be49ba756ce9c5e66669540b</string> + <key>url</key> + <string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/101318/891520/vulkan_gltf-1-windows-572743.tar.bz2</string> + </map> + <key>name</key> + <string>windows</string> + </map> + <key>windows64</key> + <map> + <key>archive</key> + <map> + <key>hash</key> + <string>79b6a11622c2f83cfc2b7cd1fafb867b</string> + <key>url</key> + <string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/101319/891521/vulkan_gltf-1-windows64-572743.tar.bz2</string> + </map> + <key>name</key> + <string>windows64</string> </map> </map> <key>license</key> <string>Copyright (c) 2018 Sascha Willems</string> <key>license_file</key> - <string>vulkan_gltf.txt</string> + <string>LICENSES/vulkan_gltf.txt</string> <key>copyright</key> <string>Copyright (c) 2018 Sascha Willems</string> <key>version</key> - <string>1.0.0</string> + <string>1</string> <key>name</key> <string>vulkan_gltf</string> <key>canonical_repo</key> @@ -2900,6 +2902,72 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <key>description</key> <string>Vulkan GLTF Sample Implementation</string> </map> + <key>webrtc</key> + <map> + <key>platforms</key> + <map> + <key>darwin64</key> + <map> + <key>archive</key> + <map> + <key>hash</key> + <string>0a0a972036f2b2c9c97dead40c91f7443b8ab339</string> + <key>hash_algorithm</key> + <string>sha1</string> + <key>url</key> + <string>https://github.com/secondlife/3p-webrtc-build/releases/download/m114.5735.08.61-debug/webrtc-m114.5735.08.61-debug.9571929057-darwin64-9571929057.tar.zst</string> + </map> + <key>name</key> + <string>darwin64</string> + </map> + <key>linux64</key> + <map> + <key>archive</key> + <map> + <key>hash</key> + <string>8725ad23f33d946bd5a4e5f28e8c8324925c71a7</string> + <key>hash_algorithm</key> + <string>sha1</string> + <key>url</key> + <string>https://github.com/secondlife/3p-webrtc-build/releases/download/m114.5735.08.61-debug/webrtc-m114.5735.08.61-debug.9571929057-linux64-9571929057.tar.zst</string> + </map> + <key>name</key> + <string>linux64</string> + </map> + <key>windows64</key> + <map> + <key>archive</key> + <map> + <key>hash</key> + <string>db560661807db276a3c7d1e7d9531198c9268f68</string> + <key>hash_algorithm</key> + <string>sha1</string> + <key>url</key> + <string>https://github.com/secondlife/3p-webrtc-build/releases/download/m114.5735.08.61-debug/webrtc-m114.5735.08.61-debug.9571929057-windows64-9571929057.tar.zst</string> + </map> + <key>name</key> + <string>windows64</string> + </map> + </map> + <key>license</key> + <string>MIT</string> + <key>license_file</key> + <string>LICENSES/webrtc-license.txt</string> + <key>copyright</key> + <string>Copyright (c) 2011, The WebRTC project authors. All rights reserved.</string> + <key>version</key> + <string>m114.5735.08.61-debug.9571929057</string> + <key>name</key> + <string>webrtc</string> + <key>vcs_branch</key> + <string>secondlife</string> + <key>vcs_revision</key> + <string>d3f62d32bac8694d3c7423c731ae30c113bf6a11</string> + <key>vcs_url</key> + <string>https://github.com/secondlife/3p-webrtc-build</string> + <key>canonical_repo</key> + <string>https://github.com/secondlife/3p-webrtc-build</string> + </map> <key>xxhash</key> <map> <key>platforms</key> @@ -2909,14 +2977,54 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <key>archive</key> <map> <key>hash</key> - <string>1a73c476b371b62066d1c3eced249660e9467e53</string> + <string>e4f77ba0a9b8ec3cc3fabc51c4da81d2</string> + <key>url</key> + <string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/110070/956941/xxhash-0.8.1.578006-windows-578006.tar.bz2</string> + </map> + <key>name</key> + <string>common</string> + </map> + <key>darwin64</key> + <map> + <key>archive</key> + <map> + <key>hash</key> + <string>fdcc803a76a3359bb426db7dac161406676d51e7</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://github.com/secondlife/3p-xxhash/releases/download/v0.8.1-69ff69a/xxhash-0.8.1-69ff69a-common-69ff69a.tar.zst</string> + <string>https://github.com/secondlife/3p-xxhash/releases/download/v0.8.1.7501c90/xxhash-0.8.1.7501c90-darwin64-7501c90.tar.zst</string> </map> <key>name</key> - <string>common</string> + <string>darwin64</string> + </map> + <key>linux64</key> + <map> + <key>archive</key> + <map> + <key>hash</key> + <string>7acb3f94a549fbb9bd7bc16604e34f33c5365a9b</string> + <key>hash_algorithm</key> + <string>sha1</string> + <key>url</key> + <string>https://github.com/secondlife/3p-xxhash/releases/download/v0.8.1.7501c90/xxhash-0.8.1.7501c90-linux64-7501c90.tar.zst</string> + </map> + <key>name</key> + <string>linux64</string> + </map> + <key>windows64</key> + <map> + <key>archive</key> + <map> + <key>hash</key> + <string>4522d075ea4703ef4b527c3039864ef735ea7953</string> + <key>hash_algorithm</key> + <string>sha1</string> + <key>url</key> + <string>https://github.com/secondlife/3p-xxhash/releases/download/v0.8.1.7501c90/xxhash-0.8.1.7501c90-windows64-7501c90.tar.zst</string> + </map> + <key>name</key> + <string>windows64</string> </map> </map> <key>license</key> @@ -2926,7 +3034,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <key>copyright</key> <string>Copyright (c) 2012-2021 Yann Collet</string> <key>version</key> - <string>0.8.1-69ff69a</string> + <string>0.8.1.7501c90</string> <key>name</key> <string>xxhash</string> <key>description</key> diff --git a/etc/message.xml b/etc/message.xml index b444fe6c11..dee3fd72dd 100755 --- a/etc/message.xml +++ b/etc/message.xml @@ -506,6 +506,13 @@ <boolean>false</boolean> </map> + <key>VoiceSignalingRequest</key> + <map> + <key>flavor</key> + <string>llsd</string> + <key>trusted-sender</key> + <boolean>false</boolean> + </map> <!-- Server to client --> <key>RequiredVoiceVersion</key> <map> @@ -688,6 +695,9 @@ <key>ProvisionVoiceAccountRequest</key> <boolean>false</boolean> + + <key>VoiceSignalingRequest</key> + <boolean>false</boolean> <key>RemoteParcelRequest</key> <boolean>false</boolean> diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt index 154d83a640..873a8b34be 100644 --- a/indra/CMakeLists.txt +++ b/indra/CMakeLists.txt @@ -58,6 +58,7 @@ add_subdirectory(${LIBS_OPEN_PREFIX}llmessage) add_subdirectory(${LIBS_OPEN_PREFIX}llprimitive) add_subdirectory(${LIBS_OPEN_PREFIX}llrender) add_subdirectory(${LIBS_OPEN_PREFIX}llfilesystem) +add_subdirectory(${LIBS_OPEN_PREFIX}llwebrtc) add_subdirectory(${LIBS_OPEN_PREFIX}llwindow) add_subdirectory(${LIBS_OPEN_PREFIX}llxml) diff --git a/indra/cmake/00-Common.cmake b/indra/cmake/00-Common.cmake index c345cfac5c..4c98983df5 100644 --- a/indra/cmake/00-Common.cmake +++ b/indra/cmake/00-Common.cmake @@ -87,7 +87,7 @@ if (WINDOWS) if( ADDRESS_SIZE EQUAL 32 ) add_compile_options( /arch:SSE2 ) endif() - + # Are we using the crummy Visual Studio KDU build workaround? if (NOT VS_DISABLE_FATAL_WARNINGS) add_compile_options(/WX) @@ -103,6 +103,11 @@ if (WINDOWS) string(REPLACE "/Zi" "/Z7" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}") string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") endif() + + # workaround for github runner image breakage: + # https://github.com/actions/runner-images/issues/10004#issuecomment-2153445161 + # can be removed after the above issue is resolved and deployed across GHA + add_compile_definitions(_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR) endif (WINDOWS) @@ -190,6 +195,10 @@ if (LINUX OR DARWIN) list(APPEND GCC_WARNINGS -Wno-reorder -Wno-non-virtual-dtor ) + if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 13) + list(APPEND GCC_WARNINGS -Wno-unused-but-set-variable -Wno-unused-variable ) + endif() + add_compile_options(${GCC_WARNINGS}) add_compile_options(-m${ADDRESS_SIZE}) endif (LINUX OR DARWIN) diff --git a/indra/cmake/CMakeLists.txt b/indra/cmake/CMakeLists.txt index df05032172..907f08833f 100644 --- a/indra/cmake/CMakeLists.txt +++ b/indra/cmake/CMakeLists.txt @@ -65,6 +65,7 @@ set(cmake_SOURCE_FILES ViewerMiscLibs.cmake VisualLeakDetector.cmake LibVLCPlugin.cmake + WebRTC.cmake XmlRpcEpi.cmake xxHash.cmake ZLIBNG.cmake diff --git a/indra/cmake/WebRTC.cmake b/indra/cmake/WebRTC.cmake new file mode 100644 index 0000000000..d336dcdb10 --- /dev/null +++ b/indra/cmake/WebRTC.cmake @@ -0,0 +1,34 @@ +# -*- cmake -*- +include(Linking) +include(Prebuilt) + +include_guard() + +add_library( ll::webrtc INTERFACE IMPORTED ) +target_include_directories( ll::webrtc SYSTEM INTERFACE "${LIBS_PREBUILT_DIR}/include/webrtc" "${LIBS_PREBUILT_DIR}/include/webrtc/third_party/abseil-cpp") +if (NOT USESYSTEMLIBS) +use_prebuilt_binary(webrtc) +endif () + +if (WINDOWS) + target_link_libraries( ll::webrtc INTERFACE webrtc.lib ) +elseif (DARWIN) + FIND_LIBRARY(COREAUDIO_LIBRARY CoreAudio) + FIND_LIBRARY(COREGRAPHICS_LIBRARY CoreGraphics) + FIND_LIBRARY(AUDIOTOOLBOX_LIBRARY AudioToolbox) + FIND_LIBRARY(COREFOUNDATION_LIBRARY CoreFoundation) + FIND_LIBRARY(COCOA_LIBRARY Cocoa) + + target_link_libraries( ll::webrtc INTERFACE + libwebrtc.a + ${COREAUDIO_LIBRARY} + ${AUDIOTOOLBOX_LIBRARY} + ${COREGRAPHICS_LIBRARY} + ${COREFOUNDATION_LIBRARY} + ${COCOA_LIBRARY} + ) +elseif (LINUX) + target_link_libraries( ll::webrtc INTERFACE libwebrtc.a X11 ) +endif (WINDOWS) + + diff --git a/indra/llcommon/llprofilercategories.h b/indra/llcommon/llprofilercategories.h index 617431f629..0de343d020 100644 --- a/indra/llcommon/llprofilercategories.h +++ b/indra/llcommon/llprofilercategories.h @@ -1,5 +1,5 @@ /** - * @file llprofiler_ategories.h + * @file llprofilercategories.h * @brief Profiling categories to minimize Tracy memory usage when viewing captures. * * $LicenseInfo:firstyear=2022&license=viewerlgpl$ @@ -33,7 +33,7 @@ // LL_PROFILER_CATEGORY_ENABLE_DRAWPOOL // LL_PROFILER_CATEGORY_ENABLE_LLSD // LL_PROFILER_CATEGORY_ENABLE_MEMORY -// LL_PROFILER_CATEGORY_ENABLE_SHADERS +// LL_PROFILER_CATEGORY_ENABLE_SHADER // // NOTE: You can still manually use: // LL_PROFILE_ZONE_SCOPED(); @@ -67,6 +67,7 @@ #define LL_PROFILER_CATEGORY_ENABLE_VERTEX 1 #define LL_PROFILER_CATEGORY_ENABLE_VOLUME 1 #define LL_PROFILER_CATEGORY_ENABLE_WIN32 1 +#define LL_PROFILER_CATEGORY_ENABLE_VOICE 1 #if LL_PROFILER_CATEGORY_ENABLE_APP #define LL_PROFILE_ZONE_NAMED_CATEGORY_APP LL_PROFILE_ZONE_NAMED @@ -276,5 +277,13 @@ #define LL_PROFILE_ZONE_SCOPED_CATEGORY_WIN32 #endif +#if LL_PROFILER_CATEGORY_ENABLE_VOICE +#define LL_PROFILE_ZONE_NAMED_CATEGORY_VOICE LL_PROFILE_ZONE_NAMED +#define LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE LL_PROFILE_ZONE_SCOPED +#else +#define LL_PROFILE_ZONE_NAMED_CATEGORY_VOICE(name) +#define LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE +#endif + #endif // LL_PROFILER_CATEGORIES_H diff --git a/indra/llmath/llquaternion.h b/indra/llmath/llquaternion.h index fbe4da97f7..be7f119a2b 100644 --- a/indra/llmath/llquaternion.h +++ b/indra/llmath/llquaternion.h @@ -132,6 +132,7 @@ public: friend LLQuaternion operator~(const LLQuaternion &a); // Returns a* (Conjugate of a) bool operator==(const LLQuaternion &b) const; // Returns a == b bool operator!=(const LLQuaternion &b) const; // Returns a != b + F64 operator[](int idx) const { return mQ[idx]; } friend const LLQuaternion& operator*=(LLQuaternion &a, const LLQuaternion &b); // Returns a * b diff --git a/indra/llwebrtc/CMakeLists.txt b/indra/llwebrtc/CMakeLists.txt new file mode 100644 index 0000000000..fb670322ff --- /dev/null +++ b/indra/llwebrtc/CMakeLists.txt @@ -0,0 +1,72 @@ +# -*- cmake -*- + +# some webrtc headers require C++ 20 +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +include(00-Common) +include(Linking) +include(WebRTC) + +project(llwebrtc) + +if (LINUX) + add_compile_options(-Wno-deprecated-declarations) # webrtc::CreateAudioDeviceWithDataObserver is deprecated +endif (LINUX) + +set(llwebrtc_SOURCE_FILES + llwebrtc.cpp + ) + +set(llwebrtc_HEADER_FILES + CMakeLists.txt + llwebrtc.h + llwebrtc_impl.h + ) + +list(APPEND llwebrtc_SOURCE_FILES ${llwebrtc_HEADER_FILES}) + +add_library (llwebrtc SHARED ${llwebrtc_SOURCE_FILES}) + +set_target_properties(llwebrtc PROPERTIES PUBLIC_HEADER llwebrtc.h) + +if (WINDOWS) + target_link_libraries(llwebrtc PRIVATE ll::webrtc + secur32 + winmm + dmoguids + wmcodecdspuuid + msdmo + strmiids + iphlpapi) +elseif (DARWIN) + target_link_libraries(llwebrtc PRIVATE ll::webrtc) +elseif (LINUX) + target_link_libraries(llwebrtc PRIVATE ll::webrtc) +endif (WINDOWS) + +target_include_directories( llwebrtc INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) + +if (WINDOWS) + set_property(TARGET llwebrtc PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreadedDebug") +endif (WINDOWS) + +ADD_CUSTOM_COMMAND(TARGET llwebrtc POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + $<TARGET_FILE:llwebrtc> + ${SHARED_LIB_STAGING_DIR}) +# Add tests +if (LL_TESTS) +endif (LL_TESTS) + +if (INSTALL) + if (EXISTS ${CMAKE_SYSROOT}/usr/lib/${ARCH}-linux-gnu) + set(_LIB lib/${ARCH}-linux-gnu) + elseif (EXISTS /lib64) + set(_LIB lib64) + else () + set(_LIB lib) + endif () + install(TARGETS ${PROJECT_NAME} DESTINATION ${_LIB}) +endif () diff --git a/indra/llwebrtc/llwebrtc.cpp b/indra/llwebrtc/llwebrtc.cpp new file mode 100644 index 0000000000..d5bd913315 --- /dev/null +++ b/indra/llwebrtc/llwebrtc.cpp @@ -0,0 +1,1345 @@ +/** + * @file llwebrtc.cpp + * @brief WebRTC interface implementation + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llwebrtc_impl.h" +#include <algorithm> +#include <string.h> + +#include "api/audio_codecs/audio_decoder_factory.h" +#include "api/audio_codecs/audio_encoder_factory.h" +#include "api/audio_codecs/builtin_audio_decoder_factory.h" +#include "api/audio_codecs/builtin_audio_encoder_factory.h" +#include "api/media_stream_interface.h" +#include "api/media_stream_track.h" +#include "modules/audio_processing/audio_buffer.h" +#include "modules/audio_mixer/audio_mixer_impl.h" + +namespace llwebrtc +{ + +static int16_t PLAYOUT_DEVICE_DEFAULT = -1; +static int16_t PLAYOUT_DEVICE_BAD = -2; +static int16_t RECORD_DEVICE_DEFAULT = -1; +static int16_t RECORD_DEVICE_BAD = -2; + +LLAudioDeviceObserver::LLAudioDeviceObserver() : mSumVector {0}, mMicrophoneEnergy(0.0) {} + +float LLAudioDeviceObserver::getMicrophoneEnergy() { return mMicrophoneEnergy; } + +// TODO: Pull smoothing/filtering code into a common helper function +// for LLAudioDeviceObserver and LLCustomProcessor + +void LLAudioDeviceObserver::OnCaptureData(const void *audio_samples, + const size_t num_samples, + const size_t bytes_per_sample, + const size_t num_channels, + const uint32_t samples_per_sec) +{ + // calculate the energy + float energy = 0; + const short *samples = (const short *) audio_samples; + for (size_t index = 0; index < num_samples * num_channels; index++) + { + float sample = (static_cast<float>(samples[index]) / (float) 32767); + energy += sample * sample; + } + + // smooth it. + size_t buffer_size = sizeof(mSumVector) / sizeof(mSumVector[0]); + float totalSum = 0; + int i; + for (i = 0; i < (buffer_size - 1); i++) + { + mSumVector[i] = mSumVector[i + 1]; + totalSum += mSumVector[i]; + } + mSumVector[i] = energy; + totalSum += energy; + mMicrophoneEnergy = std::sqrt(totalSum / (num_samples * buffer_size)); +} + +void LLAudioDeviceObserver::OnRenderData(const void *audio_samples, + const size_t num_samples, + const size_t bytes_per_sample, + const size_t num_channels, + const uint32_t samples_per_sec) +{ +} + +LLCustomProcessor::LLCustomProcessor() : mSampleRateHz(0), mNumChannels(0), mMicrophoneEnergy(0.0), mGain(1.0) +{ + memset(mSumVector, 0, sizeof(mSumVector)); +} + +void LLCustomProcessor::Initialize(int sample_rate_hz, int num_channels) +{ + mSampleRateHz = sample_rate_hz; + mNumChannels = num_channels; + memset(mSumVector, 0, sizeof(mSumVector)); +} + +void LLCustomProcessor::Process(webrtc::AudioBuffer *audio_in) +{ + webrtc::StreamConfig stream_config; + stream_config.set_sample_rate_hz(mSampleRateHz); + stream_config.set_num_channels(mNumChannels); + std::vector<float *> frame; + std::vector<float> frame_samples; + + if (audio_in->num_channels() < 1 || audio_in->num_frames() < 480) + { + return; + } + + // grab the input audio + frame_samples.resize(stream_config.num_samples()); + frame.resize(stream_config.num_channels()); + for (size_t ch = 0; ch < stream_config.num_channels(); ++ch) + { + frame[ch] = &(frame_samples)[ch * stream_config.num_frames()]; + } + + audio_in->CopyTo(stream_config, &frame[0]); + + // calculate the energy + float energy = 0; + for (size_t index = 0; index < stream_config.num_samples(); index++) + { + float sample = frame_samples[index]; + sample = sample * mGain; // apply gain + frame_samples[index] = sample; // write processed sample back to buffer. + energy += sample * sample; + } + + audio_in->CopyFrom(&frame[0], stream_config); + + // smooth it. + size_t buffer_size = sizeof(mSumVector) / sizeof(mSumVector[0]); + float totalSum = 0; + int i; + for (i = 0; i < (buffer_size - 1); i++) + { + mSumVector[i] = mSumVector[i + 1]; + totalSum += mSumVector[i]; + } + mSumVector[i] = energy; + totalSum += energy; + mMicrophoneEnergy = std::sqrt(totalSum / (stream_config.num_samples() * buffer_size)); +} + +// +// LLWebRTCImpl implementation +// + +LLWebRTCImpl::LLWebRTCImpl() : + mPeerCustomProcessor(nullptr), + mMute(true), + mTuningMode(false), + mPlayoutDevice(0), + mRecordingDevice(0), + mTuningAudioDeviceObserver(nullptr) +{ +} + +void LLWebRTCImpl::init() +{ + mPlayoutDevice = 0; + mRecordingDevice = 0; + rtc::InitializeSSL(); + + // Normal logging is rather spammy, so turn it off. + rtc::LogMessage::LogToDebug(rtc::LS_NONE); + rtc::LogMessage::SetLogToStderr(true); + + mTaskQueueFactory = webrtc::CreateDefaultTaskQueueFactory(); + + // Create the native threads. + mNetworkThread = rtc::Thread::CreateWithSocketServer(); + mNetworkThread->SetName("WebRTCNetworkThread", nullptr); + mNetworkThread->Start(); + mWorkerThread = rtc::Thread::Create(); + mWorkerThread->SetName("WebRTCWorkerThread", nullptr); + mWorkerThread->Start(); + mSignalingThread = rtc::Thread::Create(); + mSignalingThread->SetName("WebRTCSignalingThread", nullptr); + mSignalingThread->Start(); + + mTuningAudioDeviceObserver = new LLAudioDeviceObserver; + mWorkerThread->PostTask( + [this]() + { + // Initialize the audio devices on the Worker Thread + mTuningDeviceModule = + webrtc::CreateAudioDeviceWithDataObserver(webrtc::AudioDeviceModule::AudioLayer::kPlatformDefaultAudio, + mTaskQueueFactory.get(), + std::unique_ptr<webrtc::AudioDeviceDataObserver>(mTuningAudioDeviceObserver)); + + mTuningDeviceModule->Init(); + mTuningDeviceModule->SetPlayoutDevice(mPlayoutDevice); + mTuningDeviceModule->SetRecordingDevice(mRecordingDevice); + mTuningDeviceModule->EnableBuiltInAEC(false); + mTuningDeviceModule->SetAudioDeviceSink(this); + mTuningDeviceModule->InitMicrophone(); + mTuningDeviceModule->InitSpeaker(); + mTuningDeviceModule->InitRecording(); + mTuningDeviceModule->InitPlayout(); + mTuningDeviceModule->SetStereoRecording(true); + mTuningDeviceModule->SetStereoPlayout(true); + updateDevices(); + }); + + mWorkerThread->BlockingCall( + [this]() + { + // the peer device module doesn't need an observer + // as we pull peer data after audio processing. + mPeerDeviceModule = webrtc::CreateAudioDeviceWithDataObserver(webrtc::AudioDeviceModule::AudioLayer::kPlatformDefaultAudio, + mTaskQueueFactory.get(), + nullptr); + mPeerDeviceModule->Init(); + mPeerDeviceModule->SetPlayoutDevice(mPlayoutDevice); + mPeerDeviceModule->SetRecordingDevice(mRecordingDevice); + mPeerDeviceModule->EnableBuiltInAEC(false); + mPeerDeviceModule->InitMicrophone(); + mPeerDeviceModule->InitSpeaker(); + mPeerDeviceModule->InitRecording(); + mPeerDeviceModule->InitPlayout(); + mPeerDeviceModule->SetStereoRecording(true); + mPeerDeviceModule->SetStereoPlayout(true); + }); + + // The custom processor allows us to retrieve audio data (and levels) + // from after other audio processing such as AEC, AGC, etc. + mPeerCustomProcessor = new LLCustomProcessor; + webrtc::AudioProcessingBuilder apb; + apb.SetCapturePostProcessing(std::unique_ptr<webrtc::CustomProcessing>(mPeerCustomProcessor)); + mAudioProcessingModule = apb.Create(); + + webrtc::AudioProcessing::Config apm_config; + apm_config.echo_canceller.enabled = false; + apm_config.echo_canceller.mobile_mode = false; + apm_config.gain_controller1.enabled = false; + apm_config.gain_controller1.mode = webrtc::AudioProcessing::Config::GainController1::kAdaptiveAnalog; + apm_config.gain_controller2.enabled = false; + apm_config.high_pass_filter.enabled = true; + apm_config.noise_suppression.enabled = true; + apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kVeryHigh; + apm_config.transient_suppression.enabled = true; + apm_config.pipeline.multi_channel_render = true; + apm_config.pipeline.multi_channel_capture = true; + apm_config.pipeline.multi_channel_capture = true; + + webrtc::ProcessingConfig processing_config; + processing_config.input_stream().set_num_channels(2); + processing_config.input_stream().set_sample_rate_hz(48000); + processing_config.output_stream().set_num_channels(2); + processing_config.output_stream().set_sample_rate_hz(48000); + processing_config.reverse_input_stream().set_num_channels(2); + processing_config.reverse_input_stream().set_sample_rate_hz(48000); + processing_config.reverse_output_stream().set_num_channels(2); + processing_config.reverse_output_stream().set_sample_rate_hz(48000); + + mAudioProcessingModule->ApplyConfig(apm_config); + mAudioProcessingModule->Initialize(processing_config); + + + mPeerConnectionFactory = webrtc::CreatePeerConnectionFactory(mNetworkThread.get(), + mWorkerThread.get(), + mSignalingThread.get(), + mPeerDeviceModule, + webrtc::CreateBuiltinAudioEncoderFactory(), + webrtc::CreateBuiltinAudioDecoderFactory(), + nullptr /* video_encoder_factory */, + nullptr /* video_decoder_factory */, + nullptr /* audio_mixer */, + mAudioProcessingModule); + + mWorkerThread->BlockingCall([this]() { mPeerDeviceModule->StartPlayout(); }); +} + +void LLWebRTCImpl::terminate() +{ + for (auto &connection : mPeerConnections) + { + connection->terminate(); + } + + // connection->terminate() above spawns a number of Signaling thread calls to + // shut down the connection. The following Blocking Call will wait + // until they're done before it's executed, allowing time to clean up. + + mSignalingThread->BlockingCall([this]() { mPeerConnectionFactory = nullptr; }); + + mPeerConnections.clear(); + + mWorkerThread->BlockingCall( + [this]() + { + if (mTuningDeviceModule) + { + mTuningDeviceModule->StopRecording(); + mTuningDeviceModule->Terminate(); + } + if (mPeerDeviceModule) + { + mPeerDeviceModule->StopRecording(); + mPeerDeviceModule->Terminate(); + } + mTuningDeviceModule = nullptr; + mPeerDeviceModule = nullptr; + mTaskQueueFactory = nullptr; + }); +} + +// +// Devices functions +// +// Most device-related functionality needs to happen +// on the worker thread (the audio thread,) so those calls will be +// proxied over to that thread. +// +void LLWebRTCImpl::setRecording(bool recording) +{ + mWorkerThread->PostTask( + [this, recording]() + { + if (recording) + { + mPeerDeviceModule->StartRecording(); + } + else + { + mPeerDeviceModule->StopRecording(); + } + }); +} + +void LLWebRTCImpl::setAudioConfig(LLWebRTCDeviceInterface::AudioConfig config) +{ + webrtc::AudioProcessing::Config apm_config; + apm_config.echo_canceller.enabled = config.mEchoCancellation; + apm_config.echo_canceller.mobile_mode = false; + apm_config.gain_controller1.enabled = config.mAGC; + apm_config.gain_controller1.mode = webrtc::AudioProcessing::Config::GainController1::kAdaptiveAnalog; + apm_config.gain_controller2.enabled = false; + apm_config.high_pass_filter.enabled = true; + apm_config.transient_suppression.enabled = true; + apm_config.pipeline.multi_channel_render = true; + apm_config.pipeline.multi_channel_capture = true; + apm_config.pipeline.multi_channel_capture = true; + + switch (config.mNoiseSuppressionLevel) + { + case LLWebRTCDeviceInterface::AudioConfig::NOISE_SUPPRESSION_LEVEL_NONE: + apm_config.noise_suppression.enabled = false; + apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kLow; + break; + case LLWebRTCDeviceInterface::AudioConfig::NOISE_SUPPRESSION_LEVEL_LOW: + apm_config.noise_suppression.enabled = true; + apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kLow; + break; + case LLWebRTCDeviceInterface::AudioConfig::NOISE_SUPPRESSION_LEVEL_MODERATE: + apm_config.noise_suppression.enabled = true; + apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kModerate; + break; + case LLWebRTCDeviceInterface::AudioConfig::NOISE_SUPPRESSION_LEVEL_HIGH: + apm_config.noise_suppression.enabled = true; + apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kHigh; + break; + case LLWebRTCDeviceInterface::AudioConfig::NOISE_SUPPRESSION_LEVEL_VERY_HIGH: + apm_config.noise_suppression.enabled = true; + apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kVeryHigh; + break; + default: + apm_config.noise_suppression.enabled = false; + apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kLow; + } + mAudioProcessingModule->ApplyConfig(apm_config); +} + +void LLWebRTCImpl::refreshDevices() +{ + mWorkerThread->PostTask([this]() { updateDevices(); }); +} + +void LLWebRTCImpl::setDevicesObserver(LLWebRTCDevicesObserver *observer) { mVoiceDevicesObserverList.emplace_back(observer); } + +void LLWebRTCImpl::unsetDevicesObserver(LLWebRTCDevicesObserver *observer) +{ + std::vector<LLWebRTCDevicesObserver *>::iterator it = + std::find(mVoiceDevicesObserverList.begin(), mVoiceDevicesObserverList.end(), observer); + if (it != mVoiceDevicesObserverList.end()) + { + mVoiceDevicesObserverList.erase(it); + } +} + +void ll_set_device_module_capture_device(rtc::scoped_refptr<webrtc::AudioDeviceModule> device_module, int16_t device) +{ + device_module->StopRecording(); +#if WEBRTC_WIN + if (device < 0) + { + device_module->SetRecordingDevice(webrtc::AudioDeviceModule::kDefaultDevice); + } + else + { + device_module->SetRecordingDevice(device); + } +#else + // passed in default is -1, but the device list + // has it at 0 + device_module->SetRecordingDevice(device + 1); +#endif + device_module->InitMicrophone(); + device_module->InitRecording(); + device_module->SetStereoRecording(false); + device_module->StartRecording(); +} + +void LLWebRTCImpl::setCaptureDevice(const std::string &id) +{ + int16_t recordingDevice = RECORD_DEVICE_DEFAULT; + if (id != "Default") + { + for (int16_t i = 0; i < mRecordingDeviceList.size(); i++) + { + if (mRecordingDeviceList[i].mID == id) + { + recordingDevice = i; + break; + } + } + } + if (recordingDevice == mRecordingDevice) + { + return; + } + mRecordingDevice = recordingDevice; + if (mTuningMode) + { + mWorkerThread->PostTask([this, recordingDevice]() { ll_set_device_module_capture_device(mTuningDeviceModule, recordingDevice); }); + } + else + { + mWorkerThread->PostTask([this, recordingDevice]() { ll_set_device_module_capture_device(mPeerDeviceModule, recordingDevice); }); + } +} + + +void ll_set_device_module_render_device(rtc::scoped_refptr<webrtc::AudioDeviceModule> device_module, int16_t device) +{ + device_module->StopPlayout(); +#if WEBRTC_WIN + if (device < 0) + { + device_module->SetPlayoutDevice(webrtc::AudioDeviceModule::kDefaultDevice); + } + else + { + device_module->SetPlayoutDevice(device); + } +#else + device_module->SetPlayoutDevice(device + 1); +#endif + device_module->InitSpeaker(); + device_module->InitPlayout(); + device_module->SetStereoPlayout(true); +} + +void LLWebRTCImpl::setRenderDevice(const std::string &id) +{ + int16_t playoutDevice = PLAYOUT_DEVICE_DEFAULT; + if (id != "Default") + { + for (int16_t i = 0; i < mPlayoutDeviceList.size(); i++) + { + if (mPlayoutDeviceList[i].mID == id) + { + playoutDevice = i; + break; + } + } + } + if (playoutDevice == mPlayoutDevice) + { + return; + } + mPlayoutDevice = playoutDevice; + + if (mTuningMode) + { + mWorkerThread->PostTask( + [this, playoutDevice]() + { + ll_set_device_module_render_device(mTuningDeviceModule, playoutDevice); + }); + } + else + { + mWorkerThread->PostTask( + [this, playoutDevice]() + { + ll_set_device_module_render_device(mPeerDeviceModule, playoutDevice); + mPeerDeviceModule->StartPlayout(); + }); + } +} + +// updateDevices needs to happen on the worker thread. +void LLWebRTCImpl::updateDevices() +{ + int16_t renderDeviceCount = mTuningDeviceModule->PlayoutDevices(); + + mPlayoutDeviceList.clear(); +#if WEBRTC_WIN + int16_t index = 0; +#else + // index zero is always "Default" for darwin/linux, + // which is a special case, so skip it. + int16_t index = 1; +#endif + for (; index < renderDeviceCount; index++) + { + char name[webrtc::kAdmMaxDeviceNameSize]; + char guid[webrtc::kAdmMaxGuidSize]; + mTuningDeviceModule->PlayoutDeviceName(index, name, guid); + mPlayoutDeviceList.emplace_back(name, guid); + } + + int16_t captureDeviceCount = mTuningDeviceModule->RecordingDevices(); + + mRecordingDeviceList.clear(); +#if WEBRTC_WIN + index = 0; +#else + // index zero is always "Default" for darwin/linux, + // which is a special case, so skip it. + index = 1; +#endif + for (; index < captureDeviceCount; index++) + { + char name[webrtc::kAdmMaxDeviceNameSize]; + char guid[webrtc::kAdmMaxGuidSize]; + mTuningDeviceModule->RecordingDeviceName(index, name, guid); + mRecordingDeviceList.emplace_back(name, guid); + } + + for (auto &observer : mVoiceDevicesObserverList) + { + observer->OnDevicesChanged(mPlayoutDeviceList, mRecordingDeviceList); + } +} + +void LLWebRTCImpl::OnDevicesUpdated() +{ + // reset these to a bad value so an update is forced + mRecordingDevice = RECORD_DEVICE_BAD; + mPlayoutDevice = PLAYOUT_DEVICE_BAD; + + updateDevices(); +} + + +void LLWebRTCImpl::setTuningMode(bool enable) +{ + mTuningMode = enable; + mWorkerThread->PostTask( + [this, enable] { + if (enable) + { + mPeerDeviceModule->StopRecording(); + mPeerDeviceModule->StopPlayout(); + ll_set_device_module_render_device(mTuningDeviceModule, mPlayoutDevice); + ll_set_device_module_capture_device(mTuningDeviceModule, mRecordingDevice); + mTuningDeviceModule->InitPlayout(); + mTuningDeviceModule->InitRecording(); + mTuningDeviceModule->StartRecording(); + // TODO: Starting Playout on the TDM appears to create an audio artifact (click) + // in this case, so disabling it for now. We may have to do something different + // if we enable 'echo playback' via the TDM when tuning. + //mTuningDeviceModule->StartPlayout(); + } + else + { + mTuningDeviceModule->StopRecording(); + //mTuningDeviceModule->StopPlayout(); + ll_set_device_module_render_device(mPeerDeviceModule, mPlayoutDevice); + ll_set_device_module_capture_device(mPeerDeviceModule, mRecordingDevice); + mPeerDeviceModule->InitPlayout(); + mPeerDeviceModule->InitRecording(); + mPeerDeviceModule->StartPlayout(); + mPeerDeviceModule->StartRecording(); + } + } + ); + mSignalingThread->PostTask( + [this, enable] + { + for (auto &connection : mPeerConnections) + { + if (enable) + { + connection->enableSenderTracks(false); + } + else + { + connection->resetMute(); + } + connection->enableReceiverTracks(!enable); + } + }); +} + +float LLWebRTCImpl::getTuningAudioLevel() { return -20 * log10f(mTuningAudioDeviceObserver->getMicrophoneEnergy()); } + +float LLWebRTCImpl::getPeerConnectionAudioLevel() { return -20 * log10f(mPeerCustomProcessor->getMicrophoneEnergy()); } + +void LLWebRTCImpl::setPeerConnectionGain(float gain) { mPeerCustomProcessor->setGain(gain); } + + +// +// Peer Connection Helpers +// + +LLWebRTCPeerConnectionInterface *LLWebRTCImpl::newPeerConnection() +{ + rtc::scoped_refptr<LLWebRTCPeerConnectionImpl> peerConnection = rtc::scoped_refptr<LLWebRTCPeerConnectionImpl>(new rtc::RefCountedObject<LLWebRTCPeerConnectionImpl>()); + peerConnection->init(this); + + mPeerConnections.emplace_back(peerConnection); + peerConnection->enableSenderTracks(!mMute); + return peerConnection.get(); +} + +void LLWebRTCImpl::freePeerConnection(LLWebRTCPeerConnectionInterface* peer_connection) +{ + std::vector<rtc::scoped_refptr<LLWebRTCPeerConnectionImpl>>::iterator it = + std::find(mPeerConnections.begin(), mPeerConnections.end(), peer_connection); + if (it != mPeerConnections.end()) + { + mPeerConnections.erase(it); + } + if (mPeerConnections.empty()) + { + setRecording(false); + } +} + + +// +// LLWebRTCPeerConnectionImpl implementation. +// +// Most peer connection (signaling) happens on +// the signaling thread. + +LLWebRTCPeerConnectionImpl::LLWebRTCPeerConnectionImpl() : + mWebRTCImpl(nullptr), + mPeerConnection(nullptr), + mMute(false), + mAnswerReceived(false) +{ +} + +LLWebRTCPeerConnectionImpl::~LLWebRTCPeerConnectionImpl() +{ + mSignalingObserverList.clear(); + mDataObserverList.clear(); +} + +// +// LLWebRTCPeerConnection interface +// + +void LLWebRTCPeerConnectionImpl::init(LLWebRTCImpl * webrtc_impl) +{ + mWebRTCImpl = webrtc_impl; + mPeerConnectionFactory = mWebRTCImpl->getPeerConnectionFactory(); +} +void LLWebRTCPeerConnectionImpl::terminate() +{ + mWebRTCImpl->PostSignalingTask( + [=]() + { + if (mPeerConnection) + { + if (mDataChannel) + { + { + mDataChannel->Close(); + mDataChannel = nullptr; + } + } + + mPeerConnection->Close(); + if (mLocalStream) + { + auto tracks = mLocalStream->GetAudioTracks(); + for (auto& track : tracks) + { + mLocalStream->RemoveTrack(track); + } + mLocalStream = nullptr; + } + mPeerConnection = nullptr; + + for (auto &observer : mSignalingObserverList) + { + observer->OnPeerConnectionClosed(); + } + } + }); +} + +void LLWebRTCPeerConnectionImpl::setSignalingObserver(LLWebRTCSignalingObserver *observer) { mSignalingObserverList.emplace_back(observer); } + +void LLWebRTCPeerConnectionImpl::unsetSignalingObserver(LLWebRTCSignalingObserver *observer) +{ + std::vector<LLWebRTCSignalingObserver *>::iterator it = + std::find(mSignalingObserverList.begin(), mSignalingObserverList.end(), observer); + if (it != mSignalingObserverList.end()) + { + mSignalingObserverList.erase(it); + } +} + + +bool LLWebRTCPeerConnectionImpl::initializeConnection(const LLWebRTCPeerConnectionInterface::InitOptions& options) +{ + RTC_DCHECK(!mPeerConnection); + mAnswerReceived = false; + + mWebRTCImpl->PostSignalingTask( + [this,options]() + { + webrtc::PeerConnectionInterface::RTCConfiguration config; + for (auto server : options.mServers) + { + webrtc::PeerConnectionInterface::IceServer ice_server; + for (auto url : server.mUrls) + { + ice_server.urls.push_back(url); + } + ice_server.username = server.mUserName; + ice_server.password = server.mPassword; + config.servers.push_back(ice_server); + } + config.sdp_semantics = webrtc::SdpSemantics::kUnifiedPlan; + + config.set_min_port(60000); + config.set_max_port(60100); + + webrtc::PeerConnectionDependencies pc_dependencies(this); + auto error_or_peer_connection = mPeerConnectionFactory->CreatePeerConnectionOrError(config, std::move(pc_dependencies)); + if (error_or_peer_connection.ok()) + { + mPeerConnection = std::move(error_or_peer_connection.value()); + } + else + { + RTC_LOG(LS_ERROR) << __FUNCTION__ << "Error creating peer connection: " << error_or_peer_connection.error().message(); + for (auto &observer : mSignalingObserverList) + { + observer->OnRenegotiationNeeded(); + } + return; + } + + webrtc::DataChannelInit init; + init.ordered = true; + + auto data_channel_or_error = mPeerConnection->CreateDataChannelOrError("SLData", &init); + if (data_channel_or_error.ok()) + { + mDataChannel = std::move(data_channel_or_error.value()); + + mDataChannel->RegisterObserver(this); + } + + cricket::AudioOptions audioOptions; + audioOptions.auto_gain_control = true; + audioOptions.echo_cancellation = true; + audioOptions.noise_suppression = true; + + mLocalStream = mPeerConnectionFactory->CreateLocalMediaStream("SLStream"); + + rtc::scoped_refptr<webrtc::AudioTrackInterface> audio_track( + mPeerConnectionFactory->CreateAudioTrack("SLAudio", mPeerConnectionFactory->CreateAudioSource(audioOptions).get())); + audio_track->set_enabled(false); + mLocalStream->AddTrack(audio_track); + + mPeerConnection->AddTrack(audio_track, {"SLStream"}); + + auto senders = mPeerConnection->GetSenders(); + + for (auto &sender : senders) + { + webrtc::RtpParameters params; + webrtc::RtpCodecParameters codecparam; + codecparam.name = "opus"; + codecparam.kind = cricket::MEDIA_TYPE_AUDIO; + codecparam.clock_rate = 48000; + codecparam.num_channels = 2; + codecparam.parameters["stereo"] = "1"; + codecparam.parameters["sprop-stereo"] = "1"; + params.codecs.push_back(codecparam); + sender->SetParameters(params); + } + + auto receivers = mPeerConnection->GetReceivers(); + for (auto &receiver : receivers) + { + webrtc::RtpParameters params; + webrtc::RtpCodecParameters codecparam; + codecparam.name = "opus"; + codecparam.kind = cricket::MEDIA_TYPE_AUDIO; + codecparam.clock_rate = 48000; + codecparam.num_channels = 2; + codecparam.parameters["stereo"] = "1"; + codecparam.parameters["sprop-stereo"] = "1"; + params.codecs.push_back(codecparam); + receiver->SetParameters(params); + } + + webrtc::PeerConnectionInterface::RTCOfferAnswerOptions offerOptions; + mPeerConnection->CreateOffer(this, offerOptions); + }); + + return true; +} + +bool LLWebRTCPeerConnectionImpl::shutdownConnection() +{ + terminate(); + return true; +} + +void LLWebRTCPeerConnectionImpl::enableSenderTracks(bool enable) +{ + // set_enabled shouldn't be done on the worker thread. + if (mPeerConnection) + { + auto senders = mPeerConnection->GetSenders(); + for (auto &sender : senders) + { + sender->track()->set_enabled(enable); + } + } +} + +void LLWebRTCPeerConnectionImpl::enableReceiverTracks(bool enable) +{ + // set_enabled shouldn't be done on the worker thread + if (mPeerConnection) + { + auto receivers = mPeerConnection->GetReceivers(); + for (auto &receiver : receivers) + { + receiver->track()->set_enabled(enable); + } + } +} + +// Tell the peer connection that we've received a SDP answer from the sim. +void LLWebRTCPeerConnectionImpl::AnswerAvailable(const std::string &sdp) +{ + RTC_LOG(LS_INFO) << __FUNCTION__ << " Remote SDP: " << sdp; + + mWebRTCImpl->PostSignalingTask( + [this, sdp]() + { + if (mPeerConnection) + { + RTC_LOG(LS_INFO) << __FUNCTION__ << " " << mPeerConnection->peer_connection_state(); + mPeerConnection->SetRemoteDescription(webrtc::CreateSessionDescription(webrtc::SdpType::kAnswer, sdp), + rtc::scoped_refptr<webrtc::SetRemoteDescriptionObserverInterface>(this)); + } + }); +} + + +// +// LLWebRTCAudioInterface implementation +// + +void LLWebRTCPeerConnectionImpl::setMute(bool mute) +{ + mMute = mute; + mWebRTCImpl->PostSignalingTask( + [this]() + { + if (mPeerConnection) + { + auto senders = mPeerConnection->GetSenders(); + + RTC_LOG(LS_INFO) << __FUNCTION__ << (mMute ? "disabling" : "enabling") << " streams count " << senders.size(); + for (auto &sender : senders) + { + auto track = sender->track(); + if (track) + { + track->set_enabled(!mMute); + } + } + } + }); +} + +void LLWebRTCPeerConnectionImpl::resetMute() +{ + setMute(mMute); +} + +void LLWebRTCPeerConnectionImpl::setReceiveVolume(float volume) +{ + mWebRTCImpl->PostSignalingTask( + [this, volume]() + { + if (mPeerConnection) + { + auto receivers = mPeerConnection->GetReceivers(); + + for (auto &receiver : receivers) + { + for (auto &stream : receiver->streams()) + { + for (auto &track : stream->GetAudioTracks()) + { + track->GetSource()->SetVolume(volume); + } + } + } + } + }); +} + +void LLWebRTCPeerConnectionImpl::setSendVolume(float volume) +{ + mWebRTCImpl->PostSignalingTask( + [this, volume]() + { + if (mLocalStream) + { + for (auto &track : mLocalStream->GetAudioTracks()) + { + track->GetSource()->SetVolume(volume*5.0); + } + } + }); +} + +// +// PeerConnectionObserver implementation. +// + +void LLWebRTCPeerConnectionImpl::OnAddTrack(rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver, + const std::vector<rtc::scoped_refptr<webrtc::MediaStreamInterface>> &streams) +{ + RTC_LOG(LS_INFO) << __FUNCTION__ << " " << receiver->id(); + webrtc::RtpParameters params; + webrtc::RtpCodecParameters codecparam; + codecparam.name = "opus"; + codecparam.kind = cricket::MEDIA_TYPE_AUDIO; + codecparam.clock_rate = 48000; + codecparam.num_channels = 2; + codecparam.parameters["stereo"] = "1"; + codecparam.parameters["sprop-stereo"] = "1"; + params.codecs.push_back(codecparam); + receiver->SetParameters(params); +} + +void LLWebRTCPeerConnectionImpl::OnRemoveTrack(rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver) +{ + RTC_LOG(LS_INFO) << __FUNCTION__ << " " << receiver->id(); +} + +void LLWebRTCPeerConnectionImpl::OnDataChannel(rtc::scoped_refptr<webrtc::DataChannelInterface> channel) +{ + if (mDataChannel) + { + mDataChannel->UnregisterObserver(); + } + mDataChannel = channel; + channel->RegisterObserver(this); +} + +void LLWebRTCPeerConnectionImpl::OnIceGatheringChange(webrtc::PeerConnectionInterface::IceGatheringState new_state) +{ + LLWebRTCSignalingObserver::EIceGatheringState webrtc_new_state = LLWebRTCSignalingObserver::EIceGatheringState::ICE_GATHERING_NEW; + switch (new_state) + { + case webrtc::PeerConnectionInterface::IceGatheringState::kIceGatheringNew: + webrtc_new_state = LLWebRTCSignalingObserver::EIceGatheringState::ICE_GATHERING_NEW; + break; + case webrtc::PeerConnectionInterface::IceGatheringState::kIceGatheringGathering: + webrtc_new_state = LLWebRTCSignalingObserver::EIceGatheringState::ICE_GATHERING_GATHERING; + break; + case webrtc::PeerConnectionInterface::IceGatheringState::kIceGatheringComplete: + webrtc_new_state = LLWebRTCSignalingObserver::EIceGatheringState::ICE_GATHERING_COMPLETE; + break; + default: + RTC_LOG(LS_ERROR) << __FUNCTION__ << " Bad Ice Gathering State" << new_state; + webrtc_new_state = LLWebRTCSignalingObserver::EIceGatheringState::ICE_GATHERING_NEW; + return; + } + + if (mAnswerReceived) + { + for (auto &observer : mSignalingObserverList) + { + observer->OnIceGatheringState(webrtc_new_state); + } + } +} + +// Called any time the PeerConnectionState changes. +void LLWebRTCPeerConnectionImpl::OnConnectionChange(webrtc::PeerConnectionInterface::PeerConnectionState new_state) +{ + RTC_LOG(LS_ERROR) << __FUNCTION__ << " Peer Connection State Change " << new_state; + + switch (new_state) + { + case webrtc::PeerConnectionInterface::PeerConnectionState::kConnected: + { + mWebRTCImpl->PostWorkerTask([this]() { + for (auto &observer : mSignalingObserverList) + { + observer->OnAudioEstablished(this); + } + }); + break; + } + case webrtc::PeerConnectionInterface::PeerConnectionState::kFailed: + case webrtc::PeerConnectionInterface::PeerConnectionState::kDisconnected: + { + for (auto &observer : mSignalingObserverList) + { + observer->OnRenegotiationNeeded(); + } + + break; + } + default: + { + break; + } + } +} + +// Convert an ICE candidate into a string appropriate for trickling +// to the Secondlife WebRTC server via the sim. +static std::string iceCandidateToTrickleString(const webrtc::IceCandidateInterface *candidate) +{ + std::ostringstream candidate_stream; + + candidate_stream << + candidate->candidate().foundation() << " " << + std::to_string(candidate->candidate().component()) << " " << + candidate->candidate().protocol() << " " << + std::to_string(candidate->candidate().priority()) << " " << + candidate->candidate().address().ipaddr().ToString() << " " << + candidate->candidate().address().PortAsString() << " typ "; + + if (candidate->candidate().type() == cricket::LOCAL_PORT_TYPE) + { + candidate_stream << "host"; + } + else if (candidate->candidate().type() == cricket::STUN_PORT_TYPE) + { + candidate_stream << "srflx " << + "raddr " << candidate->candidate().related_address().ipaddr().ToString() << " " << + "rport " << candidate->candidate().related_address().PortAsString(); + } + else if (candidate->candidate().type() == cricket::RELAY_PORT_TYPE) + { + candidate_stream << "relay " << + "raddr " << candidate->candidate().related_address().ipaddr().ToString() << " " << + "rport " << candidate->candidate().related_address().PortAsString(); + } + else if (candidate->candidate().type() == cricket::PRFLX_PORT_TYPE) + { + candidate_stream << "prflx " << + "raddr " << candidate->candidate().related_address().ipaddr().ToString() << " " << + "rport " << candidate->candidate().related_address().PortAsString(); + } + else { + RTC_LOG(LS_ERROR) << __FUNCTION__ << " Unknown candidate type " << candidate->candidate().type(); + } + if (candidate->candidate().protocol() == "tcp") + { + candidate_stream << " tcptype " << candidate->candidate().tcptype(); + } + + return candidate_stream.str(); +} + +// The webrtc library has a new ice candidate. +void LLWebRTCPeerConnectionImpl::OnIceCandidate(const webrtc::IceCandidateInterface *candidate) +{ + RTC_LOG(LS_INFO) << __FUNCTION__ << " " << candidate->sdp_mline_index(); + + if (!candidate) + { + RTC_LOG(LS_ERROR) << __FUNCTION__ << " No Ice Candidate Given"; + return; + } + if (mAnswerReceived) + { + // We've already received an answer SDP from the Secondlife WebRTC server + // so simply tell observers about our new ice candidate. + for (auto &observer : mSignalingObserverList) + { + LLWebRTCIceCandidate ice_candidate; + ice_candidate.mCandidate = iceCandidateToTrickleString(candidate); + ice_candidate.mMLineIndex = candidate->sdp_mline_index(); + ice_candidate.mSdpMid = candidate->sdp_mid(); + observer->OnIceCandidate(ice_candidate); + } + } + else + { + // As we've not yet received our answer, cache the candidate. + mCachedIceCandidates.push_back( + webrtc::CreateIceCandidate(candidate->sdp_mid(), + candidate->sdp_mline_index(), + candidate->candidate())); + } +} + +// +// CreateSessionDescriptionObserver implementation. +// +void LLWebRTCPeerConnectionImpl::OnSuccess(webrtc::SessionDescriptionInterface *desc) +{ + std::string sdp; + desc->ToString(&sdp); + RTC_LOG(LS_INFO) << sdp; + ; + // mangle the sdp as this is the only way currently to bump up + // the send audio rate to 48k + std::istringstream sdp_stream(sdp); + std::ostringstream sdp_mangled_stream; + std::string sdp_line; + std::string opus_payload; + while (std::getline(sdp_stream, sdp_line)) + { + int bandwidth = 0; + int payload_id = 0; + // force mono down, stereo up + if (std::sscanf(sdp_line.c_str(), "a=rtpmap:%i opus/%i/2", &payload_id, &bandwidth) == 2) + { + opus_payload = std::to_string(payload_id); + sdp_mangled_stream << "a=rtpmap:" << opus_payload << " opus/48000/2" << "\n"; + } + else if (sdp_line.find("a=fmtp:" + opus_payload) == 0) + { + sdp_mangled_stream << sdp_line << "a=fmtp:" << opus_payload + << " minptime=10;useinbandfec=1;stereo=1;sprop-stereo=1;maxplaybackrate=48000;sprop-maxplaybackrate=48000;sprop-maxcapturerate=48000\n"; + } + else + { + sdp_mangled_stream << sdp_line << "\n"; + } + } + + RTC_LOG(LS_INFO) << __FUNCTION__ << " Local SDP: " << sdp_mangled_stream.str(); + std::string mangled_sdp = sdp_mangled_stream.str(); + for (auto &observer : mSignalingObserverList) + { + observer->OnOfferAvailable(mangled_sdp); + } + + mPeerConnection->SetLocalDescription(std::unique_ptr<webrtc::SessionDescriptionInterface>( + webrtc::CreateSessionDescription(webrtc::SdpType::kOffer, mangled_sdp)), + rtc::scoped_refptr<webrtc::SetLocalDescriptionObserverInterface>(this)); + +} + +void LLWebRTCPeerConnectionImpl::OnFailure(webrtc::RTCError error) +{ + RTC_LOG(LS_ERROR) << ToString(error.type()) << ": " << error.message(); + for (auto &observer : mSignalingObserverList) + { + observer->OnRenegotiationNeeded(); + } +} + +// +// SetRemoteDescriptionObserverInterface implementation. +// +void LLWebRTCPeerConnectionImpl::OnSetRemoteDescriptionComplete(webrtc::RTCError error) +{ + // we've received an answer SDP from the sim. + + RTC_LOG(LS_INFO) << __FUNCTION__ << " " << mPeerConnection->signaling_state(); + if (!error.ok()) + { + RTC_LOG(LS_ERROR) << ToString(error.type()) << ": " << error.message(); + for (auto &observer : mSignalingObserverList) + { + observer->OnRenegotiationNeeded(); + } + return; + } + mAnswerReceived = true; + + // tell the observers about any cached ICE candidates. + for (auto &observer : mSignalingObserverList) + { + for (auto &candidate : mCachedIceCandidates) + { + LLWebRTCIceCandidate ice_candidate; + ice_candidate.mCandidate = iceCandidateToTrickleString(candidate.get()); + ice_candidate.mMLineIndex = candidate->sdp_mline_index(); + ice_candidate.mSdpMid = candidate->sdp_mid(); + observer->OnIceCandidate(ice_candidate); + } + } + mCachedIceCandidates.clear(); + if (mPeerConnection) + { + OnIceGatheringChange(mPeerConnection->ice_gathering_state()); + } + +} + +// +// SetLocalDescriptionObserverInterface implementation. +// +void LLWebRTCPeerConnectionImpl::OnSetLocalDescriptionComplete(webrtc::RTCError error) +{ +} + +// +// DataChannelObserver implementation +// + +void LLWebRTCPeerConnectionImpl::OnStateChange() +{ + if (!mDataChannel) + { + return; + } + RTC_LOG(LS_INFO) << __FUNCTION__ << " Data Channel State: " << webrtc::DataChannelInterface::DataStateString(mDataChannel->state()); + switch (mDataChannel->state()) + { + case webrtc::DataChannelInterface::kOpen: + RTC_LOG(LS_INFO) << __FUNCTION__ << " Data Channel State Open"; + for (auto &observer : mSignalingObserverList) + { + observer->OnDataChannelReady(this); + } + break; + case webrtc::DataChannelInterface::kConnecting: + RTC_LOG(LS_INFO) << __FUNCTION__ << " Data Channel State Connecting"; + break; + case webrtc::DataChannelInterface::kClosing: + RTC_LOG(LS_INFO) << __FUNCTION__ << " Data Channel State closing"; + break; + case webrtc::DataChannelInterface::kClosed: + RTC_LOG(LS_INFO) << __FUNCTION__ << " Data Channel State closed"; + break; + default: + break; + } +} + +void LLWebRTCPeerConnectionImpl::OnMessage(const webrtc::DataBuffer& buffer) +{ + std::string data((const char*)buffer.data.cdata(), buffer.size()); + for (auto &observer : mDataObserverList) + { + observer->OnDataReceived(data, buffer.binary); + } +} + +// +// LLWebRTCDataInterface +// + +void LLWebRTCPeerConnectionImpl::sendData(const std::string& data, bool binary) +{ + if (mDataChannel) + { + rtc::CopyOnWriteBuffer cowBuffer(data.data(), data.length()); + webrtc::DataBuffer buffer(cowBuffer, binary); + mWebRTCImpl->PostNetworkTask([this, buffer]() { + if (mDataChannel) + { + mDataChannel->Send(buffer); + } + }); + } +} + +void LLWebRTCPeerConnectionImpl::setDataObserver(LLWebRTCDataObserver* observer) +{ + mDataObserverList.emplace_back(observer); +} + +void LLWebRTCPeerConnectionImpl::unsetDataObserver(LLWebRTCDataObserver* observer) +{ + std::vector<LLWebRTCDataObserver *>::iterator it = + std::find(mDataObserverList.begin(), mDataObserverList.end(), observer); + if (it != mDataObserverList.end()) + { + mDataObserverList.erase(it); + } +} + +LLWebRTCImpl * gWebRTCImpl = nullptr; +LLWebRTCDeviceInterface * getDeviceInterface() +{ + return gWebRTCImpl; +} + +LLWebRTCPeerConnectionInterface* newPeerConnection() +{ + return gWebRTCImpl->newPeerConnection(); +} + +void freePeerConnection(LLWebRTCPeerConnectionInterface* peer_connection) +{ + gWebRTCImpl->freePeerConnection(peer_connection); +} + + +void init() +{ + gWebRTCImpl = new LLWebRTCImpl(); + gWebRTCImpl->init(); +} + +void terminate() +{ + if (gWebRTCImpl) + { + gWebRTCImpl->terminate(); + gWebRTCImpl = nullptr; + } +} + +} // namespace llwebrtc diff --git a/indra/llwebrtc/llwebrtc.h b/indra/llwebrtc/llwebrtc.h new file mode 100644 index 0000000000..f447ea990a --- /dev/null +++ b/indra/llwebrtc/llwebrtc.h @@ -0,0 +1,278 @@ +/** + * @file llwebrtc.h + * @brief WebRTC interface + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free tSoftware + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/* + * llwebrtc wraps the native webrtc c++ library in a dynamic library with a simlified interface + * so that the viewer can use it. This is done because native webrtc has a different + * overall threading model than the viewer. + * The native webrtc library is also compiled with clang, and has memory management + * functions that conflict namespace-wise with those in the viewer. + * + * Due to these differences, code from the viewer cannot be pulled in to this + * dynamic library, so it remains very simple. + */ + +#ifndef LLWEBRTC_H +#define LLWEBRTC_H + +#include <string> +#include <vector> + +#ifdef LL_MAKEDLL +#ifdef WEBRTC_WIN +#define LLSYMEXPORT __declspec(dllexport) +#elif WEBRTC_LINUX +#define LLSYMEXPORT __attribute__((visibility("default"))) +#else +#define LLSYMEXPORT /**/ +#endif +#else +#define LLSYMEXPORT /**/ +#endif // LL_MAKEDLL + +namespace llwebrtc +{ + +// LLWebRTCVoiceDevice is a simple representation of the +// components of a device, used to communicate this +// information to the viewer. + + +// A note on threading. +// Native WebRTC has it's own threading model. Some discussion +// can be found here (https://webrtc.github.io/webrtc-org/native-code/native-apis/) +// +// Note that all callbacks to observers will occurr on one of the WebRTC native threads +// (signaling, worker, etc.) Care should be taken to assure there are not +// bad interactions with the viewer threads. + +class LLWebRTCVoiceDevice +{ + public: + std::string mDisplayName; // friendly name for user interface purposes + std::string mID; // internal value for selection + + LLWebRTCVoiceDevice(const std::string &display_name, const std::string &id) : + mDisplayName(display_name), + mID(id) + { + if (mID.empty()) + { + mID = display_name; + } + }; +}; + +typedef std::vector<LLWebRTCVoiceDevice> LLWebRTCVoiceDeviceList; + + +// The LLWebRTCDeviceObserver should be implemented by the viewer +// webrtc module, which will receive notifications when devices +// change (are unplugged, etc.) +class LLWebRTCDevicesObserver +{ + public: + virtual void OnDevicesChanged(const LLWebRTCVoiceDeviceList &render_devices, + const LLWebRTCVoiceDeviceList &capture_devices) = 0; +}; + + +// The LLWebRTCDeviceInterface provides a way for the viewer +// to enumerate, set, and get notifications of changes +// for both capture (microphone) and render (speaker) +// devices. + +class LLWebRTCDeviceInterface +{ + public: + struct AudioConfig { + + bool mAGC { true }; + + bool mEchoCancellation { true }; + + // TODO: The various levels of noise suppression are configured + // on the APM which would require setting config on the APM. + // We should pipe the various values through + // later. + typedef enum { + NOISE_SUPPRESSION_LEVEL_NONE = 0, + NOISE_SUPPRESSION_LEVEL_LOW, + NOISE_SUPPRESSION_LEVEL_MODERATE, + NOISE_SUPPRESSION_LEVEL_HIGH, + NOISE_SUPPRESSION_LEVEL_VERY_HIGH + } ENoiseSuppressionLevel; + ENoiseSuppressionLevel mNoiseSuppressionLevel { NOISE_SUPPRESSION_LEVEL_VERY_HIGH }; + }; + + virtual void setAudioConfig(AudioConfig config) = 0; + + // instructs webrtc to refresh the device list. + virtual void refreshDevices() = 0; + + // set the capture and render devices using the unique identifier for the device + virtual void setCaptureDevice(const std::string& id) = 0; + virtual void setRenderDevice(const std::string& id) = 0; + + // Device observers for device change callbacks. + virtual void setDevicesObserver(LLWebRTCDevicesObserver *observer) = 0; + virtual void unsetDevicesObserver(LLWebRTCDevicesObserver *observer) = 0; + + // tuning and audio levels + virtual void setTuningMode(bool enable) = 0; + virtual float getTuningAudioLevel() = 0; // for use during tuning + virtual float getPeerConnectionAudioLevel() = 0; // for use when not tuning + virtual void setPeerConnectionGain(float gain) = 0; +}; + +// LLWebRTCAudioInterface provides the viewer with a way +// to set audio characteristics (mute, send and receive volume) +class LLWebRTCAudioInterface +{ + public: + virtual void setMute(bool mute) = 0; + virtual void setReceiveVolume(float volume) = 0; // volume between 0.0 and 1.0 + virtual void setSendVolume(float volume) = 0; // volume between 0.0 and 1.0 +}; + +// LLWebRTCDataObserver allows the viewer voice module to be notified when +// data is received over the data channel. +class LLWebRTCDataObserver +{ +public: + virtual void OnDataReceived(const std::string& data, bool binary) = 0; +}; + +// LLWebRTCDataInterface allows the viewer to send data over the data channel. +class LLWebRTCDataInterface +{ +public: + + virtual void sendData(const std::string& data, bool binary=false) = 0; + + virtual void setDataObserver(LLWebRTCDataObserver *observer) = 0; + virtual void unsetDataObserver(LLWebRTCDataObserver *observer) = 0; +}; + +// LLWebRTCIceCandidate is a basic structure containing +// information needed for ICE trickling. +struct LLWebRTCIceCandidate +{ + std::string mCandidate; + std::string mSdpMid; + int mMLineIndex; +}; + +// LLWebRTCSignalingObserver provides a way for the native +// webrtc library to notify the viewer voice module of +// various state changes. +class LLWebRTCSignalingObserver +{ + public: + + typedef enum e_ice_gathering_state { + ICE_GATHERING_NEW, + ICE_GATHERING_GATHERING, + ICE_GATHERING_COMPLETE + } EIceGatheringState; + + // Called when ICE gathering states have changed. + // This may be called at any time, as ICE gathering + // can be redone while a connection is up. + virtual void OnIceGatheringState(EIceGatheringState state) = 0; + + // Called when a new ice candidate is available. + virtual void OnIceCandidate(const LLWebRTCIceCandidate& candidate) = 0; + + // Called when an offer is available after a connection is requested. + virtual void OnOfferAvailable(const std::string& sdp) = 0; + + // Called when a connection enters a failure state and renegotiation is needed. + virtual void OnRenegotiationNeeded() = 0; + + // Called when a peer connection has shut down + virtual void OnPeerConnectionClosed() = 0; + + // Called when the audio channel has been established and audio + // can begin. + virtual void OnAudioEstablished(LLWebRTCAudioInterface *audio_interface) = 0; + + // Called when the data channel has been established and data + // transfer can begin. + virtual void OnDataChannelReady(LLWebRTCDataInterface *data_interface) = 0; +}; + +// LLWebRTCPeerConnectionInterface representsd a connection to a peer, +// in most cases a Secondlife WebRTC server. This interface +// allows for management of this peer connection. +class LLWebRTCPeerConnectionInterface +{ + public: + + struct InitOptions + { + // equivalent of PeerConnectionInterface::IceServer + struct IceServers { + + // Valid formats are described in RFC7064 and RFC7065. + // Urls should containe dns hostnames (not IP addresses) + // as the TLS certificate policy is 'secure.' + // and we do not currentply support TLS extensions. + std::vector<std::string> mUrls; + std::string mUserName; + std::string mPassword; + }; + + std::vector<IceServers> mServers; + }; + + virtual bool initializeConnection(const InitOptions& options) = 0; + virtual bool shutdownConnection() = 0; + + virtual void setSignalingObserver(LLWebRTCSignalingObserver* observer) = 0; + virtual void unsetSignalingObserver(LLWebRTCSignalingObserver* observer) = 0; + + virtual void AnswerAvailable(const std::string &sdp) = 0; +}; + +// The following define the dynamic linked library +// exports. + +// This library must be initialized before use. +LLSYMEXPORT void init(); + +// And should be terminated as part of shutdown. +LLSYMEXPORT void terminate(); + +// Return an interface for device management. +LLSYMEXPORT LLWebRTCDeviceInterface* getDeviceInterface(); + +// Allocate and free peer connections. +LLSYMEXPORT LLWebRTCPeerConnectionInterface* newPeerConnection(); +LLSYMEXPORT void freePeerConnection(LLWebRTCPeerConnectionInterface *connection); +} + +#endif // LLWEBRTC_H diff --git a/indra/llwebrtc/llwebrtc_impl.h b/indra/llwebrtc/llwebrtc_impl.h new file mode 100644 index 0000000000..6672f8ce90 --- /dev/null +++ b/indra/llwebrtc/llwebrtc_impl.h @@ -0,0 +1,382 @@ +/** + * @file llwebrtc_impl.h + * @brief WebRTC dynamic library implementation header + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLWEBRTC_IMPL_H +#define LLWEBRTC_IMPL_H + +#define LL_MAKEDLL +#if defined(_WIN32) || defined(_WIN64) +#define WEBRTC_WIN 1 +#elif defined(__APPLE__) +#define WEBRTC_MAC 1 +#define WEBRTC_POSIX 1 +#elif __linux__ +#define WEBRTC_LINUX 1 +#define WEBRTC_POSIX 1 +#endif + +#include "llwebrtc.h" +// WebRTC Includes +#ifdef WEBRTC_WIN +#pragma warning(disable : 4996) // ignore 'deprecated.' We don't use the functions marked + // deprecated in the webrtc headers, but msvc complains anyway. + // Clang doesn't, and that's generally what webrtc uses. +#pragma warning(disable : 4068) // ignore 'invalid pragma.' There are clang pragma's in + // the webrtc headers, which msvc doesn't recognize. +#endif // WEBRTC_WIN + +#include "api/scoped_refptr.h" +#include "rtc_base/ref_count.h" +#include "rtc_base/ref_counted_object.h" +#include "rtc_base/ssl_adapter.h" +#include "rtc_base/thread.h" +#include "api/peer_connection_interface.h" +#include "api/media_stream_interface.h" +#include "api/create_peerconnection_factory.h" +#include "modules/audio_device/include/audio_device.h" +#include "modules/audio_device/include/audio_device_data_observer.h" +#include "rtc_base/task_queue.h" +#include "api/task_queue/task_queue_factory.h" +#include "api/task_queue/default_task_queue_factory.h" +#include "modules/audio_device/include/audio_device_defines.h" + + +namespace llwebrtc +{ + +class LLWebRTCPeerConnectionImpl; + + +// Implements a class allowing capture of audio data +// to determine audio level of the microphone. +class LLAudioDeviceObserver : public webrtc::AudioDeviceDataObserver +{ + public: + LLAudioDeviceObserver(); + + // Retrieve the RMS audio loudness + float getMicrophoneEnergy(); + + // Data retrieved from the caputure device is + // passed in here for processing. + void OnCaptureData(const void *audio_samples, + const size_t num_samples, + const size_t bytes_per_sample, + const size_t num_channels, + const uint32_t samples_per_sec) override; + + // This is for data destined for the render device. + // not currently used. + void OnRenderData(const void *audio_samples, + const size_t num_samples, + const size_t bytes_per_sample, + const size_t num_channels, + const uint32_t samples_per_sec) override; + + protected: + static const int NUM_PACKETS_TO_FILTER = 30; // 300 ms of smoothing (30 frames) + float mSumVector[NUM_PACKETS_TO_FILTER]; + float mMicrophoneEnergy; +}; + +// Used to process/retrieve audio levels after +// all of the processing (AGC, AEC, etc.) for display in-world to the user. +class LLCustomProcessor : public webrtc::CustomProcessing +{ + public: + LLCustomProcessor(); + ~LLCustomProcessor() override {} + + // (Re-) Initializes the submodule. + void Initialize(int sample_rate_hz, int num_channels) override; + + // Analyzes the given capture or render signal. + void Process(webrtc::AudioBuffer *audio) override; + + // Returns a string representation of the module state. + std::string ToString() const override { return ""; } + + float getMicrophoneEnergy() { return mMicrophoneEnergy; } + + void setGain(float gain) { mGain = gain; } + + protected: + static const int NUM_PACKETS_TO_FILTER = 30; // 300 ms of smoothing + int mSampleRateHz; + int mNumChannels; + + float mSumVector[NUM_PACKETS_TO_FILTER]; + float mMicrophoneEnergy; + float mGain; +}; + + +// Primary singleton implementation for interfacing +// with the native webrtc library. +class LLWebRTCImpl : public LLWebRTCDeviceInterface, public webrtc::AudioDeviceSink +{ + public: + LLWebRTCImpl(); + ~LLWebRTCImpl() {} + + void init(); + void terminate(); + + // + // LLWebRTCDeviceInterface + // + + void setAudioConfig(LLWebRTCDeviceInterface::AudioConfig config = LLWebRTCDeviceInterface::AudioConfig()) override; + + void refreshDevices() override; + + void setDevicesObserver(LLWebRTCDevicesObserver *observer) override; + void unsetDevicesObserver(LLWebRTCDevicesObserver *observer) override; + + void setCaptureDevice(const std::string& id) override; + void setRenderDevice(const std::string& id) override; + + void setTuningMode(bool enable) override; + float getTuningAudioLevel() override; + float getPeerConnectionAudioLevel() override; + + void setPeerConnectionGain(float gain) override; + + // + // AudioDeviceSink + // + void OnDevicesUpdated() override; + + // + // Helpers + // + + // The following thread helpers allow the + // LLWebRTCPeerConnectionImpl class to post + // tasks to the native webrtc threads. + void PostWorkerTask(absl::AnyInvocable<void() &&> task, + const webrtc::Location& location = webrtc::Location::Current()) + { + mWorkerThread->PostTask(std::move(task), location); + } + + void PostSignalingTask(absl::AnyInvocable<void() &&> task, + const webrtc::Location& location = webrtc::Location::Current()) + { + mSignalingThread->PostTask(std::move(task), location); + } + + void PostNetworkTask(absl::AnyInvocable<void() &&> task, + const webrtc::Location& location = webrtc::Location::Current()) + { + mNetworkThread->PostTask(std::move(task), location); + } + + void WorkerBlockingCall(rtc::FunctionView<void()> functor, + const webrtc::Location& location = webrtc::Location::Current()) + { + mWorkerThread->BlockingCall(std::move(functor), location); + } + + void SignalingBlockingCall(rtc::FunctionView<void()> functor, + const webrtc::Location& location = webrtc::Location::Current()) + { + mSignalingThread->BlockingCall(std::move(functor), location); + } + + void NetworkBlockingCall(rtc::FunctionView<void()> functor, + const webrtc::Location& location = webrtc::Location::Current()) + { + mNetworkThread->BlockingCall(std::move(functor), location); + } + + // Allows the LLWebRTCPeerConnectionImpl class to retrieve the + // native webrtc PeerConnectionFactory. + rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> getPeerConnectionFactory() + { + return mPeerConnectionFactory; + } + + // create or destroy a peer connection. + LLWebRTCPeerConnectionInterface* newPeerConnection(); + void freePeerConnection(LLWebRTCPeerConnectionInterface* peer_connection); + + // enables/disables capture via the capture device + void setRecording(bool recording); + + protected: + // The native webrtc threads + std::unique_ptr<rtc::Thread> mNetworkThread; + std::unique_ptr<rtc::Thread> mWorkerThread; + std::unique_ptr<rtc::Thread> mSignalingThread; + + // The factory that allows creation of native webrtc PeerConnections. + rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> mPeerConnectionFactory; + + rtc::scoped_refptr<webrtc::AudioProcessing> mAudioProcessingModule; + + // more native webrtc stuff + std::unique_ptr<webrtc::TaskQueueFactory> mTaskQueueFactory; + + + // Devices + void updateDevices(); + rtc::scoped_refptr<webrtc::AudioDeviceModule> mTuningDeviceModule; + rtc::scoped_refptr<webrtc::AudioDeviceModule> mPeerDeviceModule; + std::vector<LLWebRTCDevicesObserver *> mVoiceDevicesObserverList; + + // accessors in native webrtc for devices aren't apparently implemented yet. + bool mTuningMode; + int32_t mRecordingDevice; + LLWebRTCVoiceDeviceList mRecordingDeviceList; + + int32_t mPlayoutDevice; + LLWebRTCVoiceDeviceList mPlayoutDeviceList; + + bool mMute; + + LLAudioDeviceObserver * mTuningAudioDeviceObserver; + LLCustomProcessor * mPeerCustomProcessor; + + // peer connections + std::vector<rtc::scoped_refptr<LLWebRTCPeerConnectionImpl>> mPeerConnections; +}; + + +// The implementation of a peer connection, which contains +// the various interfaces used by the viewer to interact with +// the webrtc connection. +class LLWebRTCPeerConnectionImpl : public LLWebRTCPeerConnectionInterface, + public LLWebRTCAudioInterface, + public LLWebRTCDataInterface, + public webrtc::PeerConnectionObserver, + public webrtc::CreateSessionDescriptionObserver, + public webrtc::SetRemoteDescriptionObserverInterface, + public webrtc::SetLocalDescriptionObserverInterface, + public webrtc::DataChannelObserver + +{ + public: + LLWebRTCPeerConnectionImpl(); + ~LLWebRTCPeerConnectionImpl(); + + void init(LLWebRTCImpl * webrtc_impl); + void terminate(); + + virtual void AddRef() const override = 0; + virtual rtc::RefCountReleaseStatus Release() const override = 0; + + // + // LLWebRTCPeerConnection + // + bool initializeConnection(const InitOptions& options) override; + bool shutdownConnection() override; + + void setSignalingObserver(LLWebRTCSignalingObserver *observer) override; + void unsetSignalingObserver(LLWebRTCSignalingObserver *observer) override; + void AnswerAvailable(const std::string &sdp) override; + + // + // LLWebRTCAudioInterface + // + void setMute(bool mute) override; + void setReceiveVolume(float volume) override; // volume between 0.0 and 1.0 + void setSendVolume(float volume) override; // volume between 0.0 and 1.0 + + // + // LLWebRTCDataInterface + // + void sendData(const std::string& data, bool binary=false) override; + void setDataObserver(LLWebRTCDataObserver *observer) override; + void unsetDataObserver(LLWebRTCDataObserver *observer) override; + + // + // PeerConnectionObserver implementation. + // + + void OnSignalingChange(webrtc::PeerConnectionInterface::SignalingState new_state) override {} + void OnAddTrack(rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver, + const std::vector<rtc::scoped_refptr<webrtc::MediaStreamInterface>> &streams) override; + void OnRemoveTrack(rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver) override; + void OnDataChannel(rtc::scoped_refptr<webrtc::DataChannelInterface> channel) override; + void OnRenegotiationNeeded() override {} + void OnIceConnectionChange(webrtc::PeerConnectionInterface::IceConnectionState new_state) override {}; + void OnIceGatheringChange(webrtc::PeerConnectionInterface::IceGatheringState new_state) override; + void OnIceCandidate(const webrtc::IceCandidateInterface *candidate) override; + void OnIceConnectionReceivingChange(bool receiving) override {} + void OnConnectionChange(webrtc::PeerConnectionInterface::PeerConnectionState new_state) override; + + // + // CreateSessionDescriptionObserver implementation. + // + void OnSuccess(webrtc::SessionDescriptionInterface *desc) override; + void OnFailure(webrtc::RTCError error) override; + + // + // SetRemoteDescriptionObserverInterface implementation. + // + void OnSetRemoteDescriptionComplete(webrtc::RTCError error) override; + + // + // SetLocalDescriptionObserverInterface implementation. + // + void OnSetLocalDescriptionComplete(webrtc::RTCError error) override; + + // + // DataChannelObserver implementation. + // + void OnStateChange() override; + void OnMessage(const webrtc::DataBuffer& buffer) override; + + // Helpers + void resetMute(); + void enableSenderTracks(bool enable); + void enableReceiverTracks(bool enable); + + protected: + + LLWebRTCImpl * mWebRTCImpl; + + rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> mPeerConnectionFactory; + + bool mMute; + + // signaling + std::vector<LLWebRTCSignalingObserver *> mSignalingObserverList; + std::vector<std::unique_ptr<webrtc::IceCandidateInterface>> mCachedIceCandidates; + bool mAnswerReceived; + + rtc::scoped_refptr<webrtc::PeerConnectionInterface> mPeerConnection; + rtc::scoped_refptr<webrtc::MediaStreamInterface> mLocalStream; + + // data + std::vector<LLWebRTCDataObserver *> mDataObserverList; + rtc::scoped_refptr<webrtc::DataChannelInterface> mDataChannel; +}; + +} + +#endif // LLWEBRTC_IMPL_H diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 8070cd0796..4771dbfa1c 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -718,6 +718,7 @@ set(viewer_SOURCE_FILES llvoiceclient.cpp llvoicevisualizer.cpp llvoicevivox.cpp + llvoicewebrtc.cpp llvoinventorylistener.cpp llvopartgroup.cpp llvosky.cpp @@ -1373,6 +1374,7 @@ set(viewer_HEADER_FILES llvoiceclient.h llvoicevisualizer.h llvoicevivox.h + llvoicewebrtc.h llvoinventorylistener.h llvopartgroup.h llvosky.h @@ -1479,6 +1481,7 @@ if (USESYSTEMLIBS AND NOT DARWIN) endif (USESYSTEMLIBS AND NOT DARWIN) if (WINDOWS) + list(APPEND viewer_SOURCE_FILES llappviewerwin32.cpp llwindebug.cpp @@ -1758,6 +1761,7 @@ if (WINDOWS) ${SHARED_LIB_STAGING_DIR}/openjp2.dll ${SHARED_LIB_STAGING_DIR}/libhunspell.dll ${SHARED_LIB_STAGING_DIR}/uriparser.dll + ${SHARED_LIB_STAGING_DIR}/llwebrtc.dll #${SHARED_LIB_STAGING_DIR}/${LL_INTDIR}/SLVoice.exe #${SHARED_LIB_STAGING_DIR}/${LL_INTDIR}/libsndfile-1.dll #${SHARED_LIB_STAGING_DIR}/${LL_INTDIR}/vivoxoal.dll @@ -1824,13 +1828,14 @@ if (WINDOWS) DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/viewer_manifest.py stage_third_party_libs + llwebrtc ${COPY_INPUT_DEPENDENCIES} COMMENT "Performing viewer_manifest copy" ) add_custom_target(copy_w_viewer_manifest ALL DEPENDS ${CMAKE_CFG_INTDIR}/copy_touched.bat) - add_dependencies(${VIEWER_BINARY_NAME} stage_third_party_libs llcommon copy_w_viewer_manifest) + add_dependencies(${VIEWER_BINARY_NAME} stage_third_party_libs llcommon llwebrtc copy_w_viewer_manifest) if (EXISTS ${CMAKE_SOURCE_DIR}/copy_win_scripts) add_dependencies(${VIEWER_BINARY_NAME} copy_win_scripts) @@ -1954,6 +1959,7 @@ target_link_libraries(${VIEWER_BINARY_NAME} llcorehttp llcommon llmeshoptimizer + llwebrtc ll::ndof lllogin llprimitive diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt index 2fe040f424..5978ab1324 100644 --- a/indra/newview/VIEWER_VERSION.txt +++ b/indra/newview/VIEWER_VERSION.txt @@ -1 +1 @@ -7.1.8 +7.1.9 diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index a07396b5b1..2e305cc18d 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -379,7 +379,7 @@ <key>Value</key> <integer>0</integer> </map> - <key>AutoAcceptNewInventory</key> + <key>AutoAcceptNewInventory</key> <map> <key>Comment</key> <string>Automatically accept new notecards/textures/landmarks</string> @@ -13043,7 +13043,7 @@ <key>Type</key> <string>Boolean</string> <key>Value</key> - <integer>1</integer> + <integer>0</integer> </map> <key>AutoDisengageMic</key> <map> @@ -13221,6 +13221,39 @@ <key>Value</key> <integer>44125</integer> </map> + <key>VoiceEchoCancellation</key> + <map> + <key>Comment</key> + <string>Voice Echo Cancellation</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>1</integer> + </map> + <key>VoiceAutomaticGainControl</key> + <map> + <key>Comment</key> + <string>Voice Automatic Gain Control</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>1</integer> + </map> + <key>VoiceNoiseSuppressionLevel</key> + <map> + <key>Comment</key> + <string>Voice Noise Suppression Level</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>U32</string> + <key>Value</key> + <integer>4</integer> + </map> <key>WarningsAsChat</key> <map> <key>Comment</key> @@ -13235,13 +13268,13 @@ <key>VoiceServerType</key> <map> <key>Comment</key> - <string>The type of voice server to connect to.</string> + <string>The type of voice server to use for group, conference, and p2p calls.</string> <key>Persist</key> <integer>0</integer> <key>Type</key> <string>String</string> <key>Value</key> - <string>vivox</string> + <string/> </map> <key>WLSkyDetail</key> <map> diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index 6db7088812..68136c8396 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -310,7 +310,7 @@ bool LLAgent::isActionAllowed(const LLSD& sdname) } else { - allow_agent_voice = channel->isActive() && channel->callStarted(); + allow_agent_voice = channel->isActive(); } } @@ -4081,10 +4081,6 @@ bool LLAgent::teleportCore(bool is_local) } make_ui_sound("UISndTeleportOut"); - // MBW -- Let the voice client know a teleport has begun so it can leave the existing channel. - // This was breaking the case of teleporting within a single sim. Backing it out for now. -// LLVoiceClient::getInstance()->leaveChannel(); - return true; } diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 2d65c725b5..ab87ff6d2c 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -3409,14 +3409,14 @@ LLSD LLAppViewer::getViewerInfo() const LLVoiceVersionInfo version = LLVoiceClient::getInstance()->getVersion(); const std::string build_version = version.mBuildVersion; std::ostringstream version_string; - if (std::equal(build_version.begin(), build_version.begin() + version.serverVersion.size(), + if (std::equal(version.mBuildVersion.begin(), version.mBuildVersion.begin() + version.serverVersion.size(), version.serverVersion.begin())) { // Normal case: Show type and build version. - version_string << version.serverType << " " << build_version << std::endl; + version_string << version.voiceServerType << " " << version.mBuildVersion << std::endl; } else { // Mismatch: Show both versions. - version_string << version.serverVersion << "/" << build_version << std::endl; + version_string << version.voiceServerType << " " << version.serverVersion << "/" << version.mBuildVersion << std::endl; } info["VOICE_VERSION"] = version_string.str(); } @@ -5200,7 +5200,7 @@ void LLAppViewer::sendLogoutRequest() if(LLVoiceClient::instanceExists()) { - LLVoiceClient::getInstance()->leaveChannel(); + LLVoiceClient::getInstance()->setVoiceEnabled(false); } } } diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index 27e1d32a38..4f18e722f2 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -43,6 +43,7 @@ #ifndef LL_LLAPPVIEWER_H #define LL_LLAPPVIEWER_H +#include "llapp.h" #include "llallocator.h" #include "llapr.h" #include "llcontrol.h" diff --git a/indra/newview/llavataractions.cpp b/indra/newview/llavataractions.cpp index f9c5f42de7..2857235dd2 100644 --- a/indra/newview/llavataractions.cpp +++ b/indra/newview/llavataractions.cpp @@ -241,7 +241,7 @@ static void on_avatar_name_cache_start_call(const LLUUID& agent_id, const LLAvatarName& av_name) { std::string name = av_name.getDisplayName(); - LLUUID session_id = gIMMgr->addSession(name, IM_NOTHING_SPECIAL, agent_id, true); + LLUUID session_id = gIMMgr->addSession(name, IM_NOTHING_SPECIAL, agent_id, LLSD()); if (session_id != LLUUID::null) { gIMMgr->startCall(session_id); @@ -277,8 +277,7 @@ void LLAvatarActions::startAdhocCall(const uuid_vec_t& ids, const LLUUID& floate // create the new ad hoc voice session const std::string title = LLTrans::getString("conference-title"); - LLUUID session_id = gIMMgr->addSession(title, IM_SESSION_CONFERENCE_START, - ids[0], id_array, true, floater_id); + LLUUID session_id = gIMMgr->addSession(title, IM_SESSION_CONFERENCE_START, ids[0], id_array, LLSD(), floater_id); if (session_id == LLUUID::null) { return; @@ -322,7 +321,7 @@ void LLAvatarActions::startConference(const uuid_vec_t& ids, const LLUUID& float id_array.push_back(*it); } const std::string title = LLTrans::getString("conference-title"); - LLUUID session_id = gIMMgr->addSession(title, IM_SESSION_CONFERENCE_START, ids[0], id_array, false, floater_id); + LLUUID session_id = gIMMgr->addSession(title, IM_SESSION_CONFERENCE_START, ids[0], id_array, LLSD(), floater_id); if (session_id == LLUUID::null) { diff --git a/indra/newview/llconversationmodel.cpp b/indra/newview/llconversationmodel.cpp index 4335168417..4cd85ac756 100644 --- a/indra/newview/llconversationmodel.cpp +++ b/indra/newview/llconversationmodel.cpp @@ -357,22 +357,20 @@ void LLConversationItemSession::clearParticipants() void LLConversationItemSession::clearAndDeparentModels() { - std::for_each(mChildren.begin(), mChildren.end(), - [](LLFolderViewModelItem* c) + for (LLFolderViewModelItem* child : mChildren) + { + if (child->getNumRefs() == 0) { - if (c->getNumRefs() == 0) - { - // LLConversationItemParticipant can be created but not assigned to any view, - // it was waiting for an "add_participant" event to be processed - delete c; - } - else - { - // Model is still assigned to some view/widget - c->setParent(NULL); - } + // LLConversationItemParticipant can be created but not assigned to any view, + // it was waiting for an "add_participant" event to be processed + delete child; } - ); + else + { + // Model is still assigned to some view/widget + child->setParent(NULL); + } + } mChildren.clear(); } diff --git a/indra/newview/llconversationview.cpp b/indra/newview/llconversationview.cpp index 1abebc996b..117dab8d58 100644 --- a/indra/newview/llconversationview.cpp +++ b/indra/newview/llconversationview.cpp @@ -57,7 +57,7 @@ public: : conversation(conv) {} - virtual void onChange(EStatusType status, const std::string &channelURI, bool proximal) + virtual void onChange(EStatusType status, const LLSD& channelInfo, bool proximal) { conversation->showVoiceIndicator(conversation && status != STATUS_JOINING diff --git a/indra/newview/llfloaterimsession.cpp b/indra/newview/llfloaterimsession.cpp index a2d1cb7036..b782515c14 100644 --- a/indra/newview/llfloaterimsession.cpp +++ b/indra/newview/llfloaterimsession.cpp @@ -94,6 +94,7 @@ LLFloaterIMSession::LLFloaterIMSession(const LLUUID& session_id) mEnableCallbackRegistrar.add("Avatar.EnableGearItem", boost::bind(&LLFloaterIMSession::enableGearMenuItem, this, _2)); mCommitCallbackRegistrar.add("Avatar.GearDoToSelected", boost::bind(&LLFloaterIMSession::GearDoToSelected, this, _2)); mEnableCallbackRegistrar.add("Avatar.CheckGearItem", boost::bind(&LLFloaterIMSession::checkGearMenuItem, this, _2)); + mVoiceChannelChanged = LLVoiceChannel::setCurrentVoiceChannelChangedCallback(boost::bind(&LLFloaterIMSession::onVoiceChannelChanged, this, _1)); setDocked(true); } @@ -292,6 +293,8 @@ LLFloaterIMSession::~LLFloaterIMSession() } LLTransientFloaterMgr::getInstance()->removeControlView(LLTransientFloaterMgr::IM, this); + + mVoiceChannelChanged.disconnect(); } @@ -521,6 +524,14 @@ void LLFloaterIMSession::sendParticipantsAddedNotification(const uuid_vec_t& uui sendMsg(getString(uuids.size() > 1 ? "multiple_participants_added" : "participant_added", args)); } +void LLFloaterIMSession::onVoiceChannelChanged(const LLUUID &session_id) +{ + if (session_id == mSessionID) + { + boundVoiceChannel(); + } +} + void LLFloaterIMSession::boundVoiceChannel() { LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionID); @@ -552,7 +563,7 @@ void LLFloaterIMSession::onCallButtonClicked() } } -void LLFloaterIMSession::onChange(EStatusType status, const std::string &channelURI, bool proximal) +void LLFloaterIMSession::onChange(EStatusType status, const LLSD& channelInfo, bool proximal) { if(status != STATUS_JOINING && status != STATUS_LEFT_CHANNEL) { diff --git a/indra/newview/llfloaterimsession.h b/indra/newview/llfloaterimsession.h index 7be68ccfc7..5180b0cee1 100644 --- a/indra/newview/llfloaterimsession.h +++ b/indra/newview/llfloaterimsession.h @@ -114,8 +114,7 @@ public: // Implements LLVoiceClientStatusObserver::onChange() to enable the call // button when voice is available - void onChange(EStatusType status, const std::string &channelURI, - bool proximal); + void onChange(EStatusType status, const LLSD& channelInfo, bool proximal); virtual LLTransientFloaterMgr::ETransientGroup getGroup() { return LLTransientFloaterMgr::IM; } virtual void onVoiceChannelStateChanged( @@ -162,6 +161,8 @@ private: void onCallButtonClicked(); + void onVoiceChannelChanged(const LLUUID &session_id); + void boundVoiceChannel(); // Add the "User is typing..." indicator. @@ -196,6 +197,9 @@ private: uuid_vec_t mInvitedParticipants; uuid_vec_t mPendingParticipants; + // notification when the voice channel is swapped out from beneath us. + boost::signals2::connection mVoiceChannelChanged; + // connection to voice channel state change signal boost::signals2::connection mVoiceChannelStateChangeConnection; diff --git a/indra/newview/llfloaterimsessiontab.cpp b/indra/newview/llfloaterimsessiontab.cpp index dc64d09f9f..0ed84c381f 100644 --- a/indra/newview/llfloaterimsessiontab.cpp +++ b/indra/newview/llfloaterimsessiontab.cpp @@ -102,6 +102,26 @@ LLFloaterIMSessionTab::LLFloaterIMSessionTab(const LLSD& session_id) LLFloaterIMSessionTab::~LLFloaterIMSessionTab() { delete mRefreshTimer; + + LLFloaterIMContainer* im_container = LLFloaterIMContainer::findInstance(); + if (im_container) + { + LLParticipantList* session = dynamic_cast<LLParticipantList*>(im_container->getSessionModel(mSessionID)); + if (session) + { + for (const conversations_widgets_map::value_type& widget_pair : mConversationsWidgets) + { + LLFolderViewItem* widget = widget_pair.second; + LLFolderViewModelItem* item_vmi = widget->getViewModelItem(); + if (item_vmi && item_vmi->getNumRefs() == 1) + { + // This is the last pointer, remove participant from session + // before participant gets deleted on destroyView. + session->removeChild(item_vmi); + } + } + } + } } // static @@ -663,6 +683,27 @@ void LLFloaterIMSessionTab::removeConversationViewParticipant(const LLUUID& part LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,participant_id); if (widget) { + LLFolderViewModelItem* item_vmi = widget->getViewModelItem(); + if (item_vmi && item_vmi->getNumRefs() == 1) + { + // This is the last pointer, remove participant from session + // before participant gets deleted on destroyView. + // + // Floater (widget) and participant's view can simultaneously + // co-own the model, in which case view is responsible for + // the deletion and floater is free to clear and recreate + // the list, yet there are cases where only widget owns + // the pointer so it should do the cleanup. + // See "add_participant". + // + // Todo: If it keeps causing issues turn participants + // into LLPointers in the session + LLParticipantList* session = getParticipantList(); + if (session) + { + session->removeChild(item_vmi); + } + } widget->destroyView(); } mConversationsWidgets.erase(participant_id); diff --git a/indra/newview/llgroupactions.cpp b/indra/newview/llgroupactions.cpp index 24ae90e3ae..58cb1a614d 100644 --- a/indra/newview/llgroupactions.cpp +++ b/indra/newview/llgroupactions.cpp @@ -248,7 +248,7 @@ void LLGroupActions::startCall(const LLUUID& group_id) return; } - LLUUID session_id = gIMMgr->addSession(gdata.mName, IM_SESSION_GROUP_START, group_id, true); + LLUUID session_id = gIMMgr->addSession(gdata.mName, IM_SESSION_GROUP_START, group_id, LLSD()); if (session_id == LLUUID::null) { LL_WARNS() << "Error adding session" << LL_ENDL; @@ -370,8 +370,8 @@ void LLGroupActions::processLeaveGroupDataResponse(const LLUUID group_id) if (gdatap->mMembershipFee > 0) { args["COST"] = gdatap->mMembershipFee; - LLNotificationsUtil::add("GroupLeaveConfirmMember", args, payload, onLeaveGroup); - } + LLNotificationsUtil::add("GroupLeaveConfirmMember", args, payload, onLeaveGroup); +} else { LLNotificationsUtil::add("GroupLeaveConfirmMemberNoFee", args, payload, onLeaveGroup); diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index 1cc76d7268..a29b883f6b 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -89,8 +89,24 @@ const S32 XL8_PADDING = 3; // XL8_START_TAG.size() + XL8_END_TAG.size() /** Timeout of outgoing session initialization (in seconds) */ const static U32 SESSION_INITIALIZATION_TIMEOUT = 30; -void startConfrenceCoro(std::string url, LLUUID tempSessionId, LLUUID creatorId, LLUUID otherParticipantId, LLSD agents); -void chatterBoxInvitationCoro(std::string url, LLUUID sessionId, LLIMMgr::EInvitationType invitationType); +// This enum corresponds to the sim's and adds P2P_CHAT_SESSION, +// as webrtc uses the multiagent chat mechanism for p2p calls, +// instead of relying on vivox calling. +// Don't change this without consulting a server developer. +enum EMultiAgentChatSessionType +{ + GROUP_CHAT_SESSION = 0, + CONFERENCE_SESSION = 1, + P2P_CHAT_SESSION = 2, + SESSION_TYPE_COUNT +}; + + +void startConferenceCoro(std::string url, LLUUID tempSessionId, LLUUID creatorId, LLUUID otherParticipantId, LLSD agents); + +void startP2PVoiceCoro(std::string url, LLUUID tempSessionId, LLUUID creatorId, LLUUID otherParticipantId); + +void chatterBoxInvitationCoro(std::string url, LLUUID sessionId, LLIMMgr::EInvitationType invitationType, const LLSD& voiceChannelInfo); void chatterBoxHistoryCoro(std::string url, LLUUID sessionId, std::string from, std::string message, U32 timestamp); void start_deprecated_conference_chat(const LLUUID& temp_session_id, const LLUUID& creator_id, const LLUUID& other_participant_id, const LLSD& agents_to_invite); @@ -139,7 +155,7 @@ void process_dnd_im(const LLSD& notification) name, IM_NOTHING_SPECIAL, fromID, - false, + LLSD(), false); //will need slight refactor to retrieve whether offline message or not (assume online for now) } @@ -398,7 +414,7 @@ void on_new_message(const LLSD& msg) notify_of_message(msg, false); } -void startConfrenceCoro(std::string url, +void startConferenceCoro(std::string url, LLUUID tempSessionId, LLUUID creatorId, LLUUID otherParticipantId, LLSD agents) { LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); @@ -410,6 +426,16 @@ void startConfrenceCoro(std::string url, postData["method"] = "start conference"; postData["session-id"] = tempSessionId; postData["params"] = agents; + LLSD altParams; + std::string voice_server_type = gSavedSettings.getString("VoiceServerType"); + if (voice_server_type.empty()) + { + // default to the server type associated with the region we're on. + LLVoiceVersionInfo versionInfo = LLVoiceClient::getInstance()->getVersion(); + voice_server_type = versionInfo.internalVoiceServerType; + } + altParams["voice_server_type"] = voice_server_type; + postData["alt_params"] = altParams; LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData); @@ -439,7 +465,46 @@ void startConfrenceCoro(std::string url, } } -void chatterBoxInvitationCoro(std::string url, LLUUID sessionId, LLIMMgr::EInvitationType invitationType) +void startP2PVoiceCoro(std::string url, LLUUID sessionID, LLUUID creatorId, LLUUID otherParticipantId) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("StartP2PVoiceCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + LLSD postData; + postData["method"] = "start p2p voice"; + postData["session-id"] = sessionID; + postData["params"] = otherParticipantId; + LLSD altParams; + std::string voice_server_type = gSavedSettings.getString("VoiceServerType"); + if (voice_server_type.empty()) + { + // default to the server type associated with the region we're on. + LLVoiceVersionInfo versionInfo = LLVoiceClient::getInstance()->getVersion(); + voice_server_type = versionInfo.internalVoiceServerType; + } + altParams["voice_server_type"] = voice_server_type; + postData["alt_params"] = altParams; + + LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + LL_WARNS("LLIMModel") << "Failed to start p2p session:" << postData << "->" << result << LL_ENDL; + // try an "old school" way. + // *TODO: What about other error status codes? 4xx 5xx? + if (status == LLCore::HttpStatus(HTTP_BAD_REQUEST)) + { + static const std::string error_string("session_does_not_exist_error"); + gIMMgr->showSessionStartError(error_string, sessionID); + } + } +} + +void chatterBoxInvitationCoro(std::string url, LLUUID sessionId, LLIMMgr::EInvitationType invitationType, const LLSD& voiceChannelInfo) { LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t @@ -505,7 +570,7 @@ void chatterBoxInvitationCoro(std::string url, LLUUID sessionId, LLIMMgr::EInvit if (LLIMMgr::INVITATION_TYPE_VOICE == invitationType) { - gIMMgr->startCall(sessionId, LLVoiceChannel::INCOMING_CALL); + gIMMgr->startCall(sessionId, LLVoiceChannel::INCOMING_CALL, voiceChannelInfo); } if ((invitationType == LLIMMgr::INVITATION_TYPE_VOICE @@ -651,7 +716,13 @@ LLIMModel::LLIMModel() LLCallDialogManager::instance(); } -LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID& other_participant_id, const uuid_vec_t& ids, bool voice, bool has_offline_msg) +LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, + const std::string& name, + const EInstantMessage& type, + const LLUUID& other_participant_id, + const LLSD& voice_channel_info, + const uuid_vec_t& ids, + bool has_offline_msg) : mSessionID(session_id), mName(name), mType(type), @@ -661,43 +732,30 @@ LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, const std::string& mOtherParticipantID(other_participant_id), mInitialTargetIDs(ids), mVoiceChannel(NULL), + mP2PAsAdhocCall(false), mSpeakers(NULL), mSessionInitialized(false), mCallBackEnabled(true), mTextIMPossible(true), mStartCallOnInitialize(false), - mStartedAsIMCall(voice), + mStartedAsIMCall(!voice_channel_info.isUndefined()), mIsDNDsend(false), mAvatarNameCacheConnection() { // set P2P type by default - mSessionType = P2P_SESSION; + mSessionType = P2P_SESSION; if (IM_NOTHING_SPECIAL == mType || IM_SESSION_P2P_INVITE == mType) { - mVoiceChannel = new LLVoiceChannelP2P(session_id, name, other_participant_id); + mP2PAsAdhocCall = (LLVoiceClient::getInstance()->getOutgoingCallInterface(voice_channel_info) == NULL); } else { - mVoiceChannel = new LLVoiceChannelGroup(session_id, name); - // determine whether it is group or conference session - if (gAgent.isInGroup(mSessionID)) - { - mSessionType = GROUP_SESSION; - } - else - { - mSessionType = ADHOC_SESSION; - } - } - - if(mVoiceChannel) - { - mVoiceChannelStateChangeConnection = mVoiceChannel->setStateChangedCallback(boost::bind(&LLIMSession::onVoiceChannelStateChanged, this, _1, _2, _3)); + mSessionType = gAgent.isInGroup(mSessionID) ? GROUP_SESSION : ADHOC_SESSION; } - mSpeakers = new LLIMSpeakerMgr(mVoiceChannel); + initVoiceChannel(voice_channel_info); // All participants will be added to the list of people we've recently interacted with. @@ -707,8 +765,7 @@ LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, const std::string& //we need to wait for session initialization for outgoing ad-hoc and group chat session //correct session id for initiated ad-hoc chat will be received from the server - if (!LLIMModel::getInstance()->sendStartSession(mSessionID, mOtherParticipantID, - mInitialTargetIDs, mType)) + if (!LLIMModel::getInstance()->sendStartSession(mSessionID, mOtherParticipantID, mInitialTargetIDs, mType, mP2PAsAdhocCall)) { //we don't need to wait for any responses //so we're already initialized @@ -738,6 +795,73 @@ LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, const std::string& } } +void LLIMModel::LLIMSession::initVoiceChannel(const LLSD& voiceChannelInfo) +{ + + if (mVoiceChannel) + { + if (mVoiceChannel->isThisVoiceChannel(voiceChannelInfo)) + { + return; + } + mVoiceChannelStateChangeConnection.disconnect(); + + mVoiceChannel->deactivate(); + + delete mVoiceChannel; + mVoiceChannel = NULL; + } + mP2PAsAdhocCall = false; + if (IM_NOTHING_SPECIAL == mType || IM_SESSION_P2P_INVITE == mType) + { + LLVoiceP2POutgoingCallInterface *outgoingInterface = LLVoiceClient::getInstance()->getOutgoingCallInterface(voiceChannelInfo); + + if (outgoingInterface) + { + // only use LLVoiceChannelP2P if the provider can handle the special P2P interface, + // which uses the voice server to relay calls and invites. Otherwise, + // we use the group voice provider. + mVoiceChannel = new LLVoiceChannelP2P(mSessionID, mName, mOtherParticipantID, outgoingInterface); + } + else + { + mP2PAsAdhocCall = true; + mVoiceChannel = new LLVoiceChannelGroup(mSessionID, mName, true); + } + } + else + { + // determine whether it is group or conference session + if (mSessionType == GROUP_SESSION) + { + mSessionType = GROUP_SESSION; + mVoiceChannel = new LLVoiceChannelGroup(mSessionID, mName, false); + } + else if (mSessionType == ADHOC_SESSION) + { + mSessionType = ADHOC_SESSION; + mVoiceChannel = new LLVoiceChannelGroup(mSessionID, mName, false); + } + else + { + LL_WARNS("Voice") << "Invalid Session Type when initializing voice channel: " << mSessionType << LL_ENDL; + return; + } + } + + mVoiceChannelStateChangeConnection = + mVoiceChannel->setStateChangedCallback(boost::bind(&LLIMSession::onVoiceChannelStateChanged, this, _1, _2, _3)); + + if (!mSpeakers) + { + mSpeakers = new LLIMSpeakerMgr(mVoiceChannel); + } + else + { + mSpeakers->setVoiceChannel(mVoiceChannel); + } +} + void LLIMModel::LLIMSession::onAdHocNameCache(const LLAvatarName& av_name) { mAvatarNameCacheConnection.disconnect(); @@ -841,7 +965,7 @@ void LLIMModel::LLIMSession::onVoiceChannelStateChanged(const LLVoiceChannel::ES break; } // Update speakers list when connected - if (LLVoiceChannel::STATE_CONNECTED == new_state) + if (mSpeakers && LLVoiceChannel::STATE_CONNECTED == new_state) { mSpeakers->update(true); } @@ -857,22 +981,6 @@ LLIMModel::LLIMSession::~LLIMSession() delete mSpeakers; mSpeakers = NULL; - // End the text IM session if necessary - if(LLVoiceClient::getInstance() && mOtherParticipantID.notNull()) - { - switch(mType) - { - case IM_NOTHING_SPECIAL: - case IM_SESSION_P2P_INVITE: - LLVoiceClient::getInstance()->endUserIMSession(mOtherParticipantID); - break; - - default: - // Appease the linux compiler - break; - } - } - mVoiceChannelStateChangeConnection.disconnect(); // HAVE to do this here -- if it happens in the LLVoiceChannel destructor it will call the wrong version (since the object's partially deconstructed at that point). @@ -889,7 +997,10 @@ void LLIMModel::LLIMSession::sessionInitReplyReceived(const LLUUID& new_session_ if (new_session_id != mSessionID) { mSessionID = new_session_id; - mVoiceChannel->updateSessionID(new_session_id); + if (mVoiceChannel) + { + mVoiceChannel->updateSessionID(new_session_id); + } } } @@ -1447,7 +1558,7 @@ void LLIMModel::testMessages() //session name should not be empty bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, - const LLUUID& other_participant_id, const uuid_vec_t& ids, bool voice, bool has_offline_msg) + const LLUUID& other_participant_id, const uuid_vec_t& ids, const LLSD& voiceChannelInfo, bool has_offline_msg) { if (name.empty()) { @@ -1461,7 +1572,7 @@ bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, co return false; } - LLIMSession* session = new LLIMSession(session_id, name, type, other_participant_id, ids, voice, has_offline_msg); + LLIMSession *session = new LLIMSession(session_id, name, type, other_participant_id, voiceChannelInfo, ids, has_offline_msg); mId2SessionMap[session_id] = session; // When notifying observer, name of session is used instead of "name", because they may not be the @@ -1473,11 +1584,11 @@ bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, co } -bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID& other_participant_id, bool voice, bool has_offline_msg) +bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID& other_participant_id, const LLSD& voiceChannelInfo, bool has_offline_msg) { uuid_vec_t ids; ids.push_back(other_participant_id); - return newSession(session_id, name, type, other_participant_id, ids, voice, has_offline_msg); + return newSession(session_id, name, type, other_participant_id, ids, voiceChannelInfo, has_offline_msg); } bool LLIMModel::clearSession(const LLUUID& session_id) @@ -1725,7 +1836,7 @@ EInstantMessage LLIMModel::getType(const LLUUID& session_id) const return session->mType; } -LLVoiceChannel* LLIMModel::getVoiceChannel( const LLUUID& session_id ) const +LLVoiceChannel* LLIMModel::getVoiceChannel( const LLUUID& session_id) const { LLIMSession* session = findIMSession(session_id); if (!session) @@ -2026,7 +2137,8 @@ bool LLIMModel::sendStartSession( const LLUUID& temp_session_id, const LLUUID& other_participant_id, const uuid_vec_t& ids, - EInstantMessage dialog) + EInstantMessage dialog, + bool p2p_as_adhoc_call) { if ( dialog == IM_SESSION_GROUP_START ) { @@ -2042,7 +2154,7 @@ bool LLIMModel::sendStartSession( return true; } - else if ( dialog == IM_SESSION_CONFERENCE_START ) + else if (dialog == IM_SESSION_CONFERENCE_START ) { LLSD agents; for (int i = 0; i < (S32) ids.size(); i++) @@ -2057,8 +2169,8 @@ bool LLIMModel::sendStartSession( std::string url = region->getCapability( "ChatSessionRequest"); - LLCoros::instance().launch("startConfrenceCoro", - boost::bind(&startConfrenceCoro, url, + LLCoros::instance().launch("startConferenceCoro", + boost::bind(&startConferenceCoro, url, temp_session_id, gAgent.getID(), other_participant_id, agents)); } else @@ -2073,7 +2185,16 @@ bool LLIMModel::sendStartSession( //we also need to wait for reply from the server in case of ad-hoc chat (we'll get new session id) return true; } - + else if (p2p_as_adhoc_call && ((dialog == IM_SESSION_P2P_INVITE) || (dialog == IM_NOTHING_SPECIAL))) + { + LLViewerRegion *region = gAgent.getRegion(); + if (region) + { + std::string url = region->getCapability("ChatSessionRequest"); + LLCoros::instance().launch("startP2PVoiceCoro", boost::bind(&startP2PVoiceCoro, url, temp_session_id, gAgent.getID(), other_participant_id)); + } + return true; + } return false; } @@ -2317,6 +2438,12 @@ void LLCallDialogManager::onVoiceChannelStateChangedInt(const LLVoiceChannel::ES } break; + case LLVoiceChannel::STATE_NO_CHANNEL_INFO : + // This will happen in p2p calls using the adhoc + // infrastructure, which marks the channel as no channel info + // after the call is closed, which forces a dialogue. + return; + case LLVoiceChannel::STATE_HUNG_UP: // this state is coming before session is changed break; @@ -2659,15 +2786,15 @@ bool is_voice_call_type(const std::string &value) } LLIncomingCallDialog::LLIncomingCallDialog(const LLSD& payload) : -LLCallDialog(payload), -mAvatarNameCacheConnection() + LLCallDialog(payload), + mAvatarNameCacheConnection() { } void LLIncomingCallDialog::onLifetimeExpired() { - std::string session_handle = mPayload["session_handle"].asString(); - if (LLVoiceClient::getInstance()->isValidChannel(session_handle)) + LLVoiceP2PIncomingCallInterfacePtr call = LLVoiceClient::getInstance()->getIncomingCallInterface(mPayload["voice_channel_info"]); + if (call) { // restart notification's timer if call is still valid mLifetimeTimer.start(); @@ -2679,7 +2806,7 @@ void LLIncomingCallDialog::onLifetimeExpired() LLUUID session_id = mPayload["session_id"].asUUID(); gIMMgr->clearPendingAgentListUpdates(session_id); gIMMgr->clearPendingInvitation(session_id); - closeFloater(); + LLIncomingCallDialog::onReject(this); } } @@ -2858,16 +2985,17 @@ void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload { if (type == IM_SESSION_P2P_INVITE) { + if (session_name.empty()) + { + session_name = payload["caller_name"].asString(); + } // create a normal IM session session_id = gIMMgr->addP2PSession( - session_name, - caller_id, - payload["session_handle"].asString(), - payload["session_uri"].asString()); + session_name, caller_id, payload["voice_channel_info"]); if (voice) { - gIMMgr->startCall(session_id, LLVoiceChannel::INCOMING_CALL); + gIMMgr->startCall(session_id, LLVoiceChannel::INCOMING_CALL, payload["voice_channel_info"]); } else { @@ -2913,7 +3041,7 @@ void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload } } - gIMMgr->addSession(correct_session_name, type, session_id, true); + gIMMgr->addSession(correct_session_name, type, session_id, payload["voice_channel_info"]); std::string url = gAgent.getRegion()->getCapability( "ChatSessionRequest"); @@ -2921,8 +3049,7 @@ void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload if (voice) { LLCoros::instance().launch("chatterBoxInvitationCoro", - boost::bind(&chatterBoxInvitationCoro, url, - session_id, inv_type)); + boost::bind(&chatterBoxInvitationCoro, url, session_id, inv_type, payload["voice_channel_info"])); // send notification message to the corresponding chat if (payload["notify_box_type"].asString() == "VoiceInviteGroup" || payload["notify_box_type"].asString() == "VoiceInviteAdHoc") @@ -2943,114 +3070,48 @@ void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload { if (type == IM_SESSION_P2P_INVITE) { - if(LLVoiceClient::getInstance()) - { - std::string s = payload["session_handle"].asString(); - LLVoiceClient::getInstance()->declineInvite(s); - } - } - else - { - std::string url = gAgent.getRegion()->getCapability( - "ChatSessionRequest"); - - LLSD data; - data["method"] = "decline invitation"; - data["session-id"] = session_id; - - LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, data, - "Invitation declined", - "Invitation decline failed."); - } - } - - gIMMgr->clearPendingAgentListUpdates(session_id); - gIMMgr->clearPendingInvitation(session_id); - } -} - -bool inviteUserResponse(const LLSD& notification, const LLSD& response) -{ - if (!gIMMgr) - return false; - - const LLSD& payload = notification["payload"]; - LLUUID session_id = payload["session_id"].asUUID(); - EInstantMessage type = (EInstantMessage)payload["type"].asInteger(); - LLIMMgr::EInvitationType inv_type = (LLIMMgr::EInvitationType)payload["inv_type"].asInteger(); - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - switch(option) - { - case 0: // accept - { - if (type == IM_SESSION_P2P_INVITE) + // decline p2p voice, either via the vivox-style call mechanism + // or via the webrtc-style "decline p2p" mechanism. + LLVoiceP2PIncomingCallInterfacePtr call = LLVoiceClient::getInstance()->getIncomingCallInterface(payload["voice_channel_info"]); + if (call) { - // create a normal IM session - session_id = gIMMgr->addP2PSession( - payload["session_name"].asString(), - payload["caller_id"].asUUID(), - payload["session_handle"].asString(), - payload["session_uri"].asString()); - - gIMMgr->startCall(session_id); - - gIMMgr->clearPendingAgentListUpdates(session_id); - gIMMgr->clearPendingInvitation(session_id); + call->declineInvite(); } else { - gIMMgr->addSession( - payload["session_name"].asString(), - type, - session_id, true); + // webrtc-style decline. + LLViewerRegion *region = gAgent.getRegion(); + if (region) + { + std::string url = region->getCapability("ChatSessionRequest"); - std::string url = gAgent.getRegion()->getCapability( - "ChatSessionRequest"); + LLSD data; + data["method"] = "decline p2p voice"; + data["session-id"] = session_id; - LLCoros::instance().launch("chatterBoxInvitationCoro", - boost::bind(&chatterBoxInvitationCoro, url, - session_id, inv_type)); + LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, data, "P2P declined", "P2P decline failed."); + } } } - break; - case 2: // mute (also implies ignore, so this falls through to the "ignore" case below) - { - // mute the sender of this invite - if (!LLMuteList::getInstance()->isMuted(payload["caller_id"].asUUID())) - { - LLMute mute(payload["caller_id"].asUUID(), payload["caller_name"].asString(), LLMute::AGENT); - LLMuteList::getInstance()->add(mute); - } - } - /* FALLTHROUGH */ - - case 1: // decline - { - if (type == IM_SESSION_P2P_INVITE) - { - std::string s = payload["session_handle"].asString(); - LLVoiceClient::getInstance()->declineInvite(s); - } else { - std::string url = gAgent.getRegion()->getCapability( - "ChatSessionRequest"); + LLViewerRegion *region = gAgent.getRegion(); + if (region) + { + std::string url = region->getCapability("ChatSessionRequest"); - LLSD data; - data["method"] = "decline invitation"; - data["session-id"] = session_id; - LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, data, - "Invitation declined.", - "Invitation decline failed."); + LLSD data; + data["method"] = "decline invitation"; + data["session-id"] = session_id; + + LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, data, "Invitation declined", "Invitation decline failed."); + } } } gIMMgr->clearPendingAgentListUpdates(session_id); gIMMgr->clearPendingInvitation(session_id); - break; } - - return false; } // @@ -3124,7 +3185,7 @@ void LLIMMgr::addMessage( { fixed_session_name = av_name.getDisplayName(); } - LLIMModel::getInstance()->newSession(new_session_id, fixed_session_name, dialog, other_participant_id, false, is_offline_msg); + LLIMModel::getInstance()->newSession(new_session_id, fixed_session_name, dialog, other_participant_id, LLSD(), is_offline_msg); LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(new_session_id); if (session) @@ -3286,22 +3347,11 @@ void LLIMMgr::autoStartCallOnStartup(const LLUUID& session_id) } LLUUID LLIMMgr::addP2PSession(const std::string& name, - const LLUUID& other_participant_id, - const std::string& voice_session_handle, - const std::string& caller_uri) + const LLUUID& other_participant_id, + const LLSD& voice_channel_info) { - LLUUID session_id = addSession(name, IM_NOTHING_SPECIAL, other_participant_id, true); - - LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(session_id); - if (speaker_mgr) - { - LLVoiceChannelP2P* voice_channel = dynamic_cast<LLVoiceChannelP2P*>(speaker_mgr->getVoiceChannel()); - if (voice_channel) - { - voice_channel->setSessionHandle(voice_session_handle, caller_uri); - } - } - return session_id; + LL_DEBUGS("Voice") << "Add p2p voice channel info: " << voice_channel_info << LL_ENDL; + return addSession(name, IM_NOTHING_SPECIAL, other_participant_id, voice_channel_info); } // This adds a session to the talk view. The name is the local name of @@ -3311,11 +3361,12 @@ LLUUID LLIMMgr::addP2PSession(const std::string& name, LLUUID LLIMMgr::addSession( const std::string& name, EInstantMessage dialog, - const LLUUID& other_participant_id, bool voice) + const LLUUID& other_participant_id, + const LLSD& voiceChannelInfo) { std::vector<LLUUID> ids; ids.push_back(other_participant_id); - LLUUID session_id = addSession(name, dialog, other_participant_id, ids, voice); + LLUUID session_id = addSession(name, dialog, other_participant_id, ids, voiceChannelInfo); return session_id; } @@ -3325,7 +3376,8 @@ LLUUID LLIMMgr::addSession( const std::string& name, EInstantMessage dialog, const LLUUID& other_participant_id, - const std::vector<LLUUID>& ids, bool voice, + const std::vector<LLUUID>& ids, + const LLSD& voiceChannelInfo, const LLUUID& floater_id) { if (ids.empty()) @@ -3354,28 +3406,32 @@ LLUUID LLIMMgr::addSession( im_floater->reloadMessages(); } } + LLIMModel::LLIMSession *session = LLIMModel::getInstance()->findIMSession(session_id); - bool new_session = (LLIMModel::getInstance()->findIMSession(session_id) == NULL); + bool new_session = (session == NULL); //works only for outgoing ad-hoc sessions - if (new_session && IM_SESSION_CONFERENCE_START == dialog && ids.size()) + if (new_session && + ((IM_NOTHING_SPECIAL == dialog) || (IM_SESSION_P2P_INVITE == dialog) || (IM_SESSION_CONFERENCE_START == dialog)) && + ids.size()) { - LLIMModel::LLIMSession* ad_hoc_found = LLIMModel::getInstance()->findAdHocIMSession(ids); - if (ad_hoc_found) + session = LLIMModel::getInstance()->findAdHocIMSession(ids); + if (session) { new_session = false; - session_id = ad_hoc_found->mSessionID; + session_id = session->mSessionID; } } //Notify observers that a session was added if (new_session) { - LLIMModel::getInstance()->newSession(session_id, name, dialog, other_participant_id, ids, voice); + LLIMModel::getInstance()->newSession(session_id, name, dialog, other_participant_id, ids, voiceChannelInfo); } //Notifies observers that the session was already added else { + session->initVoiceChannel(voiceChannelInfo); std::string session_name = LLIMModel::getInstance()->getName(session_id); LLIMMgr::getInstance()->notifyObserverSessionActivated(session_id, session_name, other_participant_id); } @@ -3432,9 +3488,15 @@ void LLIMMgr::inviteToSession( const std::string& caller_name, EInstantMessage type, EInvitationType inv_type, - const std::string& session_handle, - const std::string& session_uri) + const LLSD& voice_channel_info) { + + if (caller_id == gAgentID) + { + // ignore invites from ourself. + return; + } + std::string notify_box_type; // voice invite question is different from default only for group call (EXT-7118) std::string question_type = "VoiceInviteQuestionDefault"; @@ -3475,11 +3537,11 @@ void LLIMMgr::inviteToSession( payload["caller_name"] = caller_name; payload["type"] = type; payload["inv_type"] = inv_type; - payload["session_handle"] = session_handle; - payload["session_uri"] = session_uri; payload["notify_box_type"] = notify_box_type; payload["question_type"] = question_type; + LL_WARNS("Voice") << "INVITE PAYLOAD: " << payload << LL_ENDL; + //ignore invites from muted residents if (!is_linden) { @@ -3504,7 +3566,6 @@ void LLIMMgr::inviteToSession( LLIncomingCallDialog::processCallResponse(0, payload); return; } - if (voice_invite) { bool isRejectGroupCall = (gSavedSettings.getBOOL("VoiceCallsRejectGroup") && (notify_box_type == "VoiceInviteGroup")); @@ -3528,7 +3589,7 @@ void LLIMMgr::inviteToSession( fixed_session_name = av_name.getDisplayName(); } } - LLIMModel::getInstance()->newSession(session_id, fixed_session_name, IM_NOTHING_SPECIAL, caller_id, false, false); + LLIMModel::getInstance()->newSession(session_id, fixed_session_name, IM_NOTHING_SPECIAL, caller_id, LLSD(), false); } LLSD args; @@ -3543,6 +3604,9 @@ void LLIMMgr::inviteToSession( if ( !mPendingInvitations.has(session_id.asString()) ) { + // we're throwing up a dialogue, so we're using the voice channel passed to us, + // save it in the payload. + payload["voice_channel_info"] = voice_channel_info; if (caller_name.empty()) { LLAvatarNameCache::get(caller_id, @@ -3596,6 +3660,40 @@ void LLIMMgr::clearPendingInvitation(const LLUUID& session_id) void LLIMMgr::processAgentListUpdates(const LLUUID& session_id, const LLSD& body) { + if (body.isMap() && body.has("agent_updates") && body["agent_updates"].isMap()) + { + LLSD::map_const_iterator update_it; + for (update_it = body["agent_updates"].beginMap(); update_it != body["agent_updates"].endMap(); ++update_it) + { + LLUUID agent_id = LLUUID(update_it->first); + LLSD agent_data = update_it->second; + if (agent_data.has("transition") && agent_data["transition"].asString() == "LEAVE") + { + // ignore actual leaves as those will be handled separately. + continue; + } + + if (agent_id != gAgentID && agent_data.isMap() && agent_data.has("info") && agent_data["info"].isMap()) + { + // Is one of the participants leaving a P2P Chat? + if (agent_data["info"].has("can_voice_chat") && !agent_data["info"]["can_voice_chat"].asBoolean()) + { + LLVoiceChannelGroup *channelp = dynamic_cast < LLVoiceChannelGroup*>(LLVoiceChannel::getChannelByID(session_id)); + if (channelp && channelp->isP2P()) + { + // it's an adhoc-style P2P channel, and the peer has declined voice. notify the user + // and shut down the voice channel. + LLSD notifyArgs = LLSD::emptyMap(); + notifyArgs["VOICE_CHANNEL_NAME"] = channelp->getSessionName(); + LLNotificationsUtil::add("P2PCallDeclined", notifyArgs); + endCall(session_id); + break; + } + } + } + } + } + LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); if ( im_floater ) { @@ -3753,11 +3851,14 @@ void LLIMMgr::removeSessionObserver(LLIMSessionObserver *observer) mSessionObservers.remove(observer); } -bool LLIMMgr::startCall(const LLUUID& session_id, LLVoiceChannel::EDirection direction) +bool LLIMMgr::startCall(const LLUUID& session_id, LLVoiceChannel::EDirection direction, const LLSD& voice_channel_info) { LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(session_id); if (!voice_channel) return false; - + if (voice_channel_info.isDefined() && voice_channel_info.isMap() && voice_channel_info.size() > 0) + { + voice_channel->setChannelInfo(voice_channel_info); + } voice_channel->setCallDirection(direction); voice_channel->activate(); return true; @@ -4065,6 +4166,15 @@ public: { im_mgr->processSessionUpdate(input["body"]["info"]); } + if (input["body"]["info"].has("voice_channel_info")) + { + LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(session_id); + if (session) + { + session->initVoiceChannel(input["body"]["info"]["voice_channel_info"]); + session->mVoiceChannel->activate(); + } + } } }; @@ -4149,7 +4259,7 @@ public: { LLCoros::instance().launch("chatterBoxInvitationCoro", boost::bind(&chatterBoxInvitationCoro, url, - session_id, LLIMMgr::INVITATION_TYPE_INSTANT_MESSAGE)); + session_id, LLIMMgr::INVITATION_TYPE_INSTANT_MESSAGE, LLSD())); } } //end if invitation has instant message else if ( input["body"].has("voice") ) @@ -4160,13 +4270,16 @@ public: return; } + BOOL session_type_p2p = input["body"]["voice"].get("invitation_type").asInteger() == EMultiAgentChatSessionType::P2P_CHAT_SESSION; + LL_DEBUGS("Voice") << "Received voice information from the server: " << input["body"]<< LL_ENDL; gIMMgr->inviteToSession( input["body"]["session_id"].asUUID(), input["body"]["session_name"].asString(), input["body"]["from_id"].asUUID(), input["body"]["from_name"].asString(), - IM_SESSION_INVITE, - LLIMMgr::INVITATION_TYPE_VOICE); + session_type_p2p ? IM_SESSION_P2P_INVITE : IM_SESSION_INVITE, + LLIMMgr::INVITATION_TYPE_VOICE, + input["body"]["voice"]); } else if ( input["body"].has("immediate") ) { diff --git a/indra/newview/llimview.h b/indra/newview/llimview.h index 8d1bc1c76a..9fd5e9d5c4 100644 --- a/indra/newview/llimview.h +++ b/indra/newview/llimview.h @@ -80,9 +80,11 @@ public: } SType; LLIMSession(const LLUUID& session_id, const std::string& name, - const EInstantMessage& type, const LLUUID& other_participant_id, const uuid_vec_t& ids, bool voice, bool has_offline_msg); + const EInstantMessage& type, const LLUUID& other_participant_id, const LLSD& voiceChannelInfo, const uuid_vec_t& ids, bool has_offline_msg); virtual ~LLIMSession(); + void initVoiceChannel(const LLSD &voiceChannelInfo = LLSD()); + void sessionInitReplyReceived(const LLUUID& new_session_id); void addMessagesFromHistoryCache(const std::list<LLSD>& history); // From local file void addMessagesFromServerHistory(const LLSD& history, const std::string& target_from, const std::string& target_message, U32 timestamp); // From chat server @@ -141,6 +143,7 @@ public: LLVoiceChannel* mVoiceChannel; LLIMSpeakerMgr* mSpeakers; + bool mP2PAsAdhocCall; bool mSessionInitialized; @@ -199,10 +202,10 @@ public: * @param name session name should not be empty, will return false if empty */ bool newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID& other_participant_id, - const uuid_vec_t& ids, bool voice = false, bool has_offline_msg = false); + const uuid_vec_t& ids, const LLSD& voiceChannelInfo = LLSD(), bool has_offline_msg = false); - bool newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, - const LLUUID& other_participant_id, bool voice = false, bool has_offline_msg = false); + bool newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID &other_participant_id, + const LLSD &voiceChannelInfo = LLSD(), bool has_offline_msg = false); /** * Remove all session data associated with a session specified by session_id @@ -296,7 +299,7 @@ public: static void sendLeaveSession(const LLUUID& session_id, const LLUUID& other_participant_id); static bool sendStartSession(const LLUUID& temp_session_id, const LLUUID& other_participant_id, - const uuid_vec_t& ids, EInstantMessage dialog); + const uuid_vec_t& ids, EInstantMessage dialog, bool p2p_as_adhoc_call); static void sendTypingState(LLUUID session_id, LLUUID other_participant_id, BOOL typing); static void sendMessage(const std::string& utf8_text, const LLUUID& im_session_id, const LLUUID& other_participant_id, EInstantMessage dialog); @@ -379,7 +382,8 @@ public: // session. LLUUID addSession(const std::string& name, EInstantMessage dialog, - const LLUUID& other_participant_id, bool voice = false); + const LLUUID& other_participant_id, + const LLSD& voiceChannelInfo = LLSD()); // Adds a session using a specific group of starting agents // the dialog type is assumed correct. Returns the uuid of the session. @@ -387,7 +391,8 @@ public: LLUUID addSession(const std::string& name, EInstantMessage dialog, const LLUUID& other_participant_id, - const std::vector<LLUUID>& ids, bool voice = false, + const std::vector<LLUUID> &ids, + const LLSD& voiceChannelInfo = LLSD(), const LLUUID& floater_id = LLUUID::null); /** @@ -397,10 +402,7 @@ public: * @param caller_uri - sip URI of caller. It should be always be passed into the method to avoid * incorrect working of LLVoiceChannel instances. See EXT-2985. */ - LLUUID addP2PSession(const std::string& name, - const LLUUID& other_participant_id, - const std::string& voice_session_handle, - const std::string& caller_uri); + LLUUID addP2PSession(const std::string &name, const LLUUID &other_participant_id, const LLSD &voice_call_info); /** * Leave the session with session id. Send leave session notification @@ -416,8 +418,8 @@ public: const std::string& caller_name, EInstantMessage type, EInvitationType inv_type, - const std::string& session_handle = LLStringUtil::null, - const std::string& session_uri = LLStringUtil::null); + const LLSD &voice_channel_info = LLSD() + ); void processIMTypingStart(const LLUUID& from_id, const EInstantMessage im_type); void processIMTypingStop(const LLUUID& from_id, const EInstantMessage im_type); @@ -465,7 +467,7 @@ public: * Start call in a session * @return false if voice channel doesn't exist **/ - bool startCall(const LLUUID& session_id, LLVoiceChannel::EDirection direction = LLVoiceChannel::OUTGOING_CALL); + bool startCall(const LLUUID& session_id, LLVoiceChannel::EDirection direction = LLVoiceChannel::OUTGOING_CALL, const LLSD& voice_channel_info = LLSD()); /** * End call in a session diff --git a/indra/newview/llpanelgroup.cpp b/indra/newview/llpanelgroup.cpp index 6b7680d19d..372f1a9d20 100644 --- a/indra/newview/llpanelgroup.cpp +++ b/indra/newview/llpanelgroup.cpp @@ -279,9 +279,9 @@ void LLPanelGroup::onBtnJoin() } else { - LL_DEBUGS() << "joining group: " << mID << LL_ENDL; - LLGroupActions::join(mID); - } + LL_DEBUGS() << "joining group: " << mID << LL_ENDL; + LLGroupActions::join(mID); +} } void LLPanelGroup::changed(LLGroupChange gc) @@ -292,7 +292,7 @@ void LLPanelGroup::changed(LLGroupChange gc) } // virtual -void LLPanelGroup::onChange(EStatusType status, const std::string &channelURI, bool proximal) +void LLPanelGroup::onChange(EStatusType status, const LLSD& channelInfo, bool proximal) { if(status == STATUS_JOINING || status == STATUS_LEFT_CHANNEL) { diff --git a/indra/newview/llpanelgroup.h b/indra/newview/llpanelgroup.h index d329a9561f..ddc1613fb6 100644 --- a/indra/newview/llpanelgroup.h +++ b/indra/newview/llpanelgroup.h @@ -62,7 +62,7 @@ public: // Implements LLVoiceClientStatusObserver::onChange() to enable the call // button when voice is available - /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal); + /*virtual*/ void onChange(EStatusType status, const LLSD& channelInfo, bool proximal); void showNotice(const std::string& subject, const std::string& message, diff --git a/indra/newview/llpanelpeople.cpp b/indra/newview/llpanelpeople.cpp index 44e860837e..24f3ad88cc 100644 --- a/indra/newview/llpanelpeople.cpp +++ b/indra/newview/llpanelpeople.cpp @@ -733,7 +733,7 @@ BOOL LLPanelPeople::postBuild() } // virtual -void LLPanelPeople::onChange(EStatusType status, const std::string &channelURI, bool proximal) +void LLPanelPeople::onChange(EStatusType status, const LLSD& channelInfo, bool proximal) { if(status == STATUS_JOINING || status == STATUS_LEFT_CHANNEL) { diff --git a/indra/newview/llpanelpeople.h b/indra/newview/llpanelpeople.h index f38c71e0b2..a00700700c 100644 --- a/indra/newview/llpanelpeople.h +++ b/indra/newview/llpanelpeople.h @@ -55,7 +55,7 @@ public: /*virtual*/ bool notifyChildren(const LLSD& info); // Implements LLVoiceClientStatusObserver::onChange() to enable call buttons // when voice is available - /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal); + /*virtual*/ void onChange(EStatusType status, const LLSD& channelInfo, bool proximal); // internals class Updater; diff --git a/indra/newview/llpanelprofile.cpp b/indra/newview/llpanelprofile.cpp index 0f0af37485..8b4d33cf7c 100644 --- a/indra/newview/llpanelprofile.cpp +++ b/indra/newview/llpanelprofile.cpp @@ -974,7 +974,7 @@ void LLPanelProfileSecondLife::fillCommonData(const LLAvatarData* avatar_data) setDescriptionText(avatar_data->about_text); - mSecondLifePic->setValue(avatar_data->image_id); + mSecondLifePic->setValue(avatar_data->image_id); if (getSelfProfile()) { @@ -1122,10 +1122,10 @@ void LLPanelProfileSecondLife::fillAgeData(const LLAvatarData* avatar_data) } else { - std::string register_date = getString("age_format"); - LLSD args_age; + std::string register_date = getString("age_format"); + LLSD args_age; args_age["[AGE]"] = LLDateUtil::ageFromDate(avatar_data->born_on, LLDate::now()); - LLStringUtil::format(register_date, args_age); + LLStringUtil::format(register_date, args_age); userAgeCtrl->setValue(register_date); } @@ -1172,7 +1172,7 @@ void LLPanelProfileSecondLife::changed(U32 mask) } // virtual, called by LLVoiceClient -void LLPanelProfileSecondLife::onChange(EStatusType status, const std::string &channelURI, bool proximal) +void LLPanelProfileSecondLife::onChange(EStatusType status, const LLSD& channelInfo, bool proximal) { if(status == STATUS_JOINING || status == STATUS_LEFT_CHANNEL) { @@ -1570,10 +1570,10 @@ void LLPanelProfileSecondLife::onShowInSearchCallback() mAllowPublish = value; saveAgentUserInfoCoro("allow_publish", value); -} + } void LLPanelProfileSecondLife::onHideAgeCallback() -{ + { bool value = mHideAgeCombo->getValue().asInteger(); if (value == mHideAge) return; @@ -1723,34 +1723,34 @@ void LLPanelProfileSecondLife::onCommitProfileImage(const LLUUID& id) return; std::function<void(bool)> callback = [id](bool result) - { - if (result) { - LLAvatarIconIDCache::getInstance()->add(gAgentID, id); + if (result) + { + LLAvatarIconIDCache::getInstance()->add(gAgentID, id); // Should trigger callbacks in icon controls (or request Legacy) - LLAvatarPropertiesProcessor::getInstance()->sendAvatarPropertiesRequest(gAgentID); - } - }; + LLAvatarPropertiesProcessor::getInstance()->sendAvatarPropertiesRequest(gAgentID); + } + }; if (!saveAgentUserInfoCoro("sl_image_id", id, callback)) return; mSecondLifePic->setValue(id); - LLFloater *floater = mFloaterProfileTextureHandle.get(); - if (floater) - { - LLFloaterProfileTexture * texture_view = dynamic_cast<LLFloaterProfileTexture*>(floater); - if (id == LLUUID::null) - { - texture_view->resetAsset(); - } - else + LLFloater *floater = mFloaterProfileTextureHandle.get(); + if (floater) { + LLFloaterProfileTexture * texture_view = dynamic_cast<LLFloaterProfileTexture*>(floater); + if (id == LLUUID::null) + { + texture_view->resetAsset(); + } + else + { texture_view->loadAsset(id); + } } } -} ////////////////////////////////////////////////////////////////////////// // LLPanelProfileWeb diff --git a/indra/newview/llpanelprofile.h b/indra/newview/llpanelprofile.h index 1afc248073..f0c0565628 100644 --- a/indra/newview/llpanelprofile.h +++ b/indra/newview/llpanelprofile.h @@ -85,7 +85,7 @@ public: // Implements LLVoiceClientStatusObserver::onChange() to enable the call // button when voice is available - void onChange(EStatusType status, const std::string &channelURI, bool proximal) override; + void onChange(EStatusType status, const LLSD& channelInfo, bool proximal) override; void setAvatarId(const LLUUID& avatar_id) override; diff --git a/indra/newview/llpanelvoicedevicesettings.cpp b/indra/newview/llpanelvoicedevicesettings.cpp index cac5421ffc..1a9c5716dc 100644 --- a/indra/newview/llpanelvoicedevicesettings.cpp +++ b/indra/newview/llpanelvoicedevicesettings.cpp @@ -256,43 +256,46 @@ void LLPanelVoiceDeviceSettings::refresh() if(mCtrlInputDevices) { - mCtrlInputDevices->removeall(); - mCtrlInputDevices->add(getLocalizedDeviceName(DEFAULT_DEVICE), DEFAULT_DEVICE, ADD_BOTTOM); - - for(device=LLVoiceClient::getInstance()->getCaptureDevices().begin(); - device != LLVoiceClient::getInstance()->getCaptureDevices().end(); - device++) + LLVoiceDeviceList devices = LLVoiceClient::getInstance()->getCaptureDevices(); + if (devices.size() > 0) // if zero, we've not received our devices yet { - mCtrlInputDevices->add(getLocalizedDeviceName(device->display_name), device->full_name, ADD_BOTTOM); - } + mCtrlInputDevices->removeall(); + mCtrlInputDevices->add(getLocalizedDeviceName(DEFAULT_DEVICE), DEFAULT_DEVICE, ADD_BOTTOM); + for (auto& device : devices) + { + mCtrlInputDevices->add(getLocalizedDeviceName(device.display_name), device.full_name, ADD_BOTTOM); + } - // Fix invalid input audio device preference. - if (!mCtrlInputDevices->setSelectedByValue(mInputDevice, TRUE)) - { - mCtrlInputDevices->setValue(DEFAULT_DEVICE); - gSavedSettings.setString("VoiceInputAudioDevice", DEFAULT_DEVICE); - mInputDevice = DEFAULT_DEVICE; + // Fix invalid input audio device preference. + if (!mCtrlInputDevices->setSelectedByValue(mInputDevice, TRUE)) + { + mCtrlInputDevices->setValue(DEFAULT_DEVICE); + gSavedSettings.setString("VoiceInputAudioDevice", DEFAULT_DEVICE); + mInputDevice = DEFAULT_DEVICE; + } } } if(mCtrlOutputDevices) { - mCtrlOutputDevices->removeall(); - mCtrlOutputDevices->add(getLocalizedDeviceName(DEFAULT_DEVICE), DEFAULT_DEVICE, ADD_BOTTOM); - - for(device = LLVoiceClient::getInstance()->getRenderDevices().begin(); - device != LLVoiceClient::getInstance()->getRenderDevices().end(); - device++) + LLVoiceDeviceList devices = LLVoiceClient::getInstance()->getRenderDevices(); + if (devices.size() > 0) // if zero, we've not received our devices yet { - mCtrlOutputDevices->add(getLocalizedDeviceName(device->display_name), device->full_name, ADD_BOTTOM); - } + mCtrlOutputDevices->removeall(); + mCtrlOutputDevices->add(getLocalizedDeviceName(DEFAULT_DEVICE), DEFAULT_DEVICE, ADD_BOTTOM); - // Fix invalid output audio device preference. - if (!mCtrlOutputDevices->setSelectedByValue(mOutputDevice, TRUE)) - { - mCtrlOutputDevices->setValue(DEFAULT_DEVICE); - gSavedSettings.setString("VoiceOutputAudioDevice", DEFAULT_DEVICE); - mOutputDevice = DEFAULT_DEVICE; + for (auto& device : devices) + { + mCtrlOutputDevices->add(getLocalizedDeviceName(device.display_name), device.full_name, ADD_BOTTOM); + } + + // Fix invalid output audio device preference. + if (!mCtrlOutputDevices->setSelectedByValue(mOutputDevice, TRUE)) + { + mCtrlOutputDevices->setValue(DEFAULT_DEVICE); + gSavedSettings.setString("VoiceOutputAudioDevice", DEFAULT_DEVICE); + mOutputDevice = DEFAULT_DEVICE; + } } } } @@ -336,8 +339,11 @@ void LLPanelVoiceDeviceSettings::onCommitInputDevice() if(LLVoiceClient::getInstance()) { mInputDevice = mCtrlInputDevices->getValue().asString(); - LLVoiceClient::getInstance()->setRenderDevice(mInputDevice); + LLVoiceClient::getInstance()->setCaptureDevice(mInputDevice); } + // the preferences floater stuff is a mess, hence apply will never + // be called when 'ok' is pressed, so just force it for now. + apply(); } void LLPanelVoiceDeviceSettings::onCommitOutputDevice() @@ -348,6 +354,9 @@ void LLPanelVoiceDeviceSettings::onCommitOutputDevice() mOutputDevice = mCtrlOutputDevices->getValue().asString(); LLVoiceClient::getInstance()->setRenderDevice(mOutputDevice); } + // the preferences floater stuff is a mess, hence apply will never + // be called when 'ok' is pressed, so just force it for now. + apply(); } void LLPanelVoiceDeviceSettings::onOutputDevicesClicked() diff --git a/indra/newview/llspeakers.cpp b/indra/newview/llspeakers.cpp index 2d8163d9e1..b12e8d15fc 100644 --- a/indra/newview/llspeakers.cpp +++ b/indra/newview/llspeakers.cpp @@ -288,6 +288,10 @@ LLSpeakerMgr::~LLSpeakerMgr() LLPointer<LLSpeaker> LLSpeakerMgr::setSpeaker(const LLUUID& id, const std::string& name, LLSpeaker::ESpeakerStatus status, LLSpeaker::ESpeakerType type) { + if (!mVoiceChannel) + { + return NULL; + } LLUUID session_id = getSessionID(); if (id.isNull() || (id == session_id)) { @@ -490,7 +494,7 @@ void LLSpeakerMgr::updateSpeakerList() (LLVoiceClient::getInstance()->isParticipantAvatar(*participant_it)?LLSpeaker::SPEAKER_AGENT:LLSpeaker::SPEAKER_EXTERNAL)); } } - else + else if (mVoiceChannel) { // If not, check if the list is empty, except if it's Nearby Chat (session_id NULL). LLUUID session_id = getSessionID(); @@ -618,7 +622,7 @@ void LLSpeakerMgr::getSpeakerList(speaker_list_t* speaker_list, BOOL include_tex const LLUUID LLSpeakerMgr::getSessionID() { - return mVoiceChannel->getSessionID(); + return mVoiceChannel ? mVoiceChannel->getSessionID() : LLUUID(); } bool LLSpeakerMgr::isSpeakerToBeRemoved(const LLUUID& speaker_id) @@ -816,7 +820,7 @@ void LLIMSpeakerMgr::updateSpeakers(const LLSD& update) void LLIMSpeakerMgr::toggleAllowTextChat(const LLUUID& speaker_id) { LLPointer<LLSpeaker> speakerp = findSpeaker(speaker_id); - if (!speakerp) return; + if (!speakerp || !mVoiceChannel) return; std::string url = gAgent.getRegionCapability("ChatSessionRequest"); LLSD data; @@ -835,7 +839,7 @@ void LLIMSpeakerMgr::toggleAllowTextChat(const LLUUID& speaker_id) void LLIMSpeakerMgr::moderateVoiceParticipant(const LLUUID& avatar_id, bool unmute) { LLPointer<LLSpeaker> speakerp = findSpeaker(avatar_id); - if (!speakerp) return; + if (!speakerp || !mVoiceChannel) return; // *NOTE: mantipov: probably this condition will be incorrect when avatar will be blocked for // text chat via moderation (LLSpeaker::mModeratorMutedText == TRUE) diff --git a/indra/newview/llspeakers.h b/indra/newview/llspeakers.h index eb86fadea1..c16da269e4 100644 --- a/indra/newview/llspeakers.h +++ b/indra/newview/llspeakers.h @@ -241,6 +241,7 @@ public: typedef std::vector<LLPointer<LLSpeaker> > speaker_list_t; void getSpeakerList(speaker_list_t* speaker_list, BOOL include_text); LLVoiceChannel* getVoiceChannel() { return mVoiceChannel; } + void setVoiceChannel(LLVoiceChannel *voiceChannel) { mVoiceChannel = voiceChannel; } const LLUUID getSessionID(); bool isSpeakerToBeRemoved(const LLUUID& speaker_id); diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp index b69f1aa41b..7997bcfbdb 100644 --- a/indra/newview/llviewercontrol.cpp +++ b/indra/newview/llviewercontrol.cpp @@ -844,6 +844,9 @@ void settings_setup_listeners() setting_setup_signal_listener(gSavedSettings, "PushToTalkButton", handleVoiceClientPrefsChanged); setting_setup_signal_listener(gSavedSettings, "PushToTalkToggle", handleVoiceClientPrefsChanged); setting_setup_signal_listener(gSavedSettings, "VoiceEarLocation", handleVoiceClientPrefsChanged); + setting_setup_signal_listener(gSavedSettings, "VoiceEchoCancellation", handleVoiceClientPrefsChanged); + setting_setup_signal_listener(gSavedSettings, "VoiceAutomaticGainControl", handleVoiceClientPrefsChanged); + setting_setup_signal_listener(gSavedSettings, "VoiceNoiseSuppressionLevel", handleVoiceClientPrefsChanged); setting_setup_signal_listener(gSavedSettings, "VoiceInputAudioDevice", handleVoiceClientPrefsChanged); setting_setup_signal_listener(gSavedSettings, "VoiceOutputAudioDevice", handleVoiceClientPrefsChanged); setting_setup_signal_listener(gSavedSettings, "AudioLevelMic", handleVoiceClientPrefsChanged); diff --git a/indra/newview/llviewerhelp.cpp b/indra/newview/llviewerhelp.cpp index e7b150965d..bca1c4166a 100644 --- a/indra/newview/llviewerhelp.cpp +++ b/indra/newview/llviewerhelp.cpp @@ -45,7 +45,7 @@ public: // requests will be throttled from a non-trusted browser LLHelpHandler() : LLCommandHandler("help", UNTRUSTED_CLICK_ONLY) {} - bool handle(const LLSD& params, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) + bool handle(const LLSD& params, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) override { LLViewerHelp* vhelp = LLViewerHelp::getInstance(); if (! vhelp) diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index 2ca27e0e1a..d17a3cccdd 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -9362,15 +9362,6 @@ class LLUpdateMembershipLabel : public view_listener_t } }; -void handle_voice_morphing_subscribe() -{ - LLWeb::loadURL(LLTrans::getString("voice_morphing_url")); -} - -void handle_premium_voice_morphing_subscribe() -{ - LLWeb::loadURL(LLTrans::getString("premium_voice_morphing_url")); -} class LLToggleUIHints : public view_listener_t { @@ -9571,16 +9562,6 @@ void initialize_menus() //Communicate Nearby chat view_listener_t::addMenu(new LLCommunicateNearbyChat(), "Communicate.NearbyChat"); - // Communicate > Voice morphing > Subscribe... - commit.add("Communicate.VoiceMorphing.Subscribe", boost::bind(&handle_voice_morphing_subscribe)); - // Communicate > Voice morphing > Premium perk... - commit.add("Communicate.VoiceMorphing.PremiumPerk", boost::bind(&handle_premium_voice_morphing_subscribe)); - LLVivoxVoiceClient * voice_clientp = LLVivoxVoiceClient::getInstance(); - enable.add("Communicate.VoiceMorphing.NoVoiceMorphing.Check" - , boost::bind(&LLVivoxVoiceClient::onCheckVoiceEffect, voice_clientp, "NoVoiceMorphing")); - commit.add("Communicate.VoiceMorphing.NoVoiceMorphing.Click" - , boost::bind(&LLVivoxVoiceClient::onClickVoiceEffect, voice_clientp, "NoVoiceMorphing")); - // World menu view_listener_t::addMenu(new LLWorldAlwaysRun(), "World.AlwaysRun"); view_listener_t::addMenu(new LLWorldCreateLandmark(), "World.CreateLandmark"); diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index d24e15a7d3..9a5ae3c9bf 100755 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -3232,6 +3232,7 @@ void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames) capabilityNames.append("ParcelVoiceInfoRequest"); capabilityNames.append("ProductInfoRequest"); capabilityNames.append("ProvisionVoiceAccountRequest"); + capabilityNames.append("VoiceSignalingRequest"); capabilityNames.append("ReadOfflineMsgs"); // Requires to respond reliably: AcceptFriendship, AcceptGroupInvite, DeclineFriendship, DeclineGroupInvite capabilityNames.append("RegionObjects"); capabilityNames.append("RemoteParcelRequest"); diff --git a/indra/newview/llviewerregion.h b/indra/newview/llviewerregion.h index b88d68fdf8..d598406952 100644 --- a/indra/newview/llviewerregion.h +++ b/indra/newview/llviewerregion.h @@ -43,6 +43,7 @@ #include "m4math.h" // LLMatrix4 #include "llframetimer.h" #include "llreflectionmap.h" +#include "llpointer.h" // Surface id's #define LAND 1 diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index d7382eaac6..258ab80ea4 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -614,7 +614,8 @@ BOOL LLVOAvatar::sShowAnimationDebug = FALSE; BOOL LLVOAvatar::sVisibleInFirstPerson = FALSE; F32 LLVOAvatar::sLODFactor = 1.f; F32 LLVOAvatar::sPhysicsLODFactor = 1.f; -BOOL LLVOAvatar::sJointDebug = FALSE; +BOOL LLVOAvatar::sJointDebug = FALSE; +BOOL LLVOAvatar::sLipSyncEnabled = FALSE; F32 LLVOAvatar::sUnbakedTime = 0.f; F32 LLVOAvatar::sUnbakedUpdateTime = 0.f; F32 LLVOAvatar::sGreyTime = 0.f; @@ -1155,6 +1156,7 @@ void LLVOAvatar::initClass() LLControlAvatar::sRegionChangedSlot = gAgent.addRegionChangedCallback(&LLControlAvatar::onRegionChanged); sCloudTexture = LLViewerTextureManager::getFetchedTextureFromFile("cloud-particle.j2c"); + gSavedSettings.getControl("LipSyncEnabled")->getSignal()->connect(boost::bind(&LLVOAvatar::handleVOAvatarPrefsChanged, _2)); } @@ -1162,6 +1164,12 @@ void LLVOAvatar::cleanupClass() { } +bool LLVOAvatar::handleVOAvatarPrefsChanged(const LLSD &newvalue) +{ + sLipSyncEnabled = gSavedSettings.getBOOL("LipSyncEnabled"); + return true; +} + // virtual void LLVOAvatar::initInstance() { @@ -3057,7 +3065,7 @@ void LLVOAvatar::idleUpdateLipSync(bool voice_enabled) // Use the Lipsync_Ooh and Lipsync_Aah morphs for lip sync if ( voice_enabled && mLastRezzedStatus > 0 // no point updating lip-sync for clouds - && (LLVoiceClient::getInstance()->lipSyncEnabled()) + && sLipSyncEnabled && LLVoiceClient::getInstance()->getIsSpeaking( mID ) ) { F32 ooh_morph_amount = 0.0f; @@ -9341,8 +9349,8 @@ void LLVOAvatar::parseAppearanceMessage(LLMessageSystem* mesgsys, LLAppearanceMe } else { - LL_DEBUGS("Avatar") << "AvatarAppearance msg received without any parameters, object: " << getID() << LL_ENDL; - } + LL_DEBUGS("Avatar") << "AvatarAppearance msg received without any parameters, object: " << getID() << LL_ENDL; + } LLVisualParam* appearance_version_param = getVisualParam(11000); if (appearance_version_param) diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h index c14784cc6d..1ccefa9c1e 100644 --- a/indra/newview/llvoavatar.h +++ b/indra/newview/llvoavatar.h @@ -109,6 +109,7 @@ public: virtual void initInstance(); // Called after construction to initialize the class. protected: virtual ~LLVOAvatar(); + static bool handleVOAvatarPrefsChanged(const LLSD &newvalue); /** Initialization ** ** @@ -366,6 +367,7 @@ public: static F32 sLODFactor; // user-settable LOD factor static F32 sPhysicsLODFactor; // user-settable physics LOD factor static BOOL sJointDebug; // output total number of joints being touched for each avatar + static BOOL sLipSyncEnabled; static LLPointer<LLViewerTexture> sCloudTexture; diff --git a/indra/newview/llvocache.cpp b/indra/newview/llvocache.cpp index bfac68b68f..a65e49a724 100644 --- a/indra/newview/llvocache.cpp +++ b/indra/newview/llvocache.cpp @@ -529,6 +529,16 @@ F32 LLVOCacheEntry::getSquaredPixelThreshold(bool is_front) bool LLVOCacheEntry::isAnyVisible(const LLVector4a& camera_origin, const LLVector4a& local_camera_origin, F32 dist_threshold) { +#if 0 + // this is ill-conceived and should be removed pending QA + // In the name of saving memory, we evict objects that are still within view distance from memory + // This results in constant paging of objects in and out of memory, leading to poor performance + // and many unacceptable visual glitches when rotating the camera + + // Honestly, the entire VOCache partition system needs to be removed since it doubles the overhead of + // the spatial partition system and is redundant to the object cache, but this is a start + // - davep 2024.06.07 + LLOcclusionCullingGroup* group = (LLOcclusionCullingGroup*)getGroup(); if(!group) { @@ -565,6 +575,9 @@ bool LLVOCacheEntry::isAnyVisible(const LLVector4a& camera_origin, const LLVecto } return vis; +#else + return true; +#endif } void LLVOCacheEntry::calcSceneContribution(const LLVector4a& camera_origin, bool needs_update, U32 last_update, F32 max_dist) diff --git a/indra/newview/llvoicechannel.cpp b/indra/newview/llvoicechannel.cpp index 7ca73a24bf..8681411a98 100644 --- a/indra/newview/llvoicechannel.cpp +++ b/indra/newview/llvoicechannel.cpp @@ -39,7 +39,6 @@ #include "llcorehttputil.h" LLVoiceChannel::voice_channel_map_t LLVoiceChannel::sVoiceChannelMap; -LLVoiceChannel::voice_channel_map_uri_t LLVoiceChannel::sVoiceChannelURIMap; LLVoiceChannel* LLVoiceChannel::sCurrentVoiceChannel = NULL; LLVoiceChannel* LLVoiceChannel::sSuspendedVoiceChannel = NULL; LLVoiceChannel::channel_changed_signal_t LLVoiceChannel::sCurrentVoiceChannelChangedSignal; @@ -89,29 +88,18 @@ LLVoiceChannel::~LLVoiceChannel() } sVoiceChannelMap.erase(mSessionID); - sVoiceChannelURIMap.erase(mURI); } -void LLVoiceChannel::setChannelInfo( - const std::string& uri, - const std::string& credentials) +void LLVoiceChannel::setChannelInfo(const LLSD &channelInfo) { - setURI(uri); - - mCredentials = credentials; + mChannelInfo = channelInfo; if (mState == STATE_NO_CHANNEL_INFO) { - if (mURI.empty()) - { - LLNotificationsUtil::add("VoiceChannelJoinFailed", mNotifyArgs); - LL_WARNS("Voice") << "Received empty URI for channel " << mSessionName << LL_ENDL; - deactivate(); - } - else if (mCredentials.empty()) + if (mChannelInfo.isUndefined() || !mChannelInfo.isMap() || mChannelInfo.size() == 0) { LLNotificationsUtil::add("VoiceChannelJoinFailed", mNotifyArgs); - LL_WARNS("Voice") << "Received empty credentials for channel " << mSessionName << LL_ENDL; + LL_WARNS("Voice") << "Received empty channel info for channel " << mSessionName << LL_ENDL; deactivate(); } else @@ -130,9 +118,15 @@ void LLVoiceChannel::setChannelInfo( } } -void LLVoiceChannel::onChange(EStatusType type, const std::string &channelURI, bool proximal) +void LLVoiceChannel::onChange(EStatusType type, const LLSD& channelInfo, bool proximal) { - if (channelURI != mURI) + LL_DEBUGS("Voice") << "Incoming channel info: " << channelInfo << LL_ENDL; + LL_DEBUGS("Voice") << "Current channel info: " << mChannelInfo << LL_ENDL; + if (mChannelInfo.isUndefined() || (mChannelInfo.isMap() && mChannelInfo.size() == 0)) + { + mChannelInfo = channelInfo; + } + if (!LLVoiceClient::getInstance()->compareChannels(mChannelInfo, channelInfo)) { return; } @@ -158,13 +152,16 @@ void LLVoiceChannel::handleStatusChange(EStatusType type) case STATUS_LOGGED_IN: break; case STATUS_LEFT_CHANNEL: - if (callStarted() && !mIgnoreNextSessionLeave && !sSuspended) + if (callStarted() && !sSuspended) { // if forceably removed from channel // update the UI and revert to default channel + // deactivate will set the State to STATE_HUNG_UP + // so when handleStatusChange is called again during + // shutdown callStarted will return false and deactivate + // won't be called again. deactivate(); } - mIgnoreNextSessionLeave = FALSE; break; case STATUS_JOINING: if (callStarted()) @@ -193,7 +190,7 @@ void LLVoiceChannel::handleError(EStatusType type) BOOL LLVoiceChannel::isActive() { // only considered active when currently bound channel matches what our channel - return callStarted() && LLVoiceClient::getInstance()->getCurrentChannel() == mURI; + return callStarted() && LLVoiceClient::getInstance()->isCurrentChannel(mChannelInfo); } BOOL LLVoiceChannel::callStarted() @@ -246,10 +243,8 @@ void LLVoiceChannel::activate() // activating the proximal channel between IM calls LLVoiceChannel* old_channel = sCurrentVoiceChannel; sCurrentVoiceChannel = this; - mCallDialogPayload["old_channel_name"] = ""; if (old_channel) { - mCallDialogPayload["old_channel_name"] = old_channel->getSessionName(); old_channel->deactivate(); } } @@ -257,7 +252,7 @@ void LLVoiceChannel::activate() if (mState == STATE_NO_CHANNEL_INFO) { // responsible for setting status to active - getChannelInfo(); + requestChannelInfo(); } else { @@ -270,7 +265,7 @@ void LLVoiceChannel::activate() sCurrentVoiceChannelChangedSignal(this->mSessionID); } -void LLVoiceChannel::getChannelInfo() +void LLVoiceChannel::requestChannelInfo() { // pretend we have everything we need if (sCurrentVoiceChannel == this) @@ -293,20 +288,6 @@ LLVoiceChannel* LLVoiceChannel::getChannelByID(const LLUUID& session_id) } } -//static -LLVoiceChannel* LLVoiceChannel::getChannelByURI(std::string uri) -{ - voice_channel_map_uri_t::iterator found_it = sVoiceChannelURIMap.find(uri); - if (found_it == sVoiceChannelURIMap.end()) - { - return NULL; - } - else - { - return found_it->second; - } -} - LLVoiceChannel* LLVoiceChannel::getCurrentVoiceChannel() { return sCurrentVoiceChannel; @@ -319,13 +300,6 @@ void LLVoiceChannel::updateSessionID(const LLUUID& new_session_id) sVoiceChannelMap.insert(std::make_pair(mSessionID, this)); } -void LLVoiceChannel::setURI(std::string uri) -{ - sVoiceChannelURIMap.erase(mURI); - mURI = uri; - sVoiceChannelURIMap.insert(std::make_pair(mURI, this)); -} - void LLVoiceChannel::setState(EState state) { switch(state) @@ -410,8 +384,11 @@ boost::signals2::connection LLVoiceChannel::setCurrentVoiceChannelChangedCallbac // LLVoiceChannelGroup // -LLVoiceChannelGroup::LLVoiceChannelGroup(const LLUUID& session_id, const std::string& session_name) : - LLVoiceChannel(session_id, session_name) +LLVoiceChannelGroup::LLVoiceChannelGroup(const LLUUID &session_id, + const std::string &session_name, + bool is_p2p) : + LLVoiceChannel(session_id, session_name), + mIsP2P(is_p2p) { mRetries = DEFAULT_RETRIES_COUNT; mIsRetrying = FALSE; @@ -424,7 +401,15 @@ void LLVoiceChannelGroup::deactivate() LLVoiceClient::getInstance()->leaveNonSpatialChannel(); } LLVoiceChannel::deactivate(); -} + + if (mIsP2P) + { + // void the channel info for p2p adhoc channels + // so we request it again, hence throwing up the + // connect dialogue on the other side. + setState(STATE_NO_CHANNEL_INFO); + } + } void LLVoiceChannelGroup::activate() { @@ -435,43 +420,46 @@ void LLVoiceChannelGroup::activate() if (callStarted()) { // we have the channel info, just need to use it now - LLVoiceClient::getInstance()->setNonSpatialChannel( - mURI, - mCredentials); + LLVoiceClient::getInstance()->setNonSpatialChannel(mChannelInfo, + mIsP2P && (mCallDirection == OUTGOING_CALL), + mIsP2P); - if (!gAgent.isInGroup(mSessionID)) // ad-hoc channel + if (mIsP2P) { - LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(mSessionID); - // Adding ad-hoc call participants to Recent People List. - // If it's an outgoing ad-hoc, we can use mInitialTargetIDs that holds IDs of people we - // called(both online and offline) as source to get people for recent (STORM-210). - if (session->isOutgoingAdHoc()) + LLIMModel::addSpeakersToRecent(mSessionID); + } + else + { + if (!gAgent.isInGroup(mSessionID)) // ad-hoc channel { - for (uuid_vec_t::iterator it = session->mInitialTargetIDs.begin(); - it!=session->mInitialTargetIDs.end();++it) + LLIMModel::LLIMSession *session = LLIMModel::getInstance()->findIMSession(mSessionID); + // Adding ad-hoc call participants to Recent People List. + // If it's an outgoing ad-hoc, we can use mInitialTargetIDs that holds IDs of people we + // called(both online and offline) as source to get people for recent (STORM-210). + if (session && session->isOutgoingAdHoc()) { - const LLUUID id = *it; - LLRecentPeople::instance().add(id); + for (uuid_vec_t::iterator it = session->mInitialTargetIDs.begin(); it != session->mInitialTargetIDs.end(); ++it) + { + const LLUUID id = *it; + LLRecentPeople::instance().add(id); + } + } + // If this ad-hoc is incoming then trying to get ids of people from mInitialTargetIDs + // would lead to EXT-8246. So in this case we get them from speakers list. + else + { + LLIMModel::addSpeakersToRecent(mSessionID); } - } - // If this ad-hoc is incoming then trying to get ids of people from mInitialTargetIDs - // would lead to EXT-8246. So in this case we get them from speakers list. - else - { - LLIMModel::addSpeakersToRecent(mSessionID); } } - //Mic default state is OFF on initiating/joining Ad-Hoc/Group calls - if (LLVoiceClient::getInstance()->getUserPTTState() && LLVoiceClient::getInstance()->getPTTIsToggle()) - { - LLVoiceClient::getInstance()->inputUserControlState(true); - } + // Mic default state is OFF on initiating/joining Ad-Hoc/Group calls. It's on for P2P using the AdHoc infra. + LLVoiceClient::getInstance()->setUserPTTState(mIsP2P); } } -void LLVoiceChannelGroup::getChannelInfo() +void LLVoiceChannelGroup::requestChannelInfo() { LLViewerRegion* region = gAgent.getRegion(); if (region) @@ -483,17 +471,13 @@ void LLVoiceChannelGroup::getChannelInfo() } } -void LLVoiceChannelGroup::setChannelInfo( - const std::string& uri, - const std::string& credentials) +void LLVoiceChannelGroup::setChannelInfo(const LLSD& channelInfo) { - setURI(uri); - - mCredentials = credentials; + mChannelInfo = channelInfo; if (mState == STATE_NO_CHANNEL_INFO) { - if(!mURI.empty() && !mCredentials.empty()) + if(mChannelInfo.isDefined() && mChannelInfo.isMap()) { setState(STATE_READY); @@ -516,9 +500,9 @@ void LLVoiceChannelGroup::setChannelInfo( else if ( mIsRetrying ) { // we have the channel info, just need to use it now - LLVoiceClient::getInstance()->setNonSpatialChannel( - mURI, - mCredentials); + LLVoiceClient::getInstance()->setNonSpatialChannel(channelInfo, + mCallDirection == OUTGOING_CALL, + mIsP2P); } } @@ -556,7 +540,7 @@ void LLVoiceChannelGroup::handleError(EStatusType status) mIsRetrying = TRUE; mIgnoreNextSessionLeave = TRUE; - getChannelInfo(); + requestChannelInfo(); return; } else @@ -612,6 +596,16 @@ void LLVoiceChannelGroup::voiceCallCapCoro(std::string url) LLSD postData; postData["method"] = "call"; postData["session-id"] = mSessionID; + LLSD altParams; + std::string preferred_voice_server_type = gSavedSettings.getString("VoiceServerType"); + if (preferred_voice_server_type.empty()) + { + // default to the server type associated with the region we're on. + LLVoiceVersionInfo versionInfo = LLVoiceClient::getInstance()->getVersion(); + preferred_voice_server_type = versionInfo.internalVoiceServerType; + } + altParams["preferred_voice_server_type"] = preferred_voice_server_type; + postData["alt_params"] = altParams; LL_INFOS("Voice", "voiceCallCapCoro") << "Generic POST for " << url << LL_ENDL; @@ -651,14 +645,12 @@ void LLVoiceChannelGroup::voiceCallCapCoro(std::string url) LLSD::map_const_iterator iter; for (iter = result.beginMap(); iter != result.endMap(); ++iter) { - LL_DEBUGS("Voice") << "LLVoiceCallCapResponder::result got " + LL_DEBUGS("Voice") << "LLVoiceChannelGroup::voiceCallCapCoro got " << iter->first << LL_ENDL; } + LL_INFOS("Voice") << "LLVoiceChannelGroup::voiceCallCapCoro got " << result << LL_ENDL; - channelp->setChannelInfo( - result["voice_credentials"]["channel_uri"].asString(), - result["voice_credentials"]["channel_credentials"].asString()); - + channelp->setChannelInfo(result["voice_credentials"]); } @@ -684,11 +676,12 @@ void LLVoiceChannelProximal::activate() // we're connected to a non-spatial channel, so disconnect. LLVoiceClient::getInstance()->leaveNonSpatialChannel(); } - LLVoiceChannel::activate(); + LLVoiceClient::getInstance()->activateSpatialChannel(true); + LLVoiceChannel::activate(); } -void LLVoiceChannelProximal::onChange(EStatusType type, const std::string &channelURI, bool proximal) +void LLVoiceChannelProximal::onChange(EStatusType type, const LLSD& channelInfo, bool proximal) { if (!proximal) { @@ -749,7 +742,7 @@ void LLVoiceChannelProximal::handleError(EStatusType status) LLNotificationsUtil::add(notify, mNotifyArgs); } - LLVoiceChannel::handleError(status); + // proximal voice remains up and the provider will try to reconnect. } void LLVoiceChannelProximal::deactivate() @@ -758,19 +751,23 @@ void LLVoiceChannelProximal::deactivate() { setState(STATE_HUNG_UP); } + LLVoiceClient::getInstance()->removeObserver(this); + LLVoiceClient::getInstance()->activateSpatialChannel(false); } // // LLVoiceChannelP2P // -LLVoiceChannelP2P::LLVoiceChannelP2P(const LLUUID& session_id, const std::string& session_name, const LLUUID& other_user_id) : - LLVoiceChannelGroup(session_id, session_name), - mOtherUserID(other_user_id), - mReceivedCall(FALSE) +LLVoiceChannelP2P::LLVoiceChannelP2P(const LLUUID &session_id, + const std::string &session_name, + const LLUUID &other_user_id, + LLVoiceP2POutgoingCallInterface* outgoing_call_interface) : + LLVoiceChannelGroup(session_id, session_name, true), + mOtherUserID(other_user_id), + mReceivedCall(FALSE), + mOutgoingCallInterface(outgoing_call_interface) { - // make sure URI reflects encoded version of other user's agent id - setURI(LLVoiceClient::getInstance()->sipURIFromID(other_user_id)); } void LLVoiceChannelP2P::handleStatusChange(EStatusType type) @@ -837,23 +834,23 @@ void LLVoiceChannelP2P::activate() if (callStarted()) { // no session handle yet, we're starting the call - if (mSessionHandle.empty()) + if (mIncomingCallInterface == nullptr) { mReceivedCall = FALSE; - LLVoiceClient::getInstance()->callUser(mOtherUserID); + mOutgoingCallInterface->callUser(mOtherUserID); } // otherwise answering the call else { - if (!LLVoiceClient::getInstance()->answerInvite(mSessionHandle)) + if (!mIncomingCallInterface->answerInvite()) { mCallEndedByAgent = false; - mSessionHandle.clear(); + mIncomingCallInterface.reset(); handleError(ERROR_UNKNOWN); return; } - // using the session handle invalidates it. Clear it out here so we can't reuse it by accident. - mSessionHandle.clear(); + // using the incoming call interface invalidates it. Clear it out here so we can't reuse it by accident. + mIncomingCallInterface.reset(); } // Add the party to the list of people with which we've recently interacted. @@ -867,7 +864,17 @@ void LLVoiceChannelP2P::activate() } } -void LLVoiceChannelP2P::getChannelInfo() +void LLVoiceChannelP2P::deactivate() +{ + if (callStarted()) + { + mOutgoingCallInterface->hangup(); + } + LLVoiceChannel::deactivate(); +} + + +void LLVoiceChannelP2P::requestChannelInfo() { // pretend we have everything we need, since P2P doesn't use channel info if (sCurrentVoiceChannel == this) @@ -877,8 +884,9 @@ void LLVoiceChannelP2P::getChannelInfo() } // receiving session from other user who initiated call -void LLVoiceChannelP2P::setSessionHandle(const std::string& handle, const std::string &inURI) +void LLVoiceChannelP2P::setChannelInfo(const LLSD& channel_info) { + mChannelInfo = channel_info; BOOL needs_activate = FALSE; if (callStarted()) { @@ -893,28 +901,16 @@ void LLVoiceChannelP2P::setSessionHandle(const std::string& handle, const std::s { // we are active and have priority, invite the other user again // under the assumption they will join this new session - mSessionHandle.clear(); - LLVoiceClient::getInstance()->callUser(mOtherUserID); + mOutgoingCallInterface->callUser(mOtherUserID); return; } } - mSessionHandle = handle; - - // The URI of a p2p session should always be the other end's SIP URI. - if(!inURI.empty()) - { - setURI(inURI); - } - else + mReceivedCall = TRUE; + if (channel_info.isDefined() && channel_info.isMap()) { - LL_WARNS("Voice") << "incoming SIP URL is not provided. Channel may not work properly." << LL_ENDL; - // See LLVoiceClient::sessionAddedEvent() - setURI(LLVoiceClient::getInstance()->sipURIFromID(mOtherUserID)); + mIncomingCallInterface = LLVoiceClient::getInstance()->getIncomingCallInterface(channel_info); } - - mReceivedCall = TRUE; - if (needs_activate) { activate(); @@ -932,7 +928,7 @@ void LLVoiceChannelP2P::setState(EState state) if (mReceivedCall && state == STATE_RINGING) { //TODO: remove or redirect this call status notification -// LLCallInfoDialog::show("answering", mNotifyArgs); + // LLCallInfoDialog::show("answering", mNotifyArgs); doSetState(state); return; } diff --git a/indra/newview/llvoicechannel.h b/indra/newview/llvoicechannel.h index 28651b73ec..eb16cc22ba 100644 --- a/indra/newview/llvoicechannel.h +++ b/indra/newview/llvoicechannel.h @@ -66,16 +66,14 @@ public: LLVoiceChannel(const LLUUID& session_id, const std::string& session_name); virtual ~LLVoiceChannel(); - /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal); + virtual void onChange(EStatusType status, const LLSD& channelInfo, bool proximal); virtual void handleStatusChange(EStatusType status); virtual void handleError(EStatusType status); virtual void deactivate(); virtual void activate(); - virtual void setChannelInfo( - const std::string& uri, - const std::string& credentials); - virtual void getChannelInfo(); + virtual void setChannelInfo(const LLSD &channelInfo); + virtual void requestChannelInfo(); virtual BOOL isActive(); virtual BOOL callStarted(); @@ -95,8 +93,9 @@ public: void setCallDirection(EDirection direction) {mCallDirection = direction;} EDirection getCallDirection() {return mCallDirection;} + bool isThisVoiceChannel(const LLSD &voiceChannelInfo) { return LLVoiceClient::getInstance()->compareChannels(mChannelInfo, voiceChannelInfo); } + static LLVoiceChannel* getChannelByID(const LLUUID& session_id); - static LLVoiceChannel* getChannelByURI(std::string uri); static LLVoiceChannel* getCurrentVoiceChannel(); static void initClass(); @@ -104,35 +103,30 @@ public: static void suspend(); static void resume(); -protected: + protected: virtual void setState(EState state); /** * Use this method if you want mStateChangedCallback to be executed while state is changed */ void doSetState(const EState& state); - void setURI(std::string uri); // there can be two directions INCOMING and OUTGOING EDirection mCallDirection; - std::string mURI; - std::string mCredentials; LLUUID mSessionID; EState mState; std::string mSessionName; - LLSD mNotifyArgs; - LLSD mCallDialogPayload; + LLSD mNotifyArgs; + LLSD mChannelInfo; // true if call was ended by agent bool mCallEndedByAgent; - BOOL mIgnoreNextSessionLeave; + bool mIgnoreNextSessionLeave; LLHandle<LLPanel> mLoginNotificationHandle; typedef std::map<LLUUID, LLVoiceChannel*> voice_channel_map_t; static voice_channel_map_t sVoiceChannelMap; - typedef std::map<std::string, LLVoiceChannel*> voice_channel_map_uri_t; - static voice_channel_map_uri_t sVoiceChannelURIMap; - + static LLVoiceChannel* sProximalVoiceChannel; static LLVoiceChannel* sCurrentVoiceChannel; static LLVoiceChannel* sSuspendedVoiceChannel; static BOOL sSuspended; @@ -144,55 +138,60 @@ private: class LLVoiceChannelGroup : public LLVoiceChannel { public: - LLVoiceChannelGroup(const LLUUID& session_id, const std::string& session_name); + LLVoiceChannelGroup(const LLUUID& session_id, + const std::string& session_name, + bool is_p2p); + + void handleStatusChange(EStatusType status) override; + void handleError(EStatusType status) override; + void activate() override; + void deactivate() override; + void setChannelInfo(const LLSD &channelInfo) override; + void requestChannelInfo() override; - /*virtual*/ void handleStatusChange(EStatusType status); - /*virtual*/ void handleError(EStatusType status); - /*virtual*/ void activate(); - /*virtual*/ void deactivate(); - /*vritual*/ void setChannelInfo( - const std::string& uri, - const std::string& credentials); - /*virtual*/ void getChannelInfo(); + bool isP2P() { return mIsP2P; } protected: - virtual void setState(EState state); + void setState(EState state) override; private: void voiceCallCapCoro(std::string url); U32 mRetries; BOOL mIsRetrying; + bool mIsP2P; }; class LLVoiceChannelProximal : public LLVoiceChannel, public LLSingleton<LLVoiceChannelProximal> { - LLSINGLETON(LLVoiceChannelProximal); -public: - - /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal) override; - /*virtual*/ void handleStatusChange(EStatusType status) override; - /*virtual*/ void handleError(EStatusType status) override; - /*virtual*/ BOOL isActive() override; - /*virtual*/ void activate() override; - /*virtual*/ void deactivate() override; - + LLSINGLETON_C11(LLVoiceChannelProximal); + public: + + void onChange(EStatusType status, const LLSD &channelInfo, bool proximal) override; + void handleStatusChange(EStatusType status) override; + void handleError(EStatusType status) override; + BOOL isActive() override; + void activate() override; + void deactivate() override; }; class LLVoiceChannelP2P : public LLVoiceChannelGroup { -public: - LLVoiceChannelP2P(const LLUUID& session_id, const std::string& session_name, const LLUUID& other_user_id); - - /*virtual*/ void handleStatusChange(EStatusType status) override; - /*virtual*/ void handleError(EStatusType status) override; - /*virtual*/ void activate() override; - /*virtual*/ void getChannelInfo() override; - - void setSessionHandle(const std::string& handle, const std::string &inURI); - -protected: - virtual void setState(EState state) override; + public: + LLVoiceChannelP2P(const LLUUID &session_id, + const std::string &session_name, + const LLUUID &other_user_id, + LLVoiceP2POutgoingCallInterface * outgoing_call_interface); + + void handleStatusChange(EStatusType status) override; + void handleError(EStatusType status) override; + void activate() override; + void requestChannelInfo() override; + void deactivate() override; + void setChannelInfo(const LLSD& channel_info) override; + + protected: + void setState(EState state) override; private: @@ -201,10 +200,10 @@ private: * **/ void addToTheRecentPeopleList(); - - std::string mSessionHandle; LLUUID mOtherUserID; BOOL mReceivedCall; + LLVoiceP2POutgoingCallInterface *mOutgoingCallInterface; + LLVoiceP2PIncomingCallInterfacePtr mIncomingCallInterface; }; #endif // LL_VOICECHANNEL_H diff --git a/indra/newview/llvoiceclient.cpp b/indra/newview/llvoiceclient.cpp index 0d882cf49b..779f4e3176 100644 --- a/indra/newview/llvoiceclient.cpp +++ b/indra/newview/llvoiceclient.cpp @@ -24,13 +24,13 @@ * $/LicenseInfo$ */ -#include "llviewerprecompiledheaders.h" #include "llvoiceclient.h" -#include "llviewercontrol.h" -#include "llviewerwindow.h" #include "llvoicevivox.h" +#include "llvoicewebrtc.h" #include "llviewernetwork.h" +#include "llviewercontrol.h" #include "llcommandhandler.h" +#include "lldir.h" #include "llhttpnode.h" #include "llnotificationsutil.h" #include "llsdserialize.h" @@ -113,16 +113,33 @@ std::string LLVoiceClientStatusObserver::status2string(LLVoiceClientStatusObserv return result; } - +LLVoiceModuleInterface *getVoiceModule(const std::string &voice_server_type) +{ + if (voice_server_type == VIVOX_VOICE_SERVER_TYPE || voice_server_type.empty()) + { + return (LLVoiceModuleInterface *) LLVivoxVoiceClient::getInstance(); + } + else if (voice_server_type == WEBRTC_VOICE_SERVER_TYPE) + { + return (LLVoiceModuleInterface *) LLWebRTCVoiceClient::getInstance(); + } + else + { + LLNotificationsUtil::add("VoiceVersionMismatch"); + return nullptr; + } +} /////////////////////////////////////////////////////////////////////////////////////////////// LLVoiceClient::LLVoiceClient(LLPumpIO *pump) : - mVoiceModule(NULL), + mSpatialVoiceModule(NULL), + mNonSpatialVoiceModule(NULL), m_servicePump(NULL), - mVoiceEffectEnabled(LLCachedControl<bool>(gSavedSettings, "VoiceMorphingEnabled", true)), + mVoiceEffectEnabled(LLCachedControl<bool>(gSavedSettings, "VoiceMorphingEnabled", false)), mVoiceEffectDefault(LLCachedControl<std::string>(gSavedPerAccountSettings, "VoiceEffectDefault", "00000000-0000-0000-0000-000000000000")), + mVoiceEffectSupportNotified(false), mPTTDirty(true), mPTT(true), mUsePTT(true), @@ -133,7 +150,6 @@ LLVoiceClient::LLVoiceClient(LLPumpIO *pump) mMuteMic(false), mDisableMic(false) { - updateSettings(); init(pump); } @@ -142,46 +158,138 @@ LLVoiceClient::LLVoiceClient(LLPumpIO *pump) LLVoiceClient::~LLVoiceClient() { - llassert(!mVoiceModule); } void LLVoiceClient::init(LLPumpIO *pump) { // Initialize all of the voice modules m_servicePump = pump; + LLWebRTCVoiceClient::getInstance()->init(pump); + LLVivoxVoiceClient::getInstance()->init(pump); } void LLVoiceClient::userAuthorized(const std::string& user_id, const LLUUID &agentID) { - // In the future, we should change this to allow voice module registration - // with a table lookup of sorts. - std::string voice_server = gSavedSettings.getString("VoiceServerType"); - LL_DEBUGS("Voice") << "voice server type " << voice_server << LL_ENDL; - if(voice_server == "vivox") + gAgent.addRegionChangedCallback(boost::bind(&LLVoiceClient::onRegionChanged, this)); + LLWebRTCVoiceClient::getInstance()->userAuthorized(user_id, agentID); + LLVivoxVoiceClient::getInstance()->userAuthorized(user_id, agentID); +} + +void LLVoiceClient::handleSimulatorFeaturesReceived(const LLSD &simulatorFeatures) +{ + std::string voiceServerType = simulatorFeatures["VoiceServerType"].asString(); + if (voiceServerType.empty()) { - mVoiceModule = (LLVoiceModuleInterface *)LLVivoxVoiceClient::getInstance(); + voiceServerType = VIVOX_VOICE_SERVER_TYPE; } - else + + if (mSpatialVoiceModule && !mNonSpatialVoiceModule) + { + // stop processing if we're going to change voice modules + // and we're not currently in non-spatial. + LLVoiceVersionInfo version = mSpatialVoiceModule->getVersion(); + if (version.internalVoiceServerType != voiceServerType) + { + mSpatialVoiceModule->processChannels(false); + } + } + setSpatialVoiceModule(simulatorFeatures["VoiceServerType"].asString()); + + // if we should be in spatial voice, switch to it and set the creds + if (mSpatialVoiceModule && !mNonSpatialVoiceModule) + { + if (!mSpatialCredentials.isUndefined()) + { + mSpatialVoiceModule->setSpatialChannel(mSpatialCredentials); + } + mSpatialVoiceModule->processChannels(true); + } +} + +static void simulator_features_received_callback(const LLUUID& region_id) +{ + LLViewerRegion *region = gAgent.getRegion(); + if (region && (region->getRegionID() == region_id)) + { + LLSD simulatorFeatures; + region->getSimulatorFeatures(simulatorFeatures); + if (LLVoiceClient::getInstance()) + { + LLVoiceClient::getInstance()->handleSimulatorFeaturesReceived(simulatorFeatures); + } + } +} + +void LLVoiceClient::onRegionChanged() +{ + LLViewerRegion *region = gAgent.getRegion(); + if (region && region->simulatorFeaturesReceived()) + { + LLSD simulatorFeatures; + region->getSimulatorFeatures(simulatorFeatures); + if (LLVoiceClient::getInstance()) + { + LLVoiceClient::getInstance()->handleSimulatorFeaturesReceived(simulatorFeatures); + } + } + else if (region) + { + if (mSimulatorFeaturesReceivedSlot.connected()) + { + mSimulatorFeaturesReceivedSlot.disconnect(); + } + mSimulatorFeaturesReceivedSlot = + region->setSimulatorFeaturesReceivedCallback(boost::bind(&simulator_features_received_callback, _1)); + } +} + +void LLVoiceClient::setSpatialVoiceModule(const std::string &voice_server_type) +{ + LLVoiceModuleInterface *module = getVoiceModule(voice_server_type); + if (!module) { - mVoiceModule = NULL; return; } - mVoiceModule->init(m_servicePump); - mVoiceModule->userAuthorized(user_id, agentID); + if (module != mSpatialVoiceModule) + { + if (inProximalChannel()) + { + mSpatialVoiceModule->processChannels(false); + } + module->processChannels(true); + mSpatialVoiceModule = module; + mSpatialVoiceModule->updateSettings(); + } +} + +void LLVoiceClient::setNonSpatialVoiceModule(const std::string &voice_server_type) +{ + mNonSpatialVoiceModule = getVoiceModule(voice_server_type); + if (!mNonSpatialVoiceModule) + { + // we don't have a non-spatial voice module, + // so revert to spatial. + if (mSpatialVoiceModule) + { + mSpatialVoiceModule->processChannels(true); + } + return; + } + mNonSpatialVoiceModule->updateSettings(); } void LLVoiceClient::setHidden(bool hidden) { - if (mVoiceModule) + if (mSpatialVoiceModule) { - mVoiceModule->setHidden(hidden); + mSpatialVoiceModule->setHidden(hidden); } } void LLVoiceClient::terminate() { - if (mVoiceModule) mVoiceModule->terminate(); - mVoiceModule = NULL; + if (mSpatialVoiceModule) mSpatialVoiceModule->terminate(); + mSpatialVoiceModule = NULL; m_servicePump = NULL; // Shutdown speaker volume storage before LLSingletonBase::deleteAll() does it @@ -193,15 +301,15 @@ void LLVoiceClient::terminate() const LLVoiceVersionInfo LLVoiceClient::getVersion() { - if (mVoiceModule) + if (mSpatialVoiceModule) { - return mVoiceModule->getVersion(); + return mSpatialVoiceModule->getVersion(); } else { LLVoiceVersionInfo result; result.serverVersion = std::string(); - result.serverType = std::string(); + result.voiceServerType = std::string(); result.mBuildVersion = std::string(); return result; } @@ -215,10 +323,8 @@ void LLVoiceClient::updateSettings() updateMicMuteLogic(); - if (mVoiceModule) - { - mVoiceModule->updateSettings(); - } + LLWebRTCVoiceClient::getInstance()->updateSettings(); + LLVivoxVoiceClient::getInstance()->updateSettings(); } //-------------------------------------------------- @@ -226,117 +332,75 @@ void LLVoiceClient::updateSettings() void LLVoiceClient::tuningStart() { - if (mVoiceModule) mVoiceModule->tuningStart(); + LLWebRTCVoiceClient::getInstance()->tuningStart(); + LLVivoxVoiceClient::getInstance()->tuningStart(); } void LLVoiceClient::tuningStop() { - if (mVoiceModule) mVoiceModule->tuningStop(); + LLWebRTCVoiceClient::getInstance()->tuningStop(); + LLVivoxVoiceClient::getInstance()->tuningStop(); } bool LLVoiceClient::inTuningMode() { - if (mVoiceModule) - { - return mVoiceModule->inTuningMode(); - } - else - { - return false; - } + return LLWebRTCVoiceClient::getInstance()->inTuningMode(); } void LLVoiceClient::tuningSetMicVolume(float volume) { - if (mVoiceModule) mVoiceModule->tuningSetMicVolume(volume); + LLWebRTCVoiceClient::getInstance()->tuningSetMicVolume(volume); } void LLVoiceClient::tuningSetSpeakerVolume(float volume) { - if (mVoiceModule) mVoiceModule->tuningSetSpeakerVolume(volume); + LLWebRTCVoiceClient::getInstance()->tuningSetSpeakerVolume(volume); } float LLVoiceClient::tuningGetEnergy(void) { - if (mVoiceModule) - { - return mVoiceModule->tuningGetEnergy(); - } - else - { - return 0.0; - } + return LLWebRTCVoiceClient::getInstance()->tuningGetEnergy(); } - //------------------------------------------------ // devices bool LLVoiceClient::deviceSettingsAvailable() { - if (mVoiceModule) - { - return mVoiceModule->deviceSettingsAvailable(); - } - else - { - return false; - } + return LLWebRTCVoiceClient::getInstance()->deviceSettingsAvailable(); } bool LLVoiceClient::deviceSettingsUpdated() { - if (mVoiceModule) - { - return mVoiceModule->deviceSettingsUpdated(); - } - else - { - return false; - } + return LLWebRTCVoiceClient::getInstance()->deviceSettingsUpdated(); } void LLVoiceClient::refreshDeviceLists(bool clearCurrentList) { - if (mVoiceModule) mVoiceModule->refreshDeviceLists(clearCurrentList); + LLWebRTCVoiceClient::getInstance()->refreshDeviceLists(clearCurrentList); } void LLVoiceClient::setCaptureDevice(const std::string& name) { - if (mVoiceModule) mVoiceModule->setCaptureDevice(name); - + LLVivoxVoiceClient::getInstance()->setCaptureDevice(name); + LLWebRTCVoiceClient::getInstance()->setCaptureDevice(name); } void LLVoiceClient::setRenderDevice(const std::string& name) { - if (mVoiceModule) mVoiceModule->setRenderDevice(name); + LLVivoxVoiceClient::getInstance()->setRenderDevice(name); + LLWebRTCVoiceClient::getInstance()->setRenderDevice(name); } const LLVoiceDeviceList& LLVoiceClient::getCaptureDevices() { - static LLVoiceDeviceList nullCaptureDevices; - if (mVoiceModule) - { - return mVoiceModule->getCaptureDevices(); - } - else - { - return nullCaptureDevices; - } + return LLWebRTCVoiceClient::getInstance()->getCaptureDevices(); } const LLVoiceDeviceList& LLVoiceClient::getRenderDevices() { - static LLVoiceDeviceList nullRenderDevices; - if (mVoiceModule) - { - return mVoiceModule->getRenderDevices(); - } - else - { - return nullRenderDevices; - } + return LLWebRTCVoiceClient::getInstance()->getRenderDevices(); } @@ -345,74 +409,30 @@ const LLVoiceDeviceList& LLVoiceClient::getRenderDevices() void LLVoiceClient::getParticipantList(std::set<LLUUID> &participants) { - if (mVoiceModule) - { - mVoiceModule->getParticipantList(participants); - } - else - { - participants = std::set<LLUUID>(); - } + LLWebRTCVoiceClient::getInstance()->getParticipantList(participants); + LLVivoxVoiceClient::getInstance()->getParticipantList(participants); } bool LLVoiceClient::isParticipant(const LLUUID &speaker_id) { - if(mVoiceModule) - { - return mVoiceModule->isParticipant(speaker_id); - } - return false; + return LLWebRTCVoiceClient::getInstance()->isParticipant(speaker_id) || + LLVivoxVoiceClient::getInstance()->isParticipant(speaker_id); } //-------------------------------------------------- // text chat - BOOL LLVoiceClient::isSessionTextIMPossible(const LLUUID& id) { - if (mVoiceModule) - { - return mVoiceModule->isSessionTextIMPossible(id); - } - else - { - return FALSE; - } + // all sessions can do TextIM, as we no longer support PSTN + return TRUE; } BOOL LLVoiceClient::isSessionCallBackPossible(const LLUUID& id) { - if (mVoiceModule) - { - return mVoiceModule->isSessionCallBackPossible(id); - } - else - { - return FALSE; - } -} - -/* obsolete -BOOL LLVoiceClient::sendTextMessage(const LLUUID& participant_id, const std::string& message) -{ - if (mVoiceModule) - { - return mVoiceModule->sendTextMessage(participant_id, message); - } - else - { - return FALSE; - } -} -*/ - -void LLVoiceClient::endUserIMSession(const LLUUID& participant_id) -{ - if (mVoiceModule) - { - // mVoiceModule->endUserIMSession(participant_id); // A SLim leftover - } + // we don't support PSTN calls anymore. (did we ever?) + return TRUE; } //---------------------------------------------- @@ -420,9 +440,9 @@ void LLVoiceClient::endUserIMSession(const LLUUID& participant_id) bool LLVoiceClient::inProximalChannel() { - if (mVoiceModule) + if (mSpatialVoiceModule) { - return mVoiceModule->inProximalChannel(); + return mSpatialVoiceModule->inProximalChannel(); } else { @@ -431,128 +451,162 @@ bool LLVoiceClient::inProximalChannel() } void LLVoiceClient::setNonSpatialChannel( - const std::string &uri, - const std::string &credentials) + const LLSD& channelInfo, + bool notify_on_first_join, + bool hangup_on_last_leave) { - if (mVoiceModule) + setNonSpatialVoiceModule(channelInfo["voice_server_type"].asString()); + if (mSpatialVoiceModule && mSpatialVoiceModule != mNonSpatialVoiceModule) + { + mSpatialVoiceModule->processChannels(false); + } + if (mNonSpatialVoiceModule) { - mVoiceModule->setNonSpatialChannel(uri, credentials); + mNonSpatialVoiceModule->processChannels(true); + mNonSpatialVoiceModule->setNonSpatialChannel(channelInfo, notify_on_first_join, hangup_on_last_leave); } } -void LLVoiceClient::setSpatialChannel( - const std::string &uri, - const std::string &credentials) +void LLVoiceClient::setSpatialChannel(const LLSD &channelInfo) { - if (mVoiceModule) + mSpatialCredentials = channelInfo; + LLViewerRegion *region = gAgent.getRegion(); + if (region && region->simulatorFeaturesReceived()) { - mVoiceModule->setSpatialChannel(uri, credentials); + LLSD simulatorFeatures; + region->getSimulatorFeatures(simulatorFeatures); + setSpatialVoiceModule(simulatorFeatures["VoiceServerType"].asString()); + } + else + { + return; } -} -void LLVoiceClient::leaveNonSpatialChannel() -{ - if (mVoiceModule) + if (mSpatialVoiceModule) { - mVoiceModule->leaveNonSpatialChannel(); + mSpatialVoiceModule->setSpatialChannel(channelInfo); } } -void LLVoiceClient::leaveChannel(void) +void LLVoiceClient::leaveNonSpatialChannel() { - if (mVoiceModule) + if (mNonSpatialVoiceModule) { - mVoiceModule->leaveChannel(); + mNonSpatialVoiceModule->leaveNonSpatialChannel(); + mNonSpatialVoiceModule->processChannels(false); + mNonSpatialVoiceModule = nullptr; } } -std::string LLVoiceClient::getCurrentChannel() +void LLVoiceClient::activateSpatialChannel(bool activate) { - if (mVoiceModule) + if (mSpatialVoiceModule) { - return mVoiceModule->getCurrentChannel(); - } - else - { - return std::string(); + mSpatialVoiceModule->processChannels(activate); } } +bool LLVoiceClient::isCurrentChannel(const LLSD& channelInfo) +{ + return LLWebRTCVoiceClient::getInstance()->isCurrentChannel(channelInfo) || + LLVivoxVoiceClient::getInstance()->isCurrentChannel(channelInfo); +} -//--------------------------------------- -// invitations - -void LLVoiceClient::callUser(const LLUUID &uuid) +bool LLVoiceClient::compareChannels(const LLSD &channelInfo1, const LLSD &channelInfo2) { - if (mVoiceModule) mVoiceModule->callUser(uuid); + return LLWebRTCVoiceClient::getInstance()->compareChannels(channelInfo1, channelInfo2) || + LLVivoxVoiceClient::getInstance()->compareChannels(channelInfo1, channelInfo2); } -bool LLVoiceClient::isValidChannel(std::string &session_handle) +LLVoiceP2PIncomingCallInterfacePtr LLVoiceClient::getIncomingCallInterface(const LLSD& voice_call_info) { - if (mVoiceModule) + LLVoiceModuleInterface *module = getVoiceModule(voice_call_info["voice_server_type"]); + if (module) { - return mVoiceModule->isValidChannel(session_handle); - } - else - { - return false; + return module->getIncomingCallInterface(voice_call_info); } + return nullptr; + } -bool LLVoiceClient::answerInvite(std::string &channelHandle) +//--------------------------------------- +// outgoing calls +LLVoiceP2POutgoingCallInterface *LLVoiceClient::getOutgoingCallInterface(const LLSD& voiceChannelInfo) { - if (mVoiceModule) + std::string voice_server_type = gSavedSettings.getString("VoiceServerType"); + if (voice_server_type.empty()) { - return mVoiceModule->answerInvite(channelHandle); + // default to the server type associated with the region we're on. + LLVoiceVersionInfo versionInfo = LLVoiceClient::getInstance()->getVersion(); + voice_server_type = versionInfo.internalVoiceServerType; } - else + if (voiceChannelInfo.has("voice_server_type") && voiceChannelInfo["voice_server_type"] != voice_server_type) { - return false; + // there's a mismatch between what the peer is offering and what our server + // can handle, so downgrade to vivox + voice_server_type = VIVOX_VOICE_SERVER_TYPE; } + LLVoiceModuleInterface *module = getVoiceModule(voice_server_type); + return dynamic_cast<LLVoiceP2POutgoingCallInterface *>(module); } -void LLVoiceClient::declineInvite(std::string &channelHandle) -{ - if (mVoiceModule) mVoiceModule->declineInvite(channelHandle); -} - - //------------------------------------------ // Volume/gain void LLVoiceClient::setVoiceVolume(F32 volume) { - if (mVoiceModule) mVoiceModule->setVoiceVolume(volume); + LLWebRTCVoiceClient::getInstance()->setVoiceVolume(volume); + LLVivoxVoiceClient::getInstance()->setVoiceVolume(volume); } -void LLVoiceClient::setMicGain(F32 volume) +void LLVoiceClient::setMicGain(F32 gain) { - if (mVoiceModule) mVoiceModule->setMicGain(volume); + LLWebRTCVoiceClient::getInstance()->setMicGain(gain); + LLVivoxVoiceClient::getInstance()->setMicGain(gain); } //------------------------------------------ // enable/disable voice features -bool LLVoiceClient::voiceEnabled() +// static +bool LLVoiceClient::onVoiceEffectsNotSupported(const LLSD ¬ification, const LLSD &response) { - if (mVoiceModule) + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + switch (option) { - return mVoiceModule->voiceEnabled(); + case 0: // "Okay" + gSavedSettings.setBOOL("VoiceMorphingEnabled", FALSE); + break; + + case 1: // "Cancel" + break; + + default: + llassert(0); + break; } - else + return false; +} + +bool LLVoiceClient::voiceEnabled() +{ + static LLCachedControl<bool> enable_voice_chat(gSavedSettings, "EnableVoiceChat"); + static LLCachedControl<bool> cmd_line_disable_voice(gSavedSettings, "CmdLineDisableVoice"); + bool enabled = enable_voice_chat && !cmd_line_disable_voice && !gNonInteractive; + if (enabled && !mVoiceEffectSupportNotified && getVoiceEffectEnabled() && !getVoiceEffectDefault().isNull()) { - return false; + LLNotificationsUtil::add("VoiceEffectsNotSupported", LLSD(), LLSD(), &LLVoiceClient::onVoiceEffectsNotSupported); + mVoiceEffectSupportNotified = true; } + return enabled; } void LLVoiceClient::setVoiceEnabled(bool enabled) { - if (mVoiceModule) - { - mVoiceModule->setVoiceEnabled(enabled); - } + LLWebRTCVoiceClient::getInstance()->setVoiceEnabled(enabled); + LLVivoxVoiceClient::getInstance()->setVoiceEnabled(enabled); } void LLVoiceClient::updateMicMuteLogic() @@ -571,32 +625,18 @@ void LLVoiceClient::updateMicMuteLogic() // Either of these always overrides any other PTT setting. new_mic_mute = true; } - - if (mVoiceModule) mVoiceModule->setMuteMic(new_mic_mute); -} - -void LLVoiceClient::setLipSyncEnabled(BOOL enabled) -{ - if (mVoiceModule) mVoiceModule->setLipSyncEnabled(enabled); + LLWebRTCVoiceClient::getInstance()->setMuteMic(new_mic_mute); + LLVivoxVoiceClient::getInstance()->setMuteMic(new_mic_mute); } -BOOL LLVoiceClient::lipSyncEnabled() +void LLVoiceClient::setMuteMic(bool muted) { - if (mVoiceModule) + if (mMuteMic != muted) { - return mVoiceModule->lipSyncEnabled(); + mMuteMic = muted; + updateMicMuteLogic(); + mMicroChangedSignal(); } - else - { - return false; - } -} - -void LLVoiceClient::setMuteMic(bool muted) -{ - mMuteMic = muted; - updateMicMuteLogic(); - mMicroChangedSignal(); } @@ -675,117 +715,71 @@ void LLVoiceClient::toggleUserPTTState(void) BOOL LLVoiceClient::getVoiceEnabled(const LLUUID& id) { - if (mVoiceModule) - { - return mVoiceModule->getVoiceEnabled(id); - } - else - { - return FALSE; - } + return isParticipant(id) ? TRUE : FALSE; } std::string LLVoiceClient::getDisplayName(const LLUUID& id) { - if (mVoiceModule) - { - return mVoiceModule->getDisplayName(id); - } - else + std::string result = LLWebRTCVoiceClient::getInstance()->getDisplayName(id); + if (result.empty()) { - return std::string(); + result = LLVivoxVoiceClient::getInstance()->getDisplayName(id); } + return result; } bool LLVoiceClient::isVoiceWorking() const { - if (mVoiceModule) - { - return mVoiceModule->isVoiceWorking(); - } - return false; + return LLVivoxVoiceClient::getInstance()->isVoiceWorking() || + LLWebRTCVoiceClient::getInstance()->isVoiceWorking(); } BOOL LLVoiceClient::isParticipantAvatar(const LLUUID& id) { - if (mVoiceModule) - { - return mVoiceModule->isParticipantAvatar(id); - } - else - { - return FALSE; - } + return TRUE; } BOOL LLVoiceClient::isOnlineSIP(const LLUUID& id) { - return FALSE; + return FALSE; } BOOL LLVoiceClient::getIsSpeaking(const LLUUID& id) { - if (mVoiceModule) - { - return mVoiceModule->getIsSpeaking(id); - } - else - { - return FALSE; - } + return LLWebRTCVoiceClient::getInstance()->getIsSpeaking(id) || + LLVivoxVoiceClient::getInstance()->getIsSpeaking(id); } BOOL LLVoiceClient::getIsModeratorMuted(const LLUUID& id) { - if (mVoiceModule) - { - return mVoiceModule->getIsModeratorMuted(id); - } - else - { - return FALSE; - } + // don't bother worrying about p2p calls, as + // p2p calls don't have mute. + return LLWebRTCVoiceClient::getInstance()->getIsModeratorMuted(id) || + LLVivoxVoiceClient::getInstance()->getIsModeratorMuted(id); } F32 LLVoiceClient::getCurrentPower(const LLUUID& id) { - if (mVoiceModule) - { - return mVoiceModule->getCurrentPower(id); - } - else - { - return 0.0; - } + return std::fmax(LLVivoxVoiceClient::getInstance()->getCurrentPower(id), + LLWebRTCVoiceClient::getInstance()->getCurrentPower(id)); } BOOL LLVoiceClient::getOnMuteList(const LLUUID& id) { - if (mVoiceModule) - { - return mVoiceModule->getOnMuteList(id); - } - else - { - return FALSE; - } + // don't bother worrying about p2p calls, as + // p2p calls don't have mute. + return LLMuteList::getInstance()->isMuted(id, LLMute::flagVoiceChat); } F32 LLVoiceClient::getUserVolume(const LLUUID& id) { - if (mVoiceModule) - { - return mVoiceModule->getUserVolume(id); - } - else - { - return 0.0; - } + return std::fmax(LLVivoxVoiceClient::getInstance()->getUserVolume(id), LLWebRTCVoiceClient::getInstance()->getUserVolume(id)); } void LLVoiceClient::setUserVolume(const LLUUID& id, F32 volume) { - if (mVoiceModule) mVoiceModule->setUserVolume(id, volume); + LLWebRTCVoiceClient::getInstance()->setUserVolume(id, volume); + LLVivoxVoiceClient::getInstance()->setUserVolume(id, volume); } //-------------------------------------------------- @@ -793,48 +787,49 @@ void LLVoiceClient::setUserVolume(const LLUUID& id, F32 volume) void LLVoiceClient::addObserver(LLVoiceClientStatusObserver* observer) { - if (mVoiceModule) mVoiceModule->addObserver(observer); + LLVivoxVoiceClient::getInstance()->addObserver(observer); + LLWebRTCVoiceClient::getInstance()->addObserver(observer); } void LLVoiceClient::removeObserver(LLVoiceClientStatusObserver* observer) { - if (mVoiceModule) - { - mVoiceModule->removeObserver(observer); - } + LLVivoxVoiceClient::getInstance()->removeObserver(observer); + LLWebRTCVoiceClient::getInstance()->removeObserver(observer); } void LLVoiceClient::addObserver(LLFriendObserver* observer) { - if (mVoiceModule) mVoiceModule->addObserver(observer); + LLVivoxVoiceClient::getInstance()->addObserver(observer); + LLWebRTCVoiceClient::getInstance()->addObserver(observer); } void LLVoiceClient::removeObserver(LLFriendObserver* observer) { - if (mVoiceModule) - { - mVoiceModule->removeObserver(observer); - } + LLVivoxVoiceClient::getInstance()->removeObserver(observer); + LLWebRTCVoiceClient::getInstance()->removeObserver(observer); } void LLVoiceClient::addObserver(LLVoiceClientParticipantObserver* observer) { - if (mVoiceModule) mVoiceModule->addObserver(observer); + LLVivoxVoiceClient::getInstance()->addObserver(observer); + LLWebRTCVoiceClient::getInstance()->addObserver(observer); } void LLVoiceClient::removeObserver(LLVoiceClientParticipantObserver* observer) { - if (mVoiceModule) - { - mVoiceModule->removeObserver(observer); - } + LLVivoxVoiceClient::getInstance()->removeObserver(observer); + LLWebRTCVoiceClient::getInstance()->removeObserver(observer); } std::string LLVoiceClient::sipURIFromID(const LLUUID &id) { - if (mVoiceModule) + if (mNonSpatialVoiceModule) + { + return mNonSpatialVoiceModule->sipURIFromID(id); + } + else if (mSpatialVoiceModule) { - return mVoiceModule->sipURIFromID(id); + return mSpatialVoiceModule->sipURIFromID(id); } else { @@ -844,7 +839,7 @@ std::string LLVoiceClient::sipURIFromID(const LLUUID &id) LLVoiceEffectInterface* LLVoiceClient::getVoiceEffectInterface() const { - return getVoiceEffectEnabled() ? dynamic_cast<LLVoiceEffectInterface*>(mVoiceModule) : NULL; + return NULL; } /////////////////// @@ -852,31 +847,52 @@ LLVoiceEffectInterface* LLVoiceClient::getVoiceEffectInterface() const class LLViewerRequiredVoiceVersion : public LLHTTPNode { - static BOOL sAlertedUser; + static bool sAlertedUser; virtual void post( LLHTTPNode::ResponsePtr response, const LLSD& context, const LLSD& input) const { - //You received this messsage (most likely on region cross or - //teleport) - if ( input.has("body") && input["body"].has("major_version") ) + std::string voice_server_type = "vivox"; + if (input.has("body") && input["body"].has("voice_server_type")) { - int major_voice_version = - input["body"]["major_version"].asInteger(); - // int minor_voice_version = - // input["body"]["minor_version"].asInteger(); - LLVoiceVersionInfo versionInfo = LLVoiceClient::getInstance()->getVersion(); + voice_server_type = input["body"]["voice_server_type"].asString(); + } - if (major_voice_version > 1) + LLVoiceModuleInterface *voiceModule = NULL; + + if (voice_server_type == "vivox" || voice_server_type.empty()) + { + voiceModule = (LLVoiceModuleInterface *) LLVivoxVoiceClient::getInstance(); + } + else if (voice_server_type == "webrtc") + { + voiceModule = (LLVoiceModuleInterface *) LLWebRTCVoiceClient::getInstance(); + } + else + { + LL_WARNS("Voice") << "Unknown voice server type " << voice_server_type << LL_ENDL; + if (!sAlertedUser) + { + // sAlertedUser = true; + LLNotificationsUtil::add("VoiceVersionMismatch"); + } + return; + } + + LLVoiceVersionInfo versionInfo = voiceModule->getVersion(); + if (input.has("body") && input["body"].has("major_version") && + input["body"]["major_version"].asInteger() > versionInfo.majorVersion) + { + if (!sAlertedUser) { - if (!sAlertedUser) - { - //sAlertedUser = TRUE; - LLNotificationsUtil::add("VoiceVersionMismatch"); - gSavedSettings.setBOOL("EnableVoiceChat", FALSE); // toggles listener - } + // sAlertedUser = true; + LLNotificationsUtil::add("VoiceVersionMismatch"); + LL_WARNS("Voice") << "Voice server version mismatch " << input["body"]["major_version"].asInteger() << "/" + << versionInfo.majorVersion + << LL_ENDL; } + return; } } }; @@ -910,21 +926,7 @@ class LLViewerParcelVoiceInfo : public LLHTTPNode //we believe we're in if ( body.has("voice_credentials") ) { - LLSD voice_credentials = body["voice_credentials"]; - std::string uri; - std::string credentials; - - if ( voice_credentials.has("channel_uri") ) - { - uri = voice_credentials["channel_uri"].asString(); - } - if ( voice_credentials.has("channel_credentials") ) - { - credentials = - voice_credentials["channel_credentials"].asString(); - } - - LLVoiceClient::getInstance()->setSpatialChannel(uri, credentials); + LLVoiceClient::getInstance()->setSpatialChannel(body["voice_credentials"]); } } } @@ -1087,7 +1089,7 @@ void LLSpeakerVolumeStorage::save() } } -BOOL LLViewerRequiredVoiceVersion::sAlertedUser = FALSE; +bool LLViewerRequiredVoiceVersion::sAlertedUser = false; LLHTTPRegistration<LLViewerParcelVoiceInfo> gHTTPRegistrationMessageParcelVoiceInfo( diff --git a/indra/newview/llvoiceclient.h b/indra/newview/llvoiceclient.h index e0ca739034..d603125759 100644 --- a/indra/newview/llvoiceclient.h +++ b/indra/newview/llvoiceclient.h @@ -34,9 +34,11 @@ class LLVOAvatar; #include "lliosocket.h" #include "v3math.h" #include "llframetimer.h" +#include "llsingleton.h" #include "llcallingcard.h" // for LLFriendObserver #include "llsecapi.h" #include "llcontrol.h" +#include <boost/shared_ptr.hpp> // devices @@ -86,19 +88,54 @@ public: } EStatusType; virtual ~LLVoiceClientStatusObserver() { } - virtual void onChange(EStatusType status, const std::string &channelURI, bool proximal) = 0; + virtual void onChange(EStatusType status, const LLSD& channelInfo, bool proximal) = 0; static std::string status2string(EStatusType inStatus); }; struct LLVoiceVersionInfo { - std::string serverType; + std::string voiceServerType; + std::string internalVoiceServerType; + int majorVersion; + int minorVersion; std::string serverVersion; std::string mBuildVersion; }; ////////////////////////////////// +/// @class LLVoiceP2POutgoingCallInterface +/// @brief Outgoing call interface +/// +/// For providers that support P2P signaling (vivox) +///////////////////////////////// + +class LLVoiceP2POutgoingCallInterface +{ + public: + // initiate an outgoing call to a user + virtual void callUser(const LLUUID &agentID) = 0; + virtual void hangup() = 0; +}; + +////////////////////////////////// +/// @class LLVoiceP2PIncomingCallInterface +/// @brief Incoming call interface +/// +/// For providers that support P2P signaling (vivox) +///////////////////////////////// +class LLVoiceP2PIncomingCallInterface +{ + public: + virtual ~LLVoiceP2PIncomingCallInterface() {} + + virtual bool answerInvite() = 0; + virtual void declineInvite() = 0; +}; + +typedef boost::shared_ptr<LLVoiceP2PIncomingCallInterface> LLVoiceP2PIncomingCallInterfacePtr; + +////////////////////////////////// /// @class LLVoiceModuleInterface /// @brief Voice module interface /// @@ -122,6 +159,8 @@ public: virtual const LLVoiceVersionInfo& getVersion()=0; + + ///////////////////// /// @name Tuning //@{ @@ -165,30 +204,32 @@ public: // Note that gestures should only fire if this returns true. virtual bool inProximalChannel()=0; - virtual void setNonSpatialChannel(const std::string &uri, - const std::string &credentials)=0; + virtual void setNonSpatialChannel(const LLSD& channelInfo, + bool notify_on_first_join, + bool hangup_on_last_leave)=0; - virtual bool setSpatialChannel(const std::string &uri, - const std::string &credentials)=0; + virtual bool setSpatialChannel(const LLSD& channelInfo)=0; - virtual void leaveNonSpatialChannel()=0; + virtual void leaveNonSpatialChannel() = 0; + virtual void processChannels(bool process) = 0; - virtual void leaveChannel(void)=0; + virtual bool isCurrentChannel(const LLSD &channelInfo) = 0; + virtual bool compareChannels(const LLSD &channelInfo1, const LLSD &channelInfo2) = 0; - // Returns the URI of the current channel, or an empty string if not currently in a channel. - // NOTE that it will return an empty string if it's in the process of joining a channel. - virtual std::string getCurrentChannel()=0; //@} ////////////////////////// - /// @name invitations + /// @name p2p //@{ - // start a voice channel with the specified user - virtual void callUser(const LLUUID &uuid)=0; - virtual bool isValidChannel(std::string& channelHandle)=0; - virtual bool answerInvite(std::string &channelHandle)=0; - virtual void declineInvite(std::string &channelHandle)=0; + + // initiate a call with a peer using the P2P interface, which only applies to some + // voice server types. Otherwise, a group call should be used for P2P + virtual LLVoiceP2POutgoingCallInterface* getOutgoingCallInterface() = 0; + + // an incoming call was received, and the incoming call dialogue is asking for an interface to + // answer or decline. + virtual LLVoiceP2PIncomingCallInterfacePtr getIncomingCallInterface(const LLSD &voice_call_info) = 0; //@} ///////////////////////// @@ -201,23 +242,18 @@ public: ///////////////////////// /// @name enable disable voice and features //@{ - virtual bool voiceEnabled()=0; virtual void setVoiceEnabled(bool enabled)=0; - virtual void setLipSyncEnabled(BOOL enabled)=0; - virtual BOOL lipSyncEnabled()=0; virtual void setMuteMic(bool muted)=0; // Set the mute state of the local mic. //@} ////////////////////////// /// @name nearby speaker accessors //@{ - virtual BOOL getVoiceEnabled(const LLUUID& id)=0; // true if we've received data for this avatar virtual std::string getDisplayName(const LLUUID& id)=0; virtual BOOL isParticipantAvatar(const LLUUID &id)=0; virtual BOOL getIsSpeaking(const LLUUID& id)=0; virtual BOOL getIsModeratorMuted(const LLUUID& id)=0; virtual F32 getCurrentPower(const LLUUID& id)=0; // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is... - virtual BOOL getOnMuteList(const LLUUID& id)=0; virtual F32 getUserVolume(const LLUUID& id)=0; virtual void setUserVolume(const LLUUID& id, F32 volume)=0; // set's volume for specified agent, from 0-1 (where .5 is nominal) //@} @@ -228,7 +264,6 @@ public: virtual BOOL isSessionTextIMPossible(const LLUUID& id)=0; virtual BOOL isSessionCallBackPossible(const LLUUID& id)=0; //virtual BOOL sendTextMessage(const LLUUID& participant_id, const std::string& message)=0; - virtual void endUserIMSession(const LLUUID &uuid)=0; //@} // authorize the user @@ -369,24 +404,26 @@ public: // returns true iff the user is currently in a proximal (local spatial) channel. // Note that gestures should only fire if this returns true. bool inProximalChannel(); - void setNonSpatialChannel( - const std::string &uri, - const std::string &credentials); - void setSpatialChannel( - const std::string &uri, - const std::string &credentials); + + void setNonSpatialChannel(const LLSD& channelInfo, + bool notify_on_first_join, + bool hangup_on_last_leave); + + void setSpatialChannel(const LLSD &channelInfo); + + void activateSpatialChannel(bool activate); + void leaveNonSpatialChannel(); - // Returns the URI of the current channel, or an empty string if not currently in a channel. - // NOTE that it will return an empty string if it's in the process of joining a channel. - std::string getCurrentChannel(); - // start a voice channel with the specified user - void callUser(const LLUUID &uuid); - bool isValidChannel(std::string& channelHandle); - bool answerInvite(std::string &channelHandle); - void declineInvite(std::string &channelHandle); - void leaveChannel(void); // call this on logout or teleport begin + bool isCurrentChannel(const LLSD& channelInfo); + + bool compareChannels(const LLSD& channelInfo1, const LLSD& channelInfo2); + + // initiate a call with a peer using the P2P interface, which only applies to some + // voice server types. Otherwise, a group call should be used for P2P + LLVoiceP2POutgoingCallInterface* getOutgoingCallInterface(const LLSD& voiceChannelInfo = LLSD()); + LLVoiceP2PIncomingCallInterfacePtr getIncomingCallInterface(const LLSD &voiceCallInfo); ///////////////////////////// // Sending updates of current state @@ -396,7 +433,6 @@ public: void setMicGain(F32 volume); void setUserVolume(const LLUUID& id, F32 volume); // set's volume for specified agent, from 0-1 (where .5 is nominal) bool voiceEnabled(); - void setLipSyncEnabled(BOOL enabled); void setMuteMic(bool muted); // Use this to mute the local mic (for when the client is minimized, etc), ignoring user PTT state. void setUserPTTState(bool ptt); bool getUserPTTState(); @@ -410,8 +446,6 @@ public: void updateMicMuteLogic(); - BOOL lipSyncEnabled(); - boost::signals2::connection MicroChangedCallback(const micro_changed_signal_t::slot_type& cb ) { return mMicroChangedSignal.connect(cb); } @@ -428,8 +462,6 @@ public: F32 getUserVolume(const LLUUID& id); ///////////////////////////// - BOOL getAreaVoiceDisabled(); // returns true if the area the avatar is in is speech-disabled. - // Use this to determine whether to show a "no speech" icon in the menu bar. void getParticipantList(std::set<LLUUID> &participants); bool isParticipant(const LLUUID& speaker_id); @@ -439,12 +471,15 @@ public: BOOL isSessionTextIMPossible(const LLUUID& id); BOOL isSessionCallBackPossible(const LLUUID& id); //BOOL sendTextMessage(const LLUUID& participant_id, const std::string& message) const {return true;} ; - void endUserIMSession(const LLUUID &uuid); //@} + void setSpatialVoiceModule(const std::string& voice_server_type); + void setNonSpatialVoiceModule(const std::string &voice_server_type); void userAuthorized(const std::string& user_id, - const LLUUID &agentID); + const LLUUID &agentID); + + void onRegionChanged(); void addObserver(LLVoiceClientStatusObserver* observer); void removeObserver(LLVoiceClientStatusObserver* observer); @@ -464,16 +499,29 @@ public: // Returns NULL if voice effects are not supported, or not enabled. LLVoiceEffectInterface* getVoiceEffectInterface() const; //@} -private: + + void handleSimulatorFeaturesReceived(const LLSD &simulatorFeatures); + + private: + void init(LLPumpIO *pump); protected: - LLVoiceModuleInterface* mVoiceModule; + + static bool onVoiceEffectsNotSupported(const LLSD ¬ification, const LLSD &response); + + LLVoiceModuleInterface* mSpatialVoiceModule; + LLVoiceModuleInterface* mNonSpatialVoiceModule; + LLSD mSpatialCredentials; // used to store spatial credentials for vivox + // so they're available when the region voice + // server is retrieved. LLPumpIO *m_servicePump; + boost::signals2::connection mSimulatorFeaturesReceivedSlot; LLCachedControl<bool> mVoiceEffectEnabled; LLCachedControl<std::string> mVoiceEffectDefault; + bool mVoiceEffectSupportNotified; bool mPTTDirty; bool mPTT; diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp index e03bd1a726..3ff0f43c48 100644 --- a/indra/newview/llvoicevivox.cpp +++ b/indra/newview/llvoicevivox.cpp @@ -80,12 +80,14 @@ extern LLMenuBarGL* gMenuBarView; extern void handle_voice_morphing_subscribe(); +const std::string VIVOX_VOICE_SERVER_TYPE = "vivox"; + namespace { const F32 VOLUME_SCALE_VIVOX = 0.01f; const F32 SPEAKING_TIMEOUT = 1.f; - static const std::string VOICE_SERVER_TYPE = "Vivox"; + static const std::string VISIBLE_VOICE_SERVER_TYPE = "Vivox"; // Don't retry connecting to the daemon more frequently than this: const F32 DAEMON_CONNECT_THROTTLE_SECONDS = 1.0f; @@ -293,7 +295,6 @@ LLVivoxVoiceClient::LLVivoxVoiceClient() : mTuningSpeakerVolumeDirty(true), mDevicesListUpdated(false), - mAreaVoiceDisabled(false), mAudioSession(), // TBD - should be NULL mAudioSessionChanged(false), mNextAudioSession(), @@ -325,10 +326,9 @@ LLVivoxVoiceClient::LLVivoxVoiceClient() : mMicVolumeDirty(true), mVoiceEnabled(false), + mProcessChannels(false), mWriteInProgress(false), - mLipSyncEnabled(false), - mVoiceFontsReceived(false), mVoiceFontsNew(false), mVoiceFontListDirty(false), @@ -358,7 +358,12 @@ LLVivoxVoiceClient::LLVivoxVoiceClient() : mSpeakerVolume = scale_speaker_volume(0); mVoiceVersion.serverVersion = ""; - mVoiceVersion.serverType = VOICE_SERVER_TYPE; + mVoiceVersion.voiceServerType = VISIBLE_VOICE_SERVER_TYPE; + mVoiceVersion.internalVoiceServerType = VIVOX_VOICE_SERVER_TYPE; + mVoiceVersion.majorVersion = 1; + mVoiceVersion.minorVersion = 0; + mVoiceVersion.mBuildVersion = ""; + mVoiceVersion.serverVersion = ""; // gMuteListp isn't set up at this point, so we defer this until later. // gMuteListp->addObserver(&mutelist_listener); @@ -455,7 +460,7 @@ const LLVoiceVersionInfo& LLVivoxVoiceClient::getVersion() void LLVivoxVoiceClient::updateSettings() { - setVoiceEnabled(voiceEnabled()); + setVoiceEnabled(LLVoiceClient::getInstance()->voiceEnabled()); setEarLocation(gSavedSettings.getS32("VoiceEarLocation")); std::string inputDevice = gSavedSettings.getString("VoiceInputAudioDevice"); @@ -464,7 +469,6 @@ void LLVivoxVoiceClient::updateSettings() setRenderDevice(outputDevice); F32 mic_level = gSavedSettings.getF32("AudioLevelMic"); setMicGain(mic_level); - setLipSyncEnabled(gSavedSettings.getBOOL("LipSyncEnabled")); } ///////////////////////////// @@ -914,7 +918,7 @@ bool LLVivoxVoiceClient::callbackEndDaemon(const LLSD& data) bool LLVivoxVoiceClient::startAndLaunchDaemon() { //--------------------------------------------------------------------- - if (!voiceEnabled()) + if (!LLVoiceClient::getInstance()->voiceEnabled()) { // Voice is locked out, we must not launch the vivox daemon. LL_WARNS("Voice") << "voice disabled; not starting daemon" << LL_ENDL; @@ -1174,7 +1178,9 @@ bool LLVivoxVoiceClient::provisionVoiceAccount() do { LLVoiceVivoxStats::getInstance()->provisionAttemptStart(); - result = httpAdapter->postAndSuspend(httpRequest, url, LLSD(), httpOpts); + LLSD body; + body["voice_server_type"] = "vivox"; + result = httpAdapter->postAndSuspend(httpRequest, url, body, httpOpts); if (sShuttingDown) { @@ -1218,7 +1224,7 @@ bool LLVivoxVoiceClient::provisionVoiceAccount() LL_WARNS("Voice") << "Could not access voice provision cap after " << retryCount << " attempts." << LL_ENDL; return false; } - + LL_DEBUGS("Voice") << "Voice Provision Result." << result << LL_ENDL; std::string voiceSipUriHostname; std::string voiceAccountServerUri; std::string voiceUserName = result["username"].asString(); @@ -1614,48 +1620,7 @@ bool LLVivoxVoiceClient::requestParcelVoiceInfo() return false; } - std::string uri; - std::string credentials; - - if (result.has("voice_credentials")) - { - LLSD voice_credentials = result["voice_credentials"]; - if (voice_credentials.has("channel_uri")) - { - LL_DEBUGS("Voice") << "got voice channel uri" << LL_ENDL; - uri = voice_credentials["channel_uri"].asString(); - } - else - { - LL_WARNS("Voice") << "No voice channel uri" << LL_ENDL; - } - - if (voice_credentials.has("channel_credentials")) - { - LL_DEBUGS("Voice") << "got voice channel credentials" << LL_ENDL; - credentials = - voice_credentials["channel_credentials"].asString(); - } - else - { - LLVoiceChannel* channel = LLVoiceChannel::getCurrentVoiceChannel(); - if (channel != NULL) - { - if (channel->getSessionName().empty() && channel->getSessionID().isNull()) - { - if (LLViewerParcelMgr::getInstance()->allowAgentVoice()) - { - LL_WARNS("Voice") << "No channel credentials for default channel" << LL_ENDL; - } - } - else - { - LL_WARNS("Voice") << "No voice channel credentials" << LL_ENDL; - } - } - } - } - else + if (!result.has("voice_credentials")) { if (LLViewerParcelMgr::getInstance()->allowAgentVoice()) { @@ -1669,7 +1634,7 @@ bool LLVivoxVoiceClient::requestParcelVoiceInfo() // set the spatial channel. If no voice credentials or uri are // available, then we simply drop out of voice spatially. - return !setSpatialChannel(uri, credentials); + return setSpatialChannel(result["voice_credentials"]); } bool LLVivoxVoiceClient::addAndJoinSession(const sessionStatePtr_t &nextSession) @@ -1808,7 +1773,7 @@ bool LLVivoxVoiceClient::addAndJoinSession(const sessionStatePtr_t &nextSession) } else if ((message == "failed") || (message == "removed") || (message == "timeout")) { // we will get a removed message if a voice call is declined. - + LL_INFOS("Voice") << "Result:" << result << LL_ENDL; if (message == "failed") { int reason = result["reason"].asInteger(); @@ -2032,7 +1997,6 @@ bool LLVivoxVoiceClient::waitForChannel() break; case VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING: - mIsProcessingChannels = true; llcoro::suspend(); state = VOICE_CHANNEL_STATE_PROCESS_CHANNEL; break; @@ -2046,7 +2010,7 @@ bool LLVivoxVoiceClient::waitForChannel() { recordingAndPlaybackMode(); } - else if (checkParcelChanged() || (mNextAudioSession == NULL)) + else if (mProcessChannels && (mNextAudioSession == NULL) && checkParcelChanged()) { // the parcel is changed, or we have no pending audio sessions, // so try to request the parcel voice info @@ -2061,15 +2025,23 @@ bool LLVivoxVoiceClient::waitForChannel() } else if (mNextAudioSession) { + if (!mNextAudioSession->mIsP2P && !mProcessChannels) + { + llcoro::suspend(); + break; + } sessionStatePtr_t joinSession = mNextAudioSession; mNextAudioSession.reset(); + mIsProcessingChannels = true; if (!runSession(joinSession)) //suspends { + mIsProcessingChannels = false; LL_DEBUGS("Voice") << "runSession returned false; leaving inner loop" << LL_ENDL; break; } else { + mIsProcessingChannels = false; LL_DEBUGS("Voice") << "runSession returned true to inner loop" << " RelogRequested=" << mRelogRequested @@ -2155,13 +2127,13 @@ bool LLVivoxVoiceClient::runSession(const sessionStatePtr_t &session) { LL_INFOS("Voice") << "running new voice session " << session->mHandle << LL_ENDL; - bool joined_session = addAndJoinSession(session); - - if (sShuttingDown) + if (sShuttingDown || !mProcessChannels) { return false; } + bool joined_session = addAndJoinSession(session); + if (!joined_session) { notifyStatusObservers(LLVoiceClientStatusObserver::ERROR_UNKNOWN); @@ -2185,12 +2157,14 @@ bool LLVivoxVoiceClient::runSession(const sessionStatePtr_t &session) mIsInChannel = true; mMuteMicDirty = true; + mSessionTerminateRequested = false; while (!sShuttingDown && mVoiceEnabled && isGatewayRunning() && !mSessionTerminateRequested - && !mTuningMode) + && !mTuningMode + && mProcessChannels) { sendCaptureAndRenderDevices(); // suspends @@ -2442,95 +2416,11 @@ int LLVivoxVoiceClient::voicePlaybackBuffer() bool LLVivoxVoiceClient::performMicTuning() { LL_INFOS("Voice") << "Entering voice tuning mode." << LL_ENDL; - mIsInTuningMode = true; - llcoro::suspend(); while (mTuningMode && !sShuttingDown) { - - if (mCaptureDeviceDirty || mRenderDeviceDirty) - { - // These can't be changed while in tuning mode. Set them before starting. - std::ostringstream stream; - - buildSetCaptureDevice(stream); - buildSetRenderDevice(stream); - - if (!stream.str().empty()) - { - writeString(stream.str()); - } - - llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); - } - - // loop mic back to render device. - //setMuteMic(0); // make sure the mic is not muted - std::ostringstream stream; - - stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalMic.1\">" - << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>" - << "<Value>false</Value>" - << "</Request>\n\n\n"; - - // Dirty the mute mic state so that it will get reset when we finishing previewing - mMuteMicDirty = true; - mTuningSpeakerVolumeDirty = true; - - writeString(stream.str()); - tuningCaptureStartSendMessage(1); // 1-loop, zero, don't loop - - //--------------------------------------------------------------------- - if (!sShuttingDown) - { - llcoro::suspend(); - } - - while (mTuningMode && !mCaptureDeviceDirty && !mRenderDeviceDirty && !sShuttingDown) - { - // process mic/speaker volume changes - if (mTuningMicVolumeDirty || mTuningSpeakerVolumeDirty) - { - std::ostringstream stream; - - if (mTuningMicVolumeDirty) - { - LL_INFOS("Voice") << "setting tuning mic level to " << mTuningMicVolume << LL_ENDL; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetMicLevel.1\">" - << "<Level>" << mTuningMicVolume << "</Level>" - << "</Request>\n\n\n"; - } - - if (mTuningSpeakerVolumeDirty) - { - LL_INFOS("Voice") << "setting tuning speaker level to " << mTuningSpeakerVolume << LL_ENDL; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetSpeakerLevel.1\">" - << "<Level>" << mTuningSpeakerVolume << "</Level>" - << "</Request>\n\n\n"; - } - - mTuningMicVolumeDirty = false; - mTuningSpeakerVolumeDirty = false; - - if (!stream.str().empty()) - { - writeString(stream.str()); - } - } - llcoro::suspend(); - } - - //--------------------------------------------------------------------- - - // transition out of mic tuning - tuningCaptureStopSendMessage(); - if ((mCaptureDeviceDirty || mRenderDeviceDirty) && !sShuttingDown) - { - llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); - } + llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); } mIsInTuningMode = false; @@ -2656,6 +2546,7 @@ void LLVivoxVoiceClient::sessionCreateSendMessage(const sessionStatePtr_t &sessi << "<VoiceFontID>" << font_index << "</VoiceFontID>" << "<Name>" << mChannelName << "</Name>" << "</Request>\n\n\n"; + LL_WARNS("Voice") << "Session.Create: " << stream.str() << LL_ENDL; writeString(stream.str()); } @@ -2955,6 +2846,9 @@ void LLVivoxVoiceClient::tuningStart() void LLVivoxVoiceClient::tuningStop() { mTuningMode = false; + // force a renegotiation. + mCurrentParcelLocalID = 0; + mCurrentRegionName = ""; } bool LLVivoxVoiceClient::inTuningMode() @@ -4835,7 +4729,7 @@ void LLVivoxVoiceClient::sessionState::VerifySessions() void LLVivoxVoiceClient::getParticipantList(std::set<LLUUID> &participants) { - if(mAudioSession) + if(mProcessChannels && mAudioSession) { for(participantUUIDMap::iterator iter = mAudioSession->mParticipantsByUUID.begin(); iter != mAudioSession->mParticipantsByUUID.end(); @@ -4848,11 +4742,11 @@ void LLVivoxVoiceClient::getParticipantList(std::set<LLUUID> &participants) bool LLVivoxVoiceClient::isParticipant(const LLUUID &speaker_id) { - if(mAudioSession) + if(mProcessChannels && mAudioSession) { - return (mAudioSession->mParticipantsByUUID.find(speaker_id) != mAudioSession->mParticipantsByUUID.end()); + return (mAudioSession->mParticipantsByUUID.find(speaker_id) != mAudioSession->mParticipantsByUUID.end()); } - return false; + return false; } @@ -4956,8 +4850,10 @@ bool LLVivoxVoiceClient::switchChannel( // If a terminate has been requested, we need to compare against where the URI we're already headed to. if(mNextAudioSession) { - if(mNextAudioSession->mSIPURI != uri) + if (mNextAudioSession->mSIPURI != uri) + { needsSwitch = true; + } } else { @@ -5047,22 +4943,23 @@ void LLVivoxVoiceClient::joinSession(const sessionStatePtr_t &session) } } -void LLVivoxVoiceClient::setNonSpatialChannel( - const std::string &uri, - const std::string &credentials) +void LLVivoxVoiceClient::setNonSpatialChannel(const LLSD& channelInfo, bool notify_on_first_join, bool hangup_on_last_leave) { - switchChannel(uri, false, false, false, credentials); + switchChannel(channelInfo["channel_uri"].asString(), false, false, false, channelInfo["channel_credentials"].asString()); } -bool LLVivoxVoiceClient::setSpatialChannel( - const std::string &uri, - const std::string &credentials) +bool LLVivoxVoiceClient::setSpatialChannel(const LLSD& channelInfo) { - mSpatialSessionURI = uri; - mSpatialSessionCredentials = credentials; - mAreaVoiceDisabled = mSpatialSessionURI.empty(); + mSpatialSessionURI = channelInfo["channel_uri"].asString(); + mSpatialSessionCredentials = channelInfo["channel_credentials"].asString(); + if (!mProcessChannels) + { + // we're not even processing channels (another provider is) so + // save the credentials aside and exit + return false; + } - LL_DEBUGS("Voice") << "got spatial channel uri: \"" << uri << "\"" << LL_ENDL; + LL_DEBUGS("Voice") << "got spatial channel uri: \"" << mSpatialSessionURI << "\"" << LL_ENDL; if((mIsInChannel && mAudioSession && !(mAudioSession->mIsSpatial)) || (mNextAudioSession && !(mNextAudioSession->mIsSpatial))) { @@ -5079,84 +4976,30 @@ bool LLVivoxVoiceClient::setSpatialChannel( void LLVivoxVoiceClient::callUser(const LLUUID &uuid) { std::string userURI = sipURIFromID(uuid); + mProcessChannels = true; switchChannel(userURI, false, true, true); } -#if 0 -// Vivox text IMs are not in use. -LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::startUserIMSession(const LLUUID &uuid) -{ - // Figure out if a session with the user already exists - sessionStatePtr_t session(findSession(uuid)); - if(!session) - { - // No session with user, need to start one. - std::string uri = sipURIFromID(uuid); - session = addSession(uri); +void LLVivoxVoiceClient::hangup() { leaveChannel(); } - llassert(session); - if (!session) - return session; - - session->mIsSpatial = false; - session->mReconnect = false; - session->mIsP2P = true; - session->mCallerID = uuid; - } - - if(session->mHandle.empty()) - { - // Session isn't active -- start it up. - sessionCreateSendMessage(session, false, false); - } - else - { - // Session is already active -- start up text. - sessionTextConnectSendMessage(session); - } - - return session; -} -#endif -void LLVivoxVoiceClient::endUserIMSession(const LLUUID &uuid) +LLVoiceP2PIncomingCallInterfacePtr LLVivoxVoiceClient::getIncomingCallInterface(const LLSD &voice_call_info) { -#if 0 - // Vivox text IMs are not in use. - - // Figure out if a session with the user exists - sessionStatePtr_t session(findSession(uuid)); - if(session) - { - // found the session - if(!session->mHandle.empty()) - { - // sessionTextDisconnectSendMessage(session); // a SLim leftover, not used any more. - } - } - else - { - LL_DEBUGS("Voice") << "Session not found for participant ID " << uuid << LL_ENDL; - } -#endif + return boost::make_shared<LLVivoxVoiceP2PIncomingCall>(voice_call_info); } -bool LLVivoxVoiceClient::isValidChannel(std::string &sessionHandle) -{ - return(findSession(sessionHandle) != NULL); -} -bool LLVivoxVoiceClient::answerInvite(std::string &sessionHandle) +bool LLVivoxVoiceClient::answerInvite(const std::string &sessionHandle) { // this is only ever used to answer incoming p2p call invites. sessionStatePtr_t session(findSession(sessionHandle)); - if(session) + if (session) { session->mIsSpatial = false; session->mReconnect = false; - session->mIsP2P = true; - + session->mIsP2P = true; + mProcessChannels = true; joinSession(session); return true; } @@ -5164,6 +5007,15 @@ bool LLVivoxVoiceClient::answerInvite(std::string &sessionHandle) return false; } +void LLVivoxVoiceClient::declineInvite(const std::string &sessionHandle) +{ + sessionStatePtr_t session(findSession(sessionHandle)); + if (session) + { + sessionMediaDisconnectSendMessage(session); + } +} + bool LLVivoxVoiceClient::isVoiceWorking() const { @@ -5233,16 +5085,6 @@ BOOL LLVivoxVoiceClient::isSessionTextIMPossible(const LLUUID &session_id) return result; } - -void LLVivoxVoiceClient::declineInvite(std::string &sessionHandle) -{ - sessionStatePtr_t session(findSession(sessionHandle)); - if(session) - { - sessionMediaDisconnectSendMessage(session); - } -} - void LLVivoxVoiceClient::leaveNonSpatialChannel() { LL_DEBUGS("Voice") << "Request to leave spacial channel." << LL_ENDL; @@ -5259,16 +5101,41 @@ void LLVivoxVoiceClient::leaveNonSpatialChannel() sessionTerminate(); } -std::string LLVivoxVoiceClient::getCurrentChannel() +void LLVivoxVoiceClient::processChannels(bool process) { - std::string result; + mCurrentParcelLocalID = -1; + mCurrentRegionName.clear(); + mProcessChannels = process; +} - if (mIsInChannel && !mSessionTerminateRequested) +bool LLVivoxVoiceClient::isCurrentChannel(const LLSD &channelInfo) +{ + if (!mProcessChannels || (channelInfo.has("voice_server_type") && channelInfo["voice_server_type"].asString() != VIVOX_VOICE_SERVER_TYPE)) { - result = getAudioSessionURI(); + return false; } + // favor the next audio session, as that's the one we're bringing up. + sessionStatePtr_t session = mNextAudioSession; + if (!session) + { + session = mAudioSession; + } + if (session) + { + if (!channelInfo["session_handle"].asString().empty()) + { + return session->mHandle == channelInfo["session_handle"].asString(); + } + return channelInfo["channel_uri"].asString() == session->mSIPURI; + } + return false; +} - return result; +bool LLVivoxVoiceClient::compareChannels(const LLSD& channelInfo1, const LLSD& channelInfo2) +{ + return (!channelInfo1.has("voice_server_type") || (channelInfo1["voice_server_type"] == VIVOX_VOICE_SERVER_TYPE)) && + (!channelInfo2.has("voice_server_type") || (channelInfo2["voice_server_type"] == VIVOX_VOICE_SERVER_TYPE)) && + (channelInfo1["channel_uri"] == channelInfo2["channel_uri"]); } bool LLVivoxVoiceClient::inProximalChannel() @@ -5308,16 +5175,6 @@ std::string LLVivoxVoiceClient::sipURIFromAvatar(LLVOAvatar *avatar) return result; } -std::string LLVivoxVoiceClient::nameFromAvatar(LLVOAvatar *avatar) -{ - std::string result; - if(avatar) - { - result = nameFromID(avatar->getID()); - } - return result; -} - std::string LLVivoxVoiceClient::nameFromID(const LLUUID &uuid) { std::string result; @@ -5392,11 +5249,6 @@ bool LLVivoxVoiceClient::IDFromName(const std::string inName, LLUUID &uuid) return result; } -std::string LLVivoxVoiceClient::displayNameFromAvatar(LLVOAvatar *avatar) -{ - return avatar->getFullname(); -} - std::string LLVivoxVoiceClient::sipURIFromName(std::string &name) { std::string result; @@ -5437,12 +5289,15 @@ bool LLVivoxVoiceClient::inSpatialChannel(void) return result; } -std::string LLVivoxVoiceClient::getAudioSessionURI() + +LLSD LLVivoxVoiceClient::getAudioSessionChannelInfo() { - std::string result; + LLSD result; - if(mAudioSession) - result = mAudioSession->mSIPURI; + if (mAudioSession) + { + result = mAudioSession->getVoiceChannelInfo(); + } return result; } @@ -5634,6 +5489,8 @@ void LLVivoxVoiceClient::setVoiceEnabled(bool enabled) LLVoiceChannel::getCurrentVoiceChannel()->deactivate(); gAgent.setVoiceConnected(false); status = LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED; + mCurrentParcelLocalID = -1; + mCurrentRegionName.clear(); } notifyStatusObservers(status); @@ -5644,31 +5501,6 @@ void LLVivoxVoiceClient::setVoiceEnabled(bool enabled) } } -bool LLVivoxVoiceClient::voiceEnabled() -{ - return gSavedSettings.getBOOL("EnableVoiceChat") && - !gSavedSettings.getBOOL("CmdLineDisableVoice") && - !gNonInteractive; -} - -void LLVivoxVoiceClient::setLipSyncEnabled(BOOL enabled) -{ - mLipSyncEnabled = enabled; -} - -BOOL LLVivoxVoiceClient::lipSyncEnabled() -{ - - if ( mVoiceEnabled ) - { - return mLipSyncEnabled; - } - else - { - return FALSE; - } -} - void LLVivoxVoiceClient::setEarLocation(S32 loc) { @@ -5711,27 +5543,17 @@ void LLVivoxVoiceClient::setMicGain(F32 volume) ///////////////////////////// // Accessors for data related to nearby speakers -BOOL LLVivoxVoiceClient::getVoiceEnabled(const LLUUID& id) -{ - BOOL result = FALSE; - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - // I'm not sure what the semantics of this should be. - // For now, if we have any data about the user that came through the chat channel, assume they're voice-enabled. - result = TRUE; - } - - return result; -} std::string LLVivoxVoiceClient::getDisplayName(const LLUUID& id) { std::string result; - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) + if (mProcessChannels) { - result = participant->mDisplayName; + participantStatePtr_t participant(findParticipantByID(id)); + if (participant) + { + result = participant->mDisplayName; + } } return result; @@ -5742,15 +5564,17 @@ std::string LLVivoxVoiceClient::getDisplayName(const LLUUID& id) BOOL LLVivoxVoiceClient::getIsSpeaking(const LLUUID& id) { BOOL result = FALSE; - - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) + if (mProcessChannels) { - if (participant->mSpeakingTimeout.getElapsedTimeF32() > SPEAKING_TIMEOUT) + participantStatePtr_t participant(findParticipantByID(id)); + if (participant) { - participant->mIsSpeaking = FALSE; + if (participant->mSpeakingTimeout.getElapsedTimeF32() > SPEAKING_TIMEOUT) + { + participant->mIsSpeaking = FALSE; + } + result = participant->mIsSpeaking; } - result = participant->mIsSpeaking; } return result; @@ -5759,7 +5583,10 @@ BOOL LLVivoxVoiceClient::getIsSpeaking(const LLUUID& id) BOOL LLVivoxVoiceClient::getIsModeratorMuted(const LLUUID& id) { BOOL result = FALSE; - + if (!mProcessChannels) + { + return FALSE; + } participantStatePtr_t participant(findParticipantByID(id)); if(participant) { @@ -5798,19 +5625,6 @@ BOOL LLVivoxVoiceClient::getUsingPTT(const LLUUID& id) return result; } -BOOL LLVivoxVoiceClient::getOnMuteList(const LLUUID& id) -{ - BOOL result = FALSE; - - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - result = participant->mOnMuteList; - } - - return result; -} - // External accessors. F32 LLVivoxVoiceClient::getUserVolume(const LLUUID& id) { @@ -5868,11 +5682,6 @@ std::string LLVivoxVoiceClient::getGroupID(const LLUUID& id) return result; } -BOOL LLVivoxVoiceClient::getAreaVoiceDisabled() -{ - return mAreaVoiceDisabled; -} - void LLVivoxVoiceClient::recordingLoopStart(int seconds, int deltaFramesPerControlFrame) { // LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Start)" << LL_ENDL; @@ -5999,6 +5808,18 @@ LLVivoxVoiceClient::sessionState::sessionState() : { } +LLSD LLVivoxVoiceClient::sessionState::getVoiceChannelInfo() +{ + LLSD result; + + result["voice_server_type"] = VIVOX_VOICE_SERVER_TYPE; + result["channel_credentials"] = mHash; + result["channel_uri"] = mSIPURI; + result["session_handle"] = mHandle; + + return result; +} + /*static*/ LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::createSession() { @@ -6445,16 +6266,22 @@ void LLVivoxVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::ESta LL_DEBUGS("Voice") << " " << LLVoiceClientStatusObserver::status2string(status) - << ", session URI " << getAudioSessionURI() + << ", session channelInfo " << getAudioSessionChannelInfo() << ", proximal is " << inSpatialChannel() << LL_ENDL; + if (!mProcessChannels) + { + // we're not processing...another voice module is. + // so nobody wants to hear from us. + return; + } for (status_observer_set_t::iterator it = mStatusObservers.begin(); it != mStatusObservers.end(); ) { LLVoiceClientStatusObserver* observer = *it; - observer->onChange(status, getAudioSessionURI(), inSpatialChannel()); + observer->onChange(status, getAudioSessionChannelInfo(), inSpatialChannel()); // In case onError() deleted an entry. it = mStatusObservers.upper_bound(observer); } @@ -6466,6 +6293,7 @@ void LLVivoxVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::ESta { bool voice_status = LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking(); + LL_WARNS("Voice") << "Setting voice connected " << (voice_status ? "True" : "False") << LL_ENDL; gAgent.setVoiceConnected(voice_status); if (voice_status) @@ -6540,7 +6368,6 @@ void LLVivoxVoiceClient::predAvatarNameResolution(const LLVivoxVoiceClient::sess if (session->mVoiceInvitePending) { session->mVoiceInvitePending = false; - LLIMMgr::getInstance()->inviteToSession( session->mIMSessionID, session->mName, @@ -6548,10 +6375,8 @@ void LLVivoxVoiceClient::predAvatarNameResolution(const LLVivoxVoiceClient::sess session->mName, IM_SESSION_P2P_INVITE, LLIMMgr::INVITATION_TYPE_VOICE, - session->mHandle, - session->mSIPURI); + session->getVoiceChannelInfo()); } - } } @@ -8105,3 +7930,7 @@ LLVivoxSecurity::LLVivoxSecurity() LLVivoxSecurity::~LLVivoxSecurity() { } + +bool LLVivoxVoiceP2PIncomingCall::answerInvite() { return LLVivoxVoiceClient::getInstance()->answerInvite(mCallInfo["session_handle"]); } + +void LLVivoxVoiceP2PIncomingCall::declineInvite() { LLVivoxVoiceClient::getInstance()->declineInvite(mCallInfo["session_handle"]); } diff --git a/indra/newview/llvoicevivox.h b/indra/newview/llvoicevivox.h index 36d3e9d0a1..55c1fb50d0 100644 --- a/indra/newview/llvoicevivox.h +++ b/indra/newview/llvoicevivox.h @@ -51,12 +51,27 @@ class LLVivoxProtocolParser; class LLAvatarName; class LLVivoxVoiceClientMuteListObserver; +extern const std::string VIVOX_VOICE_SERVER_TYPE; + +class LLVivoxVoiceP2PIncomingCall : public LLVoiceP2PIncomingCallInterface +{ + public: + LLVivoxVoiceP2PIncomingCall(const LLSD& call_info) : mCallInfo(call_info) {} + ~LLVivoxVoiceP2PIncomingCall() override {} + + bool answerInvite() override; + void declineInvite() override; + + protected: + LLSD mCallInfo; +}; class LLVivoxVoiceClient : public LLSingleton<LLVivoxVoiceClient>, virtual public LLVoiceModuleInterface, - virtual public LLVoiceEffectInterface + virtual public LLVoiceEffectInterface, + virtual public LLVoiceP2POutgoingCallInterface { - LLSINGLETON(LLVivoxVoiceClient); + LLSINGLETON_C11(LLVivoxVoiceClient); LOG_CLASS(LLVivoxVoiceClient); virtual ~LLVivoxVoiceClient(); @@ -64,26 +79,27 @@ public: /// @name LLVoiceModuleInterface virtual implementations /// @see LLVoiceModuleInterface //@{ - virtual void init(LLPumpIO *pump) override; // Call this once at application startup (creates connector) - virtual void terminate() override; // Call this to clean up during shutdown + void init(LLPumpIO *pump) override; // Call this once at application startup (creates connector) + void terminate() override; // Call this to clean up during shutdown - virtual const LLVoiceVersionInfo& getVersion() override; + const LLVoiceVersionInfo& getVersion() override; - virtual void updateSettings() override; // call after loading settings and whenever they change + void updateSettings() override; // call after loading settings and whenever they change // Returns true if vivox has successfully logged in and is not in error state - virtual bool isVoiceWorking() const override; + bool isVoiceWorking() const override; ///////////////////// /// @name Tuning //@{ - virtual void tuningStart() override; - virtual void tuningStop() override; - virtual bool inTuningMode() override; + void tuningStart() override; + void tuningStop() override; + bool inTuningMode() override; + + void tuningSetMicVolume(float volume) override; + void tuningSetSpeakerVolume(float volume) override; + float tuningGetEnergy(void) override; - virtual void tuningSetMicVolume(float volume) override; - virtual void tuningSetSpeakerVolume(float volume) override; - virtual float tuningGetEnergy(void) override; //@} ///////////////////// @@ -91,122 +107,121 @@ public: //@{ // This returns true when it's safe to bring up the "device settings" dialog in the prefs. // i.e. when the daemon is running and connected, and the device lists are populated. - virtual bool deviceSettingsAvailable() override; - virtual bool deviceSettingsUpdated() override; //return if the list has been updated and never fetched, only to be called from the voicepanel. + bool deviceSettingsAvailable() override; + bool deviceSettingsUpdated() override; //return if the list has been updated and never fetched, only to be called from the voicepanel. // Requery the vivox daemon for the current list of input/output devices. // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed // (use this if you want to know when it's done). // If you pass false, you'll have no way to know when the query finishes, but the device lists will not appear empty in the interim. - virtual void refreshDeviceLists(bool clearCurrentList = true) override; + void refreshDeviceLists(bool clearCurrentList = true) override; - virtual void setCaptureDevice(const std::string& name) override; - virtual void setRenderDevice(const std::string& name) override; + void setCaptureDevice(const std::string& name) override; + void setRenderDevice(const std::string& name) override; - virtual LLVoiceDeviceList& getCaptureDevices() override; - virtual LLVoiceDeviceList& getRenderDevices() override; + LLVoiceDeviceList& getCaptureDevices() override; + LLVoiceDeviceList& getRenderDevices() override; //@} - virtual void getParticipantList(std::set<LLUUID> &participants) override; - virtual bool isParticipant(const LLUUID& speaker_id) override; + void getParticipantList(std::set<LLUUID> &participants) override; + bool isParticipant(const LLUUID& speaker_id) override; // Send a text message to the specified user, initiating the session if necessary. // virtual BOOL sendTextMessage(const LLUUID& participant_id, const std::string& message) const {return false;}; - // close any existing text IM session with the specified user - virtual void endUserIMSession(const LLUUID &uuid) override; - // Returns true if calling back the session URI after the session has closed is possible. // Currently this will be false only for PSTN P2P calls. // NOTE: this will return true if the session can't be found. - virtual BOOL isSessionCallBackPossible(const LLUUID &session_id) override; + BOOL isSessionCallBackPossible(const LLUUID &session_id) override; // Returns true if the session can accepte text IM's. // Currently this will be false only for PSTN P2P calls. // NOTE: this will return true if the session can't be found. - virtual BOOL isSessionTextIMPossible(const LLUUID &session_id) override; - + BOOL isSessionTextIMPossible(const LLUUID &session_id) override; //////////////////////////// /// @name Channel stuff //@{ // returns true iff the user is currently in a proximal (local spatial) channel. // Note that gestures should only fire if this returns true. - virtual bool inProximalChannel() override; + bool inProximalChannel() override; + + void setNonSpatialChannel(const LLSD& channelInfo, + bool notify_on_first_join, + bool hangup_on_last_leave) override; - virtual void setNonSpatialChannel(const std::string &uri, - const std::string &credentials) override; + bool setSpatialChannel(const LLSD& channelInfo) override; - virtual bool setSpatialChannel(const std::string &uri, - const std::string &credentials) override; + void leaveNonSpatialChannel() override; - virtual void leaveNonSpatialChannel() override; + void processChannels(bool process) override; - virtual void leaveChannel(void) override; + void leaveChannel(void); + + bool isCurrentChannel(const LLSD &channelInfo) override; + bool compareChannels(const LLSD &channelInfo1, const LLSD &channelInfo2) override; - // Returns the URI of the current channel, or an empty string if not currently in a channel. - // NOTE that it will return an empty string if it's in the process of joining a channel. - virtual std::string getCurrentChannel() override; //@} ////////////////////////// - /// @name invitations + /// @name LLVoiceP2POutgoingCallInterface //@{ // start a voice channel with the specified user - virtual void callUser(const LLUUID &uuid) override; - virtual bool isValidChannel(std::string &channelHandle) override; - virtual bool answerInvite(std::string &channelHandle) override; - virtual void declineInvite(std::string &channelHandle) override; + void callUser(const LLUUID &uuid) override; + void hangup() override; + //@} + LLVoiceP2POutgoingCallInterface *getOutgoingCallInterface() override { return this; } + + LLVoiceP2PIncomingCallInterfacePtr getIncomingCallInterface(const LLSD &voice_call_info) override; + + bool answerInvite(const std::string &sessionHandle); + void declineInvite(const std::string &sessionHandle); + ///////////////////////// /// @name Volume/gain //@{ - virtual void setVoiceVolume(F32 volume) override; - virtual void setMicGain(F32 volume) override; + void setVoiceVolume(F32 volume) override; + void setMicGain(F32 volume) override; //@} ///////////////////////// /// @name enable disable voice and features //@{ - virtual bool voiceEnabled() override; - virtual void setVoiceEnabled(bool enabled) override; - virtual BOOL lipSyncEnabled() override; - virtual void setLipSyncEnabled(BOOL enabled) override; - virtual void setMuteMic(bool muted) override; // Set the mute state of the local mic. + void setVoiceEnabled(bool enabled) override; + void setMuteMic(bool muted) override; // Set the mute state of the local mic. //@} ////////////////////////// /// @name nearby speaker accessors //@{ - virtual BOOL getVoiceEnabled(const LLUUID& id) override; // true if we've received data for this avatar - virtual std::string getDisplayName(const LLUUID& id) override; - virtual BOOL isParticipantAvatar(const LLUUID &id) override; - virtual BOOL getIsSpeaking(const LLUUID& id) override; - virtual BOOL getIsModeratorMuted(const LLUUID& id) override; - virtual F32 getCurrentPower(const LLUUID& id) override; // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is... - virtual BOOL getOnMuteList(const LLUUID& id) override; - virtual F32 getUserVolume(const LLUUID& id) override; - virtual void setUserVolume(const LLUUID& id, F32 volume) override; // set's volume for specified agent, from 0-1 (where .5 is nominal) + std::string getDisplayName(const LLUUID& id) override; + BOOL isParticipantAvatar(const LLUUID &id) override; + BOOL getIsSpeaking(const LLUUID& id) override; + BOOL getIsModeratorMuted(const LLUUID& id) override; + F32 getCurrentPower(const LLUUID& id) override; // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is... + F32 getUserVolume(const LLUUID& id) override; + void setUserVolume(const LLUUID& id, F32 volume) override; // set's volume for specified agent, from 0-1 (where .5 is nominal) //@} // authorize the user - virtual void userAuthorized(const std::string& user_id, - const LLUUID &agentID) override; + void userAuthorized(const std::string& user_id, + const LLUUID &agentID) override; ////////////////////////////// /// @name Status notification //@{ - virtual void addObserver(LLVoiceClientStatusObserver* observer) override; - virtual void removeObserver(LLVoiceClientStatusObserver* observer) override; - virtual void addObserver(LLFriendObserver* observer) override; - virtual void removeObserver(LLFriendObserver* observer) override; - virtual void addObserver(LLVoiceClientParticipantObserver* observer) override; - virtual void removeObserver(LLVoiceClientParticipantObserver* observer) override; + void addObserver(LLVoiceClientStatusObserver* observer) override; + void removeObserver(LLVoiceClientStatusObserver* observer) override; + void addObserver(LLFriendObserver* observer) override; + void removeObserver(LLFriendObserver* observer) override; + void addObserver(LLVoiceClientParticipantObserver* observer) override; + void removeObserver(LLVoiceClientParticipantObserver* observer) override; //@} - virtual std::string sipURIFromID(const LLUUID &id) override; + std::string sipURIFromID(const LLUUID &id) override; //@} /// @name LLVoiceEffectInterface virtual implementations @@ -216,32 +231,32 @@ public: ////////////////////////// /// @name Accessors //@{ - virtual bool setVoiceEffect(const LLUUID& id) override; - virtual const LLUUID getVoiceEffect() override; - virtual LLSD getVoiceEffectProperties(const LLUUID& id) override; + bool setVoiceEffect(const LLUUID& id) override; + const LLUUID getVoiceEffect() override; + LLSD getVoiceEffectProperties(const LLUUID& id) override; - virtual void refreshVoiceEffectLists(bool clear_lists) override; - virtual const voice_effect_list_t& getVoiceEffectList() const override; - virtual const voice_effect_list_t& getVoiceEffectTemplateList() const override; + void refreshVoiceEffectLists(bool clear_lists) override; + const voice_effect_list_t& getVoiceEffectList() const override; + const voice_effect_list_t& getVoiceEffectTemplateList() const override; //@} ////////////////////////////// /// @name Status notification //@{ - virtual void addObserver(LLVoiceEffectObserver* observer) override; - virtual void removeObserver(LLVoiceEffectObserver* observer) override; + void addObserver(LLVoiceEffectObserver* observer) override; + void removeObserver(LLVoiceEffectObserver* observer) override; //@} ////////////////////////////// /// @name Effect preview buffer //@{ - virtual void enablePreviewBuffer(bool enable) override; - virtual void recordPreviewBuffer() override; - virtual void playPreviewBuffer(const LLUUID& effect_id = LLUUID::null) override; - virtual void stopPreviewBuffer() override; + void enablePreviewBuffer(bool enable) override; + void recordPreviewBuffer() override; + void playPreviewBuffer(const LLUUID& effect_id = LLUUID::null) override; + void stopPreviewBuffer() override; - virtual bool isPreviewRecording() override; - virtual bool isPreviewPlaying() override; + bool isPreviewRecording() override; + bool isPreviewPlaying() override; //@} //@} @@ -311,6 +326,8 @@ protected: static ptr_t createSession(); ~sessionState(); + LLSD getVoiceChannelInfo(); + participantStatePtr_t addParticipant(const std::string &uri); void removeParticipant(const participantStatePtr_t &participant); void removeAllParticipants(); @@ -325,6 +342,7 @@ protected: bool isCallBackPossible(); bool isTextIMPossible(); + bool isSpatial() { return mIsSpatial; } static void for_each(sessionFunc_t func); @@ -494,11 +512,6 @@ protected: std::string getGroupID(const LLUUID& id); // group ID if the user is in group chat (empty string if not applicable) ///////////////////////////// - BOOL getAreaVoiceDisabled(); // returns true if the area the avatar is in is speech-disabled. - // Use this to determine whether to show a "no speech" icon in the menu bar. - - - ///////////////////////////// // Recording controls void recordingLoopStart(int seconds = 3600, int deltaFramesPerControlFrame = 200); void recordingLoopSave(const std::string& filename); @@ -692,7 +705,6 @@ private: std::string mMainSessionGroupHandle; // handle of the "main" session group. std::string mChannelName; // Name of the channel to be looked up - bool mAreaVoiceDisabled; sessionStatePtr_t mAudioSession; // Session state for the current audio session bool mAudioSessionChanged; // set to true when the above pointer gets changed, so observers can be notified. @@ -736,10 +748,8 @@ private: bool switchChannel(std::string uri = std::string(), bool spatial = true, bool no_reconnect = false, bool is_p2p = false, std::string hash = ""); void joinSession(const sessionStatePtr_t &session); - std::string nameFromAvatar(LLVOAvatar *avatar); std::string nameFromID(const LLUUID &id); bool IDFromName(const std::string name, LLUUID &uuid); - std::string displayNameFromAvatar(LLVOAvatar *avatar); std::string sipURIFromAvatar(LLVOAvatar *avatar); std::string sipURIFromName(std::string &name); @@ -747,7 +757,7 @@ private: std::string nameFromsipURI(const std::string &uri); bool inSpatialChannel(void); - std::string getAudioSessionURI(); + LLSD getAudioSessionChannelInfo(); std::string getAudioSessionHandle(); void setHidden(bool hidden) override; //virtual @@ -803,12 +813,11 @@ private: bool mMicVolumeDirty; bool mVoiceEnabled; + bool mProcessChannels; bool mWriteInProgress; std::string mWriteString; size_t mWriteOffset; - BOOL mLipSyncEnabled; - typedef std::set<LLVoiceClientParticipantObserver*> observer_set_t; observer_set_t mParticipantObservers; diff --git a/indra/newview/llvoicewebrtc.cpp b/indra/newview/llvoicewebrtc.cpp new file mode 100644 index 0000000000..f3ab52736e --- /dev/null +++ b/indra/newview/llvoicewebrtc.cpp @@ -0,0 +1,3081 @@ + /** + * @file llvoicewebrtc.cpp + * @brief Implementation of LLWebRTCVoiceClient class which is the interface to the voice client process. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#include <algorithm> +#if !LL_LINUX +#include <format> +#endif +#include "llvoicewebrtc.h" + +#include "llsdutil.h" + +// Linden library includes +#include "llavatarnamecache.h" +#include "llvoavatarself.h" +#include "llbufferstream.h" +#include "llfile.h" +#include "llmenugl.h" +#ifdef LL_USESYSTEMLIBS +# include "expat.h" +#else +# include "expat/expat.h" +#endif +#include "llcallbacklist.h" +#include "llviewernetwork.h" // for gGridChoice +#include "llbase64.h" +#include "llviewercontrol.h" +#include "llappviewer.h" // for gDisconnected, gDisableVoice +#include "llprocess.h" + +// Viewer includes +#include "llmutelist.h" // to check for muted avatars +#include "llagent.h" +#include "llcachename.h" +#include "llimview.h" // for LLIMMgr +#include "llworld.h" +#include "llparcel.h" +#include "llviewerparcelmgr.h" +#include "llfirstuse.h" +#include "llspeakers.h" +#include "lltrans.h" +#include "llrand.h" +#include "llviewerwindow.h" +#include "llviewercamera.h" +#include "llversioninfo.h" + +#include "llviewernetwork.h" +#include "llnotificationsutil.h" + +#include "llcorehttputil.h" +#include "lleventfilter.h" + +#include "stringize.h" + +#include "llwebrtc.h" + +// for base64 decoding +#include "apr_base64.h" + +#include "json/reader.h" +#include "json/writer.h" + +const std::string WEBRTC_VOICE_SERVER_TYPE = "webrtc"; + +namespace { + + const F32 MAX_AUDIO_DIST = 50.0f; + const F32 VOLUME_SCALE_WEBRTC = 0.01f; + const F32 LEVEL_SCALE_WEBRTC = 0.008f; + + const F32 SPEAKING_AUDIO_LEVEL = 0.30; + + static const std::string REPORTED_VOICE_SERVER_TYPE = "Secondlife WebRTC Gateway"; + + // Don't send positional updates more frequently than this: + const F32 UPDATE_THROTTLE_SECONDS = 0.1f; + const F32 MAX_RETRY_WAIT_SECONDS = 10.0f; + + // Cosine of a "trivially" small angle + const F32 FOUR_DEGREES = 4.0f * (F_PI / 180.0f); + const F32 MINUSCULE_ANGLE_COS = (F32) cos(0.5f * FOUR_DEGREES); + +} // namespace + + +/////////////////////////////////////////////////////////////////////////////////////////////// + +void LLVoiceWebRTCStats::reset() +{ + mStartTime = -1.0f; + mConnectCycles = 0; + mConnectTime = -1.0f; + mConnectAttempts = 0; + mProvisionTime = -1.0f; + mProvisionAttempts = 0; + mEstablishTime = -1.0f; + mEstablishAttempts = 0; +} + +LLVoiceWebRTCStats::LLVoiceWebRTCStats() +{ + reset(); +} + +LLVoiceWebRTCStats::~LLVoiceWebRTCStats() +{ +} + +void LLVoiceWebRTCStats::connectionAttemptStart() +{ + if (!mConnectAttempts) + { + mStartTime = LLTimer::getTotalTime(); + mConnectCycles++; + } + mConnectAttempts++; +} + +void LLVoiceWebRTCStats::connectionAttemptEnd(bool success) +{ + if ( success ) + { + mConnectTime = (LLTimer::getTotalTime() - mStartTime) / USEC_PER_SEC; + } +} + +void LLVoiceWebRTCStats::provisionAttemptStart() +{ + if (!mProvisionAttempts) + { + mStartTime = LLTimer::getTotalTime(); + } + mProvisionAttempts++; +} + +void LLVoiceWebRTCStats::provisionAttemptEnd(bool success) +{ + if ( success ) + { + mProvisionTime = (LLTimer::getTotalTime() - mStartTime) / USEC_PER_SEC; + } +} + +void LLVoiceWebRTCStats::establishAttemptStart() +{ + if (!mEstablishAttempts) + { + mStartTime = LLTimer::getTotalTime(); + } + mEstablishAttempts++; +} + +void LLVoiceWebRTCStats::establishAttemptEnd(bool success) +{ + if ( success ) + { + mEstablishTime = (LLTimer::getTotalTime() - mStartTime) / USEC_PER_SEC; + } +} + +LLSD LLVoiceWebRTCStats::read() +{ + LLSD stats(LLSD::emptyMap()); + + stats["connect_cycles"] = LLSD::Integer(mConnectCycles); + stats["connect_attempts"] = LLSD::Integer(mConnectAttempts); + stats["connect_time"] = LLSD::Real(mConnectTime); + + stats["provision_attempts"] = LLSD::Integer(mProvisionAttempts); + stats["provision_time"] = LLSD::Real(mProvisionTime); + + stats["establish_attempts"] = LLSD::Integer(mEstablishAttempts); + stats["establish_time"] = LLSD::Real(mEstablishTime); + + return stats; +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +bool LLWebRTCVoiceClient::sShuttingDown = false; + +LLWebRTCVoiceClient::LLWebRTCVoiceClient() : + mHidden(false), + mTuningMode(false), + mTuningMicGain(0.0), + mTuningSpeakerVolume(50), // Set to 50 so the user can hear themselves when he sets his mic volume + mDevicesListUpdated(false), + + mSpatialCoordsDirty(false), + + mMuteMic(false), + + mEarLocation(0), + mMicGain(0.0), + + mVoiceEnabled(false), + mProcessChannels(false), + + mAvatarNameCacheConnection(), + mIsInTuningMode(false), + mIsProcessingChannels(false), + mIsCoroutineActive(false), + mWebRTCPump("WebRTCClientPump"), + mWebRTCDeviceInterface(nullptr) +{ + sShuttingDown = false; + + mSpeakerVolume = 0.0; + + mVoiceVersion.serverVersion = ""; + mVoiceVersion.voiceServerType = REPORTED_VOICE_SERVER_TYPE; + mVoiceVersion.internalVoiceServerType = WEBRTC_VOICE_SERVER_TYPE; + mVoiceVersion.minorVersion = 0; + mVoiceVersion.majorVersion = 2; + mVoiceVersion.mBuildVersion = ""; +} + +//--------------------------------------------------- + +LLWebRTCVoiceClient::~LLWebRTCVoiceClient() +{ + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } + sShuttingDown = true; +} + +//--------------------------------------------------- + +void LLWebRTCVoiceClient::init(LLPumpIO* pump) +{ + // constructor will set up LLVoiceClient::getInstance() + llwebrtc::init(); + + mWebRTCDeviceInterface = llwebrtc::getDeviceInterface(); + mWebRTCDeviceInterface->setDevicesObserver(this); + mMainQueue = LL::WorkQueue::getInstance("mainloop"); +} + +void LLWebRTCVoiceClient::terminate() +{ + if (sShuttingDown) + { + return; + } + + mVoiceEnabled = false; + llwebrtc::terminate(); + + sShuttingDown = true; +} + +//--------------------------------------------------- + +void LLWebRTCVoiceClient::cleanUp() +{ + mNextSession.reset(); + mSession.reset(); + mNeighboringRegions.clear(); + sessionState::for_each(boost::bind(predShutdownSession, _1)); + LL_DEBUGS("Voice") << "Exiting" << LL_ENDL; +} + +// -------------------------------------------------- + +const LLVoiceVersionInfo& LLWebRTCVoiceClient::getVersion() +{ + return mVoiceVersion; +} + +//--------------------------------------------------- + +void LLWebRTCVoiceClient::updateSettings() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE + + setVoiceEnabled(LLVoiceClient::getInstance()->voiceEnabled()); + static LLCachedControl<S32> sVoiceEarLocation(gSavedSettings, "VoiceEarLocation"); + setEarLocation(sVoiceEarLocation); + + static LLCachedControl<std::string> sInputDevice(gSavedSettings, "VoiceInputAudioDevice"); + setCaptureDevice(sInputDevice); + + static LLCachedControl<std::string> sOutputDevice(gSavedSettings, "VoiceOutputAudioDevice"); + setRenderDevice(sOutputDevice); + + static LLCachedControl<F32> sMicLevel(gSavedSettings, "AudioLevelMic"); + setMicGain(sMicLevel); + + llwebrtc::LLWebRTCDeviceInterface::AudioConfig config; + + static LLCachedControl<bool> sEchoCancellation(gSavedSettings, "VoiceEchoCancellation", true); + config.mEchoCancellation = sEchoCancellation; + + static LLCachedControl<bool> sAGC(gSavedSettings, "VoiceAutomaticGainControl", true); + config.mAGC = sAGC; + + static LLCachedControl<U32> sNoiseSuppressionLevel(gSavedSettings, + "VoiceNoiseSuppressionLevel", + llwebrtc::LLWebRTCDeviceInterface::AudioConfig::ENoiseSuppressionLevel::NOISE_SUPPRESSION_LEVEL_VERY_HIGH); + config.mNoiseSuppressionLevel = (llwebrtc::LLWebRTCDeviceInterface::AudioConfig::ENoiseSuppressionLevel) (U32)sNoiseSuppressionLevel; + + mWebRTCDeviceInterface->setAudioConfig(config); + +} + +// Observers +void LLWebRTCVoiceClient::addObserver(LLVoiceClientParticipantObserver *observer) +{ + mParticipantObservers.insert(observer); +} + +void LLWebRTCVoiceClient::removeObserver(LLVoiceClientParticipantObserver *observer) +{ + mParticipantObservers.erase(observer); +} + +void LLWebRTCVoiceClient::notifyParticipantObservers() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE + for (observer_set_t::iterator it = mParticipantObservers.begin(); it != mParticipantObservers.end();) + { + LLVoiceClientParticipantObserver *observer = *it; + observer->onParticipantsChanged(); + // In case onParticipantsChanged() deleted an entry. + it = mParticipantObservers.upper_bound(observer); + } +} + +void LLWebRTCVoiceClient::addObserver(LLVoiceClientStatusObserver *observer) +{ + mStatusObservers.insert(observer); +} + +void LLWebRTCVoiceClient::removeObserver(LLVoiceClientStatusObserver *observer) +{ + mStatusObservers.erase(observer); +} + +void LLWebRTCVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE + + LL_DEBUGS("Voice") << "( " << LLVoiceClientStatusObserver::status2string(status) << " )" + << " mSession=" << mSession << LL_ENDL; + + LL_DEBUGS("Voice") << " " << LLVoiceClientStatusObserver::status2string(status) << ", session channelInfo " + << getAudioSessionChannelInfo() << ", proximal is " << inSpatialChannel() << LL_ENDL; + + mIsProcessingChannels = status == LLVoiceClientStatusObserver::STATUS_JOINED; + + LLSD channelInfo = getAudioSessionChannelInfo(); + for (status_observer_set_t::iterator it = mStatusObservers.begin(); it != mStatusObservers.end();) + { + LLVoiceClientStatusObserver *observer = *it; + observer->onChange(status, channelInfo, inSpatialChannel()); + // In case onError() deleted an entry. + it = mStatusObservers.upper_bound(observer); + } + + // skipped to avoid speak button blinking + if (status != LLVoiceClientStatusObserver::STATUS_JOINING && + status != LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL && + status != LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED) + { + bool voice_status = LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking(); + + gAgent.setVoiceConnected(voice_status); + + if (voice_status) + { + LLFirstUse::speak(true); + } + } +} + +void LLWebRTCVoiceClient::addObserver(LLFriendObserver *observer) +{ +} + +void LLWebRTCVoiceClient::removeObserver(LLFriendObserver *observer) +{ +} + +//--------------------------------------------------- +// Primary voice loop. +// This voice loop is called every 100ms plus the time it +// takes to process the various functions called in the loop +// The loop does the following: +// * gates whether we do channel processing depending on +// whether we're running a WebRTC voice channel or +// one from another voice provider. +// * If in spatial voice, it determines whether we've changed +// parcels, whether region/parcel voice settings have changed, +// etc. and manages whether the voice channel needs to change. +// * calls the state machines for the sessions to negotiate +// connection to various voice channels. +// * Sends updates to the voice server when this agent's +// voice levels, or positions have changed. +void LLWebRTCVoiceClient::voiceConnectionCoro() +{ + LL_DEBUGS("Voice") << "starting" << LL_ENDL; + mIsCoroutineActive = true; + LLCoros::set_consuming(true); + try + { + LLMuteList::getInstance()->addObserver(this); + while (!sShuttingDown) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_VOICE("voiceConnectionCoroLoop") + // TODO: Doing some measurement and calculation here, + // we could reduce the timeout to take into account the + // time spent on the previous loop to have the loop + // cycle at exactly 100ms, instead of 100ms + loop + // execution time. + // Could help with voice updates making for smoother + // voice when we're busy. + llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); + bool voiceEnabled = mVoiceEnabled; + + if (!isAgentAvatarValid()) + { + continue; + } + + LLViewerRegion *regionp = gAgent.getRegion(); + if (!regionp) + { + continue; + } + + if (!mProcessChannels) + { + // we've switched away from webrtc voice, so shut all channels down. + // leave channel can be called again and again without adverse effects. + // it merely tells channels to shut down if they're not already doing so. + leaveChannel(false); + } + else if (inSpatialChannel()) + { + bool useEstateVoice = true; + // add session for region or parcel voice. + if (!regionp || regionp->getRegionID().isNull()) + { + // no region, no voice. + continue; + } + + voiceEnabled = voiceEnabled && regionp->isVoiceEnabled(); + + if (voiceEnabled) + { + LLParcel *parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + // check to see if parcel changed. + if (parcel && parcel->getLocalID() != INVALID_PARCEL_ID) + { + // parcel voice + if (!parcel->getParcelFlagAllowVoice()) + { + voiceEnabled = false; + } + else if (!parcel->getParcelFlagUseEstateVoiceChannel()) + { + // use the parcel-specific voice channel. + S32 parcel_local_id = parcel->getLocalID(); + std::string channelID = regionp->getRegionID().asString() + "-" + std::to_string(parcel->getLocalID()); + + useEstateVoice = false; + if (!inOrJoiningChannel(channelID)) + { + startParcelSession(channelID, parcel_local_id); + } + } + } + if (voiceEnabled && useEstateVoice && !inEstateChannel()) + { + // estate voice + startEstateSession(); + } + } + if (!voiceEnabled) + { + // voice is disabled, so leave and disable PTT + leaveChannel(true); + } + else + { + // we're in spatial voice, and voice is enabled, so determine positions in order + // to send position updates. + updatePosition(); + } + } + + sessionState::processSessionStates(); + if (mProcessChannels && voiceEnabled && !mHidden) + { + sendPositionUpdate(false); + updateOwnVolume(); + } + } + } + catch (const LLCoros::Stop&) + { + LL_DEBUGS("LLWebRTCVoiceClient") << "Received a shutdown exception" << LL_ENDL; + } + catch (const LLContinueError&) + { + LOG_UNHANDLED_EXCEPTION("LLWebRTCVoiceClient"); + } + catch (...) + { + // Ideally for Windows need to log SEH exception instead or to set SEH + // handlers but bugsplat shows local variables for windows, which should + // be enough + LL_WARNS("Voice") << "voiceConnectionStateMachine crashed" << LL_ENDL; + throw; + } + + cleanUp(); +} + +// For spatial, determine which neighboring regions to connect to +// for cross-region voice. +void LLWebRTCVoiceClient::updateNeighboringRegions() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE + + static const std::vector<LLVector3d> neighbors {LLVector3d(0.0f, 1.0f, 0.0f), LLVector3d(0.707f, 0.707f, 0.0f), + LLVector3d(1.0f, 0.0f, 0.0f), LLVector3d(0.707f, -0.707f, 0.0f), + LLVector3d(0.0f, -1.0f, 0.0f), LLVector3d(-0.707f, -0.707f, 0.0f), + LLVector3d(-1.0f, 0.0f, 0.0f), LLVector3d(-0.707f, 0.707f, 0.0f)}; + + // Estate voice requires connection to neighboring regions. + mNeighboringRegions.clear(); + + // add current region. + mNeighboringRegions.insert(gAgent.getRegion()->getRegionID()); + + // base off of speaker position as it'll move more slowly than camera position. + // Once we have hysteresis, we may be able to track off of speaker and camera position at 50m + // TODO: Add hysteresis so we don't flip-flop connections to neighbors + LLVector3d speaker_pos = LLWebRTCVoiceClient::getInstance()->getSpeakerPosition(); + for (auto &neighbor_pos : neighbors) + { + // include every region within 100m (2*MAX_AUDIO_DIST) to deal witht he fact that the camera + // can stray 50m away from the avatar. + LLViewerRegion *neighbor = LLWorld::instance().getRegionFromPosGlobal(speaker_pos + 2 * MAX_AUDIO_DIST * neighbor_pos); + if (neighbor && !neighbor->getRegionID().isNull()) + { + mNeighboringRegions.insert(neighbor->getRegionID()); + } + } +} + +//========================================================================= +// shut down the current audio session to make room for the next one. +void LLWebRTCVoiceClient::leaveAudioSession() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE + + if(mSession) + { + LL_DEBUGS("Voice") << "leaving session: " << mSession->mChannelID << LL_ENDL; + mSession->shutdownAllConnections(); + } + else + { + LL_WARNS("Voice") << "called with no active session" << LL_ENDL; + } +} + +//========================================================================= +// Device Management +void LLWebRTCVoiceClient::clearCaptureDevices() +{ + LL_DEBUGS("Voice") << "called" << LL_ENDL; + mCaptureDevices.clear(); +} + +void LLWebRTCVoiceClient::addCaptureDevice(const LLVoiceDevice& device) +{ + LL_DEBUGS("Voice") << "display: '" << device.display_name << "' device: '" << device.full_name << "'" << LL_ENDL; + mCaptureDevices.push_back(device); +} + +LLVoiceDeviceList& LLWebRTCVoiceClient::getCaptureDevices() +{ + return mCaptureDevices; +} + +void LLWebRTCVoiceClient::setCaptureDevice(const std::string& name) +{ + mWebRTCDeviceInterface->setCaptureDevice(name); +} +void LLWebRTCVoiceClient::setDevicesListUpdated(bool state) +{ + mDevicesListUpdated = state; +} + +// the singleton 'this' pointer will outlive the work queue. +void LLWebRTCVoiceClient::OnDevicesChanged(const llwebrtc::LLWebRTCVoiceDeviceList& render_devices, + const llwebrtc::LLWebRTCVoiceDeviceList& capture_devices) +{ + + LL::WorkQueue::postMaybe(mMainQueue, + [=] + { + OnDevicesChangedImpl(render_devices, capture_devices); + }); +} + +void LLWebRTCVoiceClient::OnDevicesChangedImpl(const llwebrtc::LLWebRTCVoiceDeviceList &render_devices, + const llwebrtc::LLWebRTCVoiceDeviceList &capture_devices) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE + std::string inputDevice = gSavedSettings.getString("VoiceInputAudioDevice"); + std::string outputDevice = gSavedSettings.getString("VoiceOutputAudioDevice"); + + LL_DEBUGS("Voice") << "Setting devices to-input: '" << inputDevice << "' output: '" << outputDevice << "'" << LL_ENDL; + clearRenderDevices(); + for (auto &device : render_devices) + { + addRenderDevice(LLVoiceDevice(device.mDisplayName, device.mID)); + } + setRenderDevice(outputDevice); + + clearCaptureDevices(); + for (auto &device : capture_devices) + { + LL_DEBUGS("Voice") << "Checking capture device:'" << device.mID << "'" << LL_ENDL; + + addCaptureDevice(LLVoiceDevice(device.mDisplayName, device.mID)); + } + setCaptureDevice(inputDevice); + + setDevicesListUpdated(true); +} + +void LLWebRTCVoiceClient::clearRenderDevices() +{ + LL_DEBUGS("Voice") << "called" << LL_ENDL; + mRenderDevices.clear(); +} + +void LLWebRTCVoiceClient::addRenderDevice(const LLVoiceDevice& device) +{ + LL_DEBUGS("Voice") << "display: '" << device.display_name << "' device: '" << device.full_name << "'" << LL_ENDL; + mRenderDevices.push_back(device); + +} + +LLVoiceDeviceList& LLWebRTCVoiceClient::getRenderDevices() +{ + return mRenderDevices; +} + +void LLWebRTCVoiceClient::setRenderDevice(const std::string& name) +{ + mWebRTCDeviceInterface->setRenderDevice(name); +} + +void LLWebRTCVoiceClient::tuningStart() +{ + if (!mIsInTuningMode) + { + mWebRTCDeviceInterface->setTuningMode(true); + mIsInTuningMode = true; + } +} + +void LLWebRTCVoiceClient::tuningStop() +{ + if (mIsInTuningMode) + { + mWebRTCDeviceInterface->setTuningMode(false); + mIsInTuningMode = false; + } +} + +bool LLWebRTCVoiceClient::inTuningMode() +{ + return mIsInTuningMode; +} + +void LLWebRTCVoiceClient::tuningSetMicVolume(float volume) +{ + mTuningMicGain = volume; +} + +void LLWebRTCVoiceClient::tuningSetSpeakerVolume(float volume) +{ + + if (volume != mTuningSpeakerVolume) + { + mTuningSpeakerVolume = volume; + } +} + +float LLWebRTCVoiceClient::getAudioLevel() +{ + if (mIsInTuningMode) + { + return (1.0 - mWebRTCDeviceInterface->getTuningAudioLevel() * LEVEL_SCALE_WEBRTC) * mTuningMicGain / 2.1; + } + else + { + return (1.0 - mWebRTCDeviceInterface->getPeerConnectionAudioLevel() * LEVEL_SCALE_WEBRTC) * mMicGain / 2.1; + } +} + +float LLWebRTCVoiceClient::tuningGetEnergy(void) +{ + return getAudioLevel(); +} + +bool LLWebRTCVoiceClient::deviceSettingsAvailable() +{ + bool result = true; + + if(mRenderDevices.empty() || mCaptureDevices.empty()) + result = false; + + return result; +} +bool LLWebRTCVoiceClient::deviceSettingsUpdated() +{ + bool updated = mDevicesListUpdated; + mDevicesListUpdated = false; + return updated; +} + +void LLWebRTCVoiceClient::refreshDeviceLists(bool clearCurrentList) +{ + if(clearCurrentList) + { + clearCaptureDevices(); + clearRenderDevices(); + } + mWebRTCDeviceInterface->refreshDevices(); +} + + +void LLWebRTCVoiceClient::setHidden(bool hidden) +{ + mHidden = hidden; + + if (inSpatialChannel()) + { + if (mHidden) + { + // get out of the channel entirely + // mute the microphone. + sessionState::for_each(boost::bind(predSetMuteMic, _1, true)); + } + else + { + // and put it back + sessionState::for_each(boost::bind(predSetMuteMic, _1, mMuteMic)); + updatePosition(); + sendPositionUpdate(true); + } + } +} + +///////////////////////////// +// session control messages. +// +// these are called by the sessions to report +// status for a given channel. By filtering +// on channel and region, these functions +// can send various notifications to +// other parts of the viewer, as well as +// managing housekeeping + +// A connection to a channel was successfully established, +// so shut down the current session and move on to the next +// if one is available. +// if the current session is the one that was established, +// notify the observers. +void LLWebRTCVoiceClient::OnConnectionEstablished(const std::string &channelID, const LLUUID ®ionID) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE + + if (gAgent.getRegion()->getRegionID() == regionID) + { + if (mNextSession && mNextSession->mChannelID == channelID) + { + if (mSession) + { + mSession->shutdownAllConnections(); + } + mSession = mNextSession; + mNextSession.reset(); + } + + if (mSession) + { + // Add ourselves as a participant. + mSession->addParticipant(gAgentID, gAgent.getRegion()->getRegionID()); + } + + // The current session was established. + if (mSession && mSession->mChannelID == channelID) + { + LLWebRTCVoiceClient::getInstance()->notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGGED_IN); + + // only set status to joined if asked to. This will happen in the case where we're not + // doing an ad-hoc based p2p session. Those sessions expect a STATUS_JOINED when the peer + // has, in fact, joined, which we detect elsewhere. + if (!mSession->mNotifyOnFirstJoin) + { + LLWebRTCVoiceClient::getInstance()->notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINED); + } + } + } +} + +void LLWebRTCVoiceClient::OnConnectionShutDown(const std::string &channelID, const LLUUID ®ionID) +{ + if (mSession && (mSession->mChannelID == channelID)) + { + if (gAgent.getRegion()->getRegionID() == regionID) + { + if (mSession && mSession->mChannelID == channelID) + { + LL_DEBUGS("Voice") << "Main WebRTC Connection Shut Down." << LL_ENDL; + } + } + mSession->removeAllParticipants(regionID); + } +} + +void LLWebRTCVoiceClient::OnConnectionFailure(const std::string &channelID, + const LLUUID ®ionID, + LLVoiceClientStatusObserver::EStatusType status_type) +{ + LL_DEBUGS("Voice") << "A connection failed. channel:" << channelID << LL_ENDL; + if (gAgent.getRegion()->getRegionID() == regionID) + { + if (mNextSession && mNextSession->mChannelID == channelID) + { + LLWebRTCVoiceClient::getInstance()->notifyStatusObservers(status_type); + } + else if (mSession && mSession->mChannelID == channelID) + { + LLWebRTCVoiceClient::getInstance()->notifyStatusObservers(status_type); + } + } +} + +// ----------------------------------------------------------- +// positional functionality. +void LLWebRTCVoiceClient::setEarLocation(S32 loc) +{ + if (mEarLocation != loc) + { + LL_DEBUGS("Voice") << "Setting mEarLocation to " << loc << LL_ENDL; + + mEarLocation = loc; + mSpatialCoordsDirty = true; + } +} + +void LLWebRTCVoiceClient::updatePosition(void) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE + + LLViewerRegion *region = gAgent.getRegion(); + if (region && isAgentAvatarValid()) + { + // get the avatar position. + LLVector3d avatar_pos = gAgentAvatarp->getPositionGlobal(); + LLQuaternion avatar_qrot = gAgentAvatarp->getRootJoint()->getWorldRotation(); + + avatar_pos += LLVector3d(0.f, 0.f, 1.f); // bump it up to head height + + LLVector3d earPosition; + LLQuaternion earRot; + switch (mEarLocation) + { + case earLocCamera: + default: + earPosition = region->getPosGlobalFromRegion(LLViewerCamera::getInstance()->getOrigin()); + earRot = LLViewerCamera::getInstance()->getQuaternion(); + break; + + case earLocAvatar: + earPosition = mAvatarPosition; + earRot = mAvatarRot; + break; + + case earLocMixed: + earPosition = mAvatarPosition; + earRot = LLViewerCamera::getInstance()->getQuaternion(); + break; + } + setListenerPosition(earPosition, // position + LLVector3::zero, // velocity + earRot); // rotation matrix + + setAvatarPosition(avatar_pos, // position + LLVector3::zero, // velocity + avatar_qrot); // rotation matrix + + enforceTether(); + + updateNeighboringRegions(); + + // update own region id to be the region id avatar is currently in. + LLWebRTCVoiceClient::participantStatePtr_t participant = findParticipantByID("Estate", gAgentID); + if(participant) + { + participant->mRegion = gAgent.getRegion()->getRegionID(); + } + } +} + +void LLWebRTCVoiceClient::setListenerPosition(const LLVector3d &position, const LLVector3 &velocity, const LLQuaternion &rot) +{ + mListenerRequestedPosition = position; + + if (mListenerVelocity != velocity) + { + mListenerVelocity = velocity; + mSpatialCoordsDirty = true; + } + + if (mListenerRot != rot) + { + mListenerRot = rot; + mSpatialCoordsDirty = true; + } +} + +void LLWebRTCVoiceClient::setAvatarPosition(const LLVector3d &position, const LLVector3 &velocity, const LLQuaternion &rot) +{ + if (dist_vec_squared(mAvatarPosition, position) > 0.01) + { + mAvatarPosition = position; + mSpatialCoordsDirty = true; + } + + if (mAvatarVelocity != velocity) + { + mAvatarVelocity = velocity; + mSpatialCoordsDirty = true; + } + + // If the two rotations are not exactly equal test their dot product + // to get the cos of the angle between them. + // If it is too small, don't update. + F32 rot_cos_diff = llabs(dot(mAvatarRot, rot)); + if ((mAvatarRot != rot) && (rot_cos_diff < MINUSCULE_ANGLE_COS)) + { + mAvatarRot = rot; + mSpatialCoordsDirty = true; + } +} + +// The listener (camera) must be within 50m of the +// avatar. Enforce it on the client. +// This will also be enforced on the voice server +// based on position sent from the simulator to the +// voice server. +void LLWebRTCVoiceClient::enforceTether() +{ + LLVector3d tethered = mListenerRequestedPosition; + + // constrain 'tethered' to within 50m of mAvatarPosition. + { + LLVector3d camera_offset = mListenerRequestedPosition - mAvatarPosition; + F32 camera_distance = (F32) camera_offset.magVec(); + if (camera_distance > MAX_AUDIO_DIST) + { + tethered = mAvatarPosition + (MAX_AUDIO_DIST / camera_distance) * camera_offset; + } + } + + if (dist_vec_squared(mListenerPosition, tethered) > 0.01) + { + mListenerPosition = tethered; + mSpatialCoordsDirty = true; + } +} + +// We send our position via a WebRTC data channel to the WebRTC +// server for fine-grained, low latency updates. On the server, +// these updates will be 'tethered' to the actual position of the avatar. +// Those updates are higher latency, however. +// This mechanism gives low latency spatial updates and server-enforced +// prevention of 'evesdropping' by sending camera updates beyond the +// standard 50m +void LLWebRTCVoiceClient::sendPositionUpdate(bool force) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE + + Json::FastWriter writer; + std::string spatial_data; + + if (mSpatialCoordsDirty || force) + { + Json::Value spatial = Json::objectValue; + + spatial["sp"] = Json::objectValue; + spatial["sp"]["x"] = (int) (mAvatarPosition[0] * 100); + spatial["sp"]["y"] = (int) (mAvatarPosition[1] * 100); + spatial["sp"]["z"] = (int) (mAvatarPosition[2] * 100); + spatial["sh"] = Json::objectValue; + spatial["sh"]["x"] = (int) (mAvatarRot[0] * 100); + spatial["sh"]["y"] = (int) (mAvatarRot[1] * 100); + spatial["sh"]["z"] = (int) (mAvatarRot[2] * 100); + spatial["sh"]["w"] = (int) (mAvatarRot[3] * 100); + + spatial["lp"] = Json::objectValue; + spatial["lp"]["x"] = (int) (mListenerPosition[0] * 100); + spatial["lp"]["y"] = (int) (mListenerPosition[1] * 100); + spatial["lp"]["z"] = (int) (mListenerPosition[2] * 100); + spatial["lh"] = Json::objectValue; + spatial["lh"]["x"] = (int) (mListenerRot[0] * 100); + spatial["lh"]["y"] = (int) (mListenerRot[1] * 100); + spatial["lh"]["z"] = (int) (mListenerRot[2] * 100); + spatial["lh"]["w"] = (int) (mListenerRot[3] * 100); + + mSpatialCoordsDirty = false; + spatial_data = writer.write(spatial); + + sessionState::for_each(boost::bind(predSendData, _1, spatial_data)); + } +} + +// Update our own volume on our participant, so it'll show up +// in the UI. This is done on all sessions, so switching +// sessions retains consistent volume levels. +void LLWebRTCVoiceClient::updateOwnVolume() { + F32 audio_level = 0.0; + if (!mMuteMic && !mTuningMode) + { + audio_level = getAudioLevel(); + } + + sessionState::for_each(boost::bind(predUpdateOwnVolume, _1, audio_level)); +} + +//////////////////////////////////// +// Managing list of participants + +// Provider-level participant management + +BOOL LLWebRTCVoiceClient::isParticipantAvatar(const LLUUID &id) +{ + // WebRTC participants are always SL avatars. + return TRUE; +} + +void LLWebRTCVoiceClient::getParticipantList(std::set<LLUUID> &participants) +{ + if (mProcessChannels && mSession) + { + for (participantUUIDMap::iterator iter = mSession->mParticipantsByUUID.begin(); + iter != mSession->mParticipantsByUUID.end(); + iter++) + { + participants.insert(iter->first); + } + } +} + +bool LLWebRTCVoiceClient::isParticipant(const LLUUID &speaker_id) +{ + if (mProcessChannels && mSession) + { + return (mSession->mParticipantsByUUID.find(speaker_id) != mSession->mParticipantsByUUID.end()); + } + return false; +} + +// protected provider-level participant management. +LLWebRTCVoiceClient::participantStatePtr_t LLWebRTCVoiceClient::findParticipantByID(const std::string &channelID, const LLUUID &id) +{ + participantStatePtr_t result; + LLWebRTCVoiceClient::sessionState::ptr_t session = sessionState::matchSessionByChannelID(channelID); + + if (session) + { + result = session->findParticipantByID(id); + } + + return result; +} + +LLWebRTCVoiceClient::participantStatePtr_t LLWebRTCVoiceClient::addParticipantByID(const std::string &channelID, const LLUUID &id, const LLUUID& region) +{ + participantStatePtr_t result; + LLWebRTCVoiceClient::sessionState::ptr_t session = sessionState::matchSessionByChannelID(channelID); + if (session) + { + result = session->addParticipant(id, region); + if (session->mNotifyOnFirstJoin && (id != gAgentID)) + { + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINED); + } + } + return result; +} + +void LLWebRTCVoiceClient::removeParticipantByID(const std::string &channelID, const LLUUID &id, const LLUUID& region) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE + + participantStatePtr_t result; + LLWebRTCVoiceClient::sessionState::ptr_t session = sessionState::matchSessionByChannelID(channelID); + if (session) + { + participantStatePtr_t participant = session->findParticipantByID(id); + if (participant && (participant->mRegion == region)) + { + session->removeParticipant(participant); + } + } +} + + +// participantState level participant management +LLWebRTCVoiceClient::participantState::participantState(const LLUUID& agent_id, const LLUUID& region) : + mURI(agent_id.asString()), + mAvatarID(agent_id), + mIsSpeaking(false), + mIsModeratorMuted(false), + mLevel(0.f), + mVolume(LLVoiceClient::VOLUME_DEFAULT), + mRegion(region) +{ +} + +LLWebRTCVoiceClient::participantStatePtr_t LLWebRTCVoiceClient::sessionState::addParticipant(const LLUUID& agent_id, const LLUUID& region) +{ + + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE + + participantStatePtr_t result; + + participantUUIDMap::iterator iter = mParticipantsByUUID.find(agent_id); + + if (iter != mParticipantsByUUID.end()) + { + result = iter->second; + result->mRegion = region; + } + + if (!result) + { + // participant isn't already in one list or the other. + result.reset(new participantState(agent_id, region)); + mParticipantsByUUID.insert(participantUUIDMap::value_type(agent_id, result)); + result->mAvatarID = agent_id; + } + + LLWebRTCVoiceClient::getInstance()->lookupName(agent_id); + + LLSpeakerVolumeStorage::getInstance()->getSpeakerVolume(result->mAvatarID, result->mVolume); + if (!LLWebRTCVoiceClient::sShuttingDown) + { + LLWebRTCVoiceClient::getInstance()->notifyParticipantObservers(); + } + + LL_DEBUGS("Voice") << "Participant \"" << result->mURI << "\" added." << LL_ENDL; + + return result; +} + + +// session-level participant management + +LLWebRTCVoiceClient::participantStatePtr_t LLWebRTCVoiceClient::sessionState::findParticipantByID(const LLUUID& id) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE + + participantStatePtr_t result; + participantUUIDMap::iterator iter = mParticipantsByUUID.find(id); + + if(iter != mParticipantsByUUID.end()) + { + result = iter->second; + } + + return result; +} + +void LLWebRTCVoiceClient::sessionState::removeParticipant(const LLWebRTCVoiceClient::participantStatePtr_t &participant) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE + + if (participant) + { + LLUUID participantID = participant->mAvatarID; + participantUUIDMap::iterator iter = mParticipantsByUUID.find(participant->mAvatarID); + + LL_DEBUGS("Voice") << "participant \"" << participant->mURI << "\" (" << participantID << ") removed." << LL_ENDL; + + if (iter == mParticipantsByUUID.end()) + { + LL_WARNS("Voice") << "Internal error: participant ID " << participantID << " not in UUID map" << LL_ENDL; + } + else + { + mParticipantsByUUID.erase(iter); + if (!LLWebRTCVoiceClient::sShuttingDown) + { + LLWebRTCVoiceClient::getInstance()->notifyParticipantObservers(); + } + } + if (mHangupOnLastLeave && (participantID != gAgentID) && (mParticipantsByUUID.size() <= 1)) + { + LLWebRTCVoiceClient::getInstance()->notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); + } + } +} + +void LLWebRTCVoiceClient::sessionState::removeAllParticipants(const LLUUID ®ion) +{ + std::vector<participantStatePtr_t> participantsToRemove; + + for (auto& participantEntry : mParticipantsByUUID) + { + if (region.isNull() || (participantEntry.second->mRegion == region)) + { + participantsToRemove.push_back(participantEntry.second); + } + } + for (auto& participant : participantsToRemove) + { + removeParticipant(participant); + } +} + +// Initiated the various types of sessions. +bool LLWebRTCVoiceClient::startEstateSession() +{ + leaveChannel(false); + mNextSession = addSession("Estate", sessionState::ptr_t(new estateSessionState())); + return true; +} + +bool LLWebRTCVoiceClient::startParcelSession(const std::string &channelID, S32 parcelID) +{ + leaveChannel(false); + mNextSession = addSession(channelID, sessionState::ptr_t(new parcelSessionState(channelID, parcelID))); + return true; +} + +bool LLWebRTCVoiceClient::startAdHocSession(const LLSD& channelInfo, bool notify_on_first_join, bool hangup_on_last_leave) +{ + leaveChannel(false); + LL_WARNS("Voice") << "Start AdHoc Session " << channelInfo << LL_ENDL; + std::string channelID = channelInfo["channel_uri"]; + std::string credentials = channelInfo["channel_credentials"]; + mNextSession = addSession(channelID, + sessionState::ptr_t(new adhocSessionState(channelID, + credentials, + notify_on_first_join, + hangup_on_last_leave))); + return true; +} + +bool LLWebRTCVoiceClient::isVoiceWorking() const +{ + return mIsProcessingChannels; +} + +// Returns true if calling back the session URI after the session has closed is possible. +// Currently this will be false only for PSTN P2P calls. +BOOL LLWebRTCVoiceClient::isSessionCallBackPossible(const LLUUID &session_id) +{ + sessionStatePtr_t session(findP2PSession(session_id)); + return session && session->isCallbackPossible() ? TRUE : FALSE; +} + +// Channel Management + +bool LLWebRTCVoiceClient::setSpatialChannel(const LLSD &channelInfo) +{ + LL_INFOS("Voice") << "SetSpatialChannel " << channelInfo << LL_ENDL; + LLViewerRegion *regionp = gAgent.getRegion(); + if (!regionp) + { + return false; + } + LLParcel *parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + + // we don't really have credentials for a spatial channel in webrtc, + // it's all handled by the sim. + if (channelInfo.isMap() && channelInfo.has("channel_uri")) + { + bool allow_voice = !channelInfo["channel_uri"].asString().empty(); + if (parcel) + { + parcel->setParcelFlag(PF_ALLOW_VOICE_CHAT, allow_voice); + parcel->setParcelFlag(PF_USE_ESTATE_VOICE_CHAN, channelInfo["channel_uri"].asUUID() == regionp->getRegionID()); + } + else + { + regionp->setRegionFlag(REGION_FLAGS_ALLOW_VOICE, allow_voice); + } + } + return true; +} + +void LLWebRTCVoiceClient::leaveNonSpatialChannel() +{ + LL_DEBUGS("Voice") << "Request to leave non-spatial channel." << LL_ENDL; + + // make sure we're not simply rejoining the current session + deleteSession(mNextSession); + + leaveChannel(true); +} + +// determine whether we're processing channels, or whether +// another voice provider is. +void LLWebRTCVoiceClient::processChannels(bool process) +{ + mProcessChannels = process; +} + +bool LLWebRTCVoiceClient::inProximalChannel() +{ + return inSpatialChannel(); +} + +bool LLWebRTCVoiceClient::inOrJoiningChannel(const std::string& channelID) +{ + return (mSession && mSession->mChannelID == channelID) || (mNextSession && mNextSession->mChannelID == channelID); +} + +bool LLWebRTCVoiceClient::inEstateChannel() +{ + return (mSession && mSession->isEstate()) || (mNextSession && mNextSession->isEstate()); +} + +bool LLWebRTCVoiceClient::inSpatialChannel() +{ + bool result = true; + + if (mNextSession) + { + result = mNextSession->isSpatial(); + } + else if(mSession) + { + result = mSession->isSpatial(); + } + + return result; +} + +// retrieves information used to negotiate p2p, adhoc, and group +// channels +LLSD LLWebRTCVoiceClient::getAudioSessionChannelInfo() +{ + LLSD result; + + if (mSession) + { + result["voice_server_type"] = WEBRTC_VOICE_SERVER_TYPE; + result["channel_uri"] = mSession->mChannelID; + } + + return result; +} + +void LLWebRTCVoiceClient::leaveChannel(bool stopTalking) +{ + if (mSession) + { + deleteSession(mSession); + } + + if (mNextSession) + { + deleteSession(mNextSession); + } + + // If voice was on, turn it off + if (stopTalking && LLVoiceClient::getInstance()->getUserPTTState()) + { + LLVoiceClient::getInstance()->setUserPTTState(false); + } +} + +bool LLWebRTCVoiceClient::isCurrentChannel(const LLSD &channelInfo) +{ + if (!mProcessChannels || (channelInfo["voice_server_type"].asString() != WEBRTC_VOICE_SERVER_TYPE)) + { + return false; + } + + sessionStatePtr_t session = mSession; + if (!session) + { + session = mNextSession; + } + + if (session) + { + if (!channelInfo["session_handle"].asString().empty()) + { + return session->mHandle == channelInfo["session_handle"].asString(); + } + return channelInfo["channel_uri"].asString() == session->mChannelID; + } + return false; +} + +bool LLWebRTCVoiceClient::compareChannels(const LLSD &channelInfo1, const LLSD &channelInfo2) +{ + return (channelInfo1["voice_server_type"] == WEBRTC_VOICE_SERVER_TYPE) && + (channelInfo1["voice_server_type"] == channelInfo2["voice_server_type"]) && + (channelInfo1["sip_uri"] == channelInfo2["sip_uri"]); +} + + +//---------------------------------------------- +// Audio muting, volume, gain, etc. + +// we're muting the mic, so tell each session such +void LLWebRTCVoiceClient::setMuteMic(bool muted) +{ + mMuteMic = muted; + // when you're hidden, your mic is always muted. + if (!mHidden) + { + sessionState::for_each(boost::bind(predSetMuteMic, _1, muted)); + } +} + +void LLWebRTCVoiceClient::predSetMuteMic(const LLWebRTCVoiceClient::sessionStatePtr_t &session, bool muted) +{ + participantStatePtr_t participant = session->findParticipantByID(gAgentID); + if (participant) + { + participant->mLevel = 0.0; + } + session->setMuteMic(muted); +} + +void LLWebRTCVoiceClient::setVoiceVolume(F32 volume) +{ + if (volume != mSpeakerVolume) + { + { + mSpeakerVolume = volume; + } + sessionState::for_each(boost::bind(predSetSpeakerVolume, _1, volume)); + } +} + +void LLWebRTCVoiceClient::predSetSpeakerVolume(const LLWebRTCVoiceClient::sessionStatePtr_t &session, F32 volume) +{ + session->setSpeakerVolume(volume); +} + +void LLWebRTCVoiceClient::setMicGain(F32 gain) +{ + if (gain != mMicGain) + { + mMicGain = gain; + mWebRTCDeviceInterface->setPeerConnectionGain(gain); + } +} + + +void LLWebRTCVoiceClient::setVoiceEnabled(bool enabled) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE + + LL_DEBUGS("Voice") + << "( " << (enabled ? "enabled" : "disabled") << " )" + << " was "<< (mVoiceEnabled ? "enabled" : "disabled") + << " coro "<< (mIsCoroutineActive ? "active" : "inactive") + << LL_ENDL; + + if (enabled != mVoiceEnabled) + { + // TODO: Refactor this so we don't call into LLVoiceChannel, but simply + // use the status observer + mVoiceEnabled = enabled; + LLVoiceClientStatusObserver::EStatusType status; + + if (enabled) + { + LL_DEBUGS("Voice") << "enabling" << LL_ENDL; + LLVoiceChannel::getCurrentVoiceChannel()->activate(); + status = LLVoiceClientStatusObserver::STATUS_VOICE_ENABLED; + mSpatialCoordsDirty = true; + updatePosition(); + if (!mIsCoroutineActive) + { + LLCoros::instance().launch("LLWebRTCVoiceClient::voiceConnectionCoro", + boost::bind(&LLWebRTCVoiceClient::voiceConnectionCoro, LLWebRTCVoiceClient::getInstance())); + } + else + { + LL_DEBUGS("Voice") << "coro should be active.. not launching" << LL_ENDL; + } + } + else + { + // Turning voice off looses your current channel -- this makes sure the UI isn't out of sync when you re-enable it. + LLVoiceChannel::getCurrentVoiceChannel()->deactivate(); + gAgent.setVoiceConnected(false); + status = LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED; + cleanUp(); + } + + notifyStatusObservers(status); + } + else + { + LL_DEBUGS("Voice") << " no-op" << LL_ENDL; + } +} + + +///////////////////////////// +// Accessors for data related to nearby speakers + +std::string LLWebRTCVoiceClient::getDisplayName(const LLUUID& id) +{ + std::string result; + if (mProcessChannels && mSession) + { + participantStatePtr_t participant(mSession->findParticipantByID(id)); + if (participant) + { + result = participant->mDisplayName; + } + } + return result; +} + +BOOL LLWebRTCVoiceClient::getIsSpeaking(const LLUUID& id) +{ + BOOL result = FALSE; + if (mProcessChannels && mSession) + { + participantStatePtr_t participant(mSession->findParticipantByID(id)); + if (participant) + { + result = participant->mIsSpeaking; + } + } + return result; +} + +// TODO: Need to pull muted status from the webrtc server +BOOL LLWebRTCVoiceClient::getIsModeratorMuted(const LLUUID& id) +{ + BOOL result = FALSE; + if (mProcessChannels && mSession) + { + participantStatePtr_t participant(mSession->findParticipantByID(id)); + if (participant) + { + result = participant->mIsModeratorMuted; + } + } + return result; +} + +F32 LLWebRTCVoiceClient::getCurrentPower(const LLUUID &id) +{ + F32 result = 0.0; + if (!mProcessChannels || !mSession) + { + return result; + } + participantStatePtr_t participant(mSession->findParticipantByID(id)); + if (participant) + { + if (participant->mIsSpeaking) + { + result = participant->mLevel; + } + } + return result; +} + +// External accessors. +F32 LLWebRTCVoiceClient::getUserVolume(const LLUUID& id) +{ + // Minimum volume will be returned for users with voice disabled + F32 result = LLVoiceClient::VOLUME_MIN; + + if (mSession) + { + participantStatePtr_t participant(mSession->findParticipantByID(id)); + if (participant) + { + result = participant->mVolume; + } + } + + return result; +} + +void LLWebRTCVoiceClient::setUserVolume(const LLUUID& id, F32 volume) +{ + F32 clamped_volume = llclamp(volume, LLVoiceClient::VOLUME_MIN, LLVoiceClient::VOLUME_MAX); + if(mSession) + { + participantStatePtr_t participant(mSession->findParticipantByID(id)); + if (participant && (participant->mAvatarID != gAgentID)) + { + if (!is_approx_equal(volume, LLVoiceClient::VOLUME_DEFAULT)) + { + // Store this volume setting for future sessions if it has been + // changed from the default + LLSpeakerVolumeStorage::getInstance()->storeSpeakerVolume(id, volume); + } + else + { + // Remove stored volume setting if it is returned to the default + LLSpeakerVolumeStorage::getInstance()->removeSpeakerVolume(id); + } + + participant->mVolume = clamped_volume; + } + } + sessionState::for_each(boost::bind(predSetUserVolume, _1, id, clamped_volume)); +} + +// set volume level (gain level) for another user. +void LLWebRTCVoiceClient::predSetUserVolume(const LLWebRTCVoiceClient::sessionStatePtr_t &session, const LLUUID &id, F32 volume) +{ + session->setUserVolume(id, volume); +} + +//////////////////////// +///LLMuteListObserver +/// + +void LLWebRTCVoiceClient::onChange() +{ +} + +void LLWebRTCVoiceClient::onChangeDetailed(const LLMute& mute) +{ + if (mute.mType == LLMute::AGENT) + { + bool muted = ((mute.mFlags & LLMute::flagVoiceChat) == 0); + sessionState::for_each(boost::bind(predSetUserMute, _1, mute.mID, muted)); + } +} + +void LLWebRTCVoiceClient::predSetUserMute(const LLWebRTCVoiceClient::sessionStatePtr_t &session, const LLUUID &id, bool mute) +{ + session->setUserMute(id, mute); +} + +//------------------------------------------------------------------------ +// Sessions + +std::map<std::string, LLWebRTCVoiceClient::sessionState::ptr_t> LLWebRTCVoiceClient::sessionState::mSessions; + + +LLWebRTCVoiceClient::sessionState::sessionState() : + mHangupOnLastLeave(false), + mNotifyOnFirstJoin(false), + mMuted(false), + mSpeakerVolume(1.0), + mShuttingDown(false) +{ +} +// ------------------------------------------------------------------ +// Predicates, for calls to all sessions + +void LLWebRTCVoiceClient::predUpdateOwnVolume(const LLWebRTCVoiceClient::sessionStatePtr_t &session, F32 audio_level) +{ + participantStatePtr_t participant = session->findParticipantByID(gAgentID); + if (participant) + { + participant->mLevel = audio_level; + // TODO: Add VAD for our own voice. + participant->mIsSpeaking = audio_level > SPEAKING_AUDIO_LEVEL; + } +} + +void LLWebRTCVoiceClient::predSendData(const LLWebRTCVoiceClient::sessionStatePtr_t &session, const std::string &spatial_data) +{ + if (session->isSpatial() && !spatial_data.empty()) + { + session->sendData(spatial_data); + } +} + +void LLWebRTCVoiceClient::sessionState::sendData(const std::string &data) +{ + for (auto &connection : mWebRTCConnections) + { + connection->sendData(data); + } +} + +void LLWebRTCVoiceClient::sessionState::setMuteMic(bool muted) +{ + mMuted = muted; + for (auto &connection : mWebRTCConnections) + { + connection->setMuteMic(muted); + } +} + +void LLWebRTCVoiceClient::sessionState::setSpeakerVolume(F32 volume) +{ + mSpeakerVolume = volume; + for (auto &connection : mWebRTCConnections) + { + connection->setSpeakerVolume(volume); + } +} + +void LLWebRTCVoiceClient::sessionState::setUserVolume(const LLUUID &id, F32 volume) +{ + if (mParticipantsByUUID.find(id) == mParticipantsByUUID.end()) + { + return; + } + for (auto &connection : mWebRTCConnections) + { + connection->setUserVolume(id, volume); + } +} + +void LLWebRTCVoiceClient::sessionState::setUserMute(const LLUUID &id, bool mute) +{ + if (mParticipantsByUUID.find(id) == mParticipantsByUUID.end()) + { + return; + } + for (auto &connection : mWebRTCConnections) + { + connection->setUserMute(id, mute); + } +} +/*static*/ +void LLWebRTCVoiceClient::sessionState::addSession( + const std::string & channelID, + LLWebRTCVoiceClient::sessionState::ptr_t& session) +{ + mSessions[channelID] = session; +} + +LLWebRTCVoiceClient::sessionState::~sessionState() +{ + LL_DEBUGS("Voice") << "Destroying session CHANNEL=" << mChannelID << LL_ENDL; + + removeAllParticipants(); +} + +/*static*/ +LLWebRTCVoiceClient::sessionState::ptr_t LLWebRTCVoiceClient::sessionState::matchSessionByChannelID(const std::string& channel_id) +{ + sessionStatePtr_t result; + + // *TODO: My kingdom for a lambda! + std::map<std::string, ptr_t>::iterator it = mSessions.find(channel_id); + if (it != mSessions.end()) + { + result = (*it).second; + } + return result; +} + +void LLWebRTCVoiceClient::sessionState::for_each(sessionFunc_t func) +{ + std::for_each(mSessions.begin(), mSessions.end(), boost::bind(for_eachPredicate, _1, func)); +} + +void LLWebRTCVoiceClient::sessionState::reapEmptySessions() +{ + std::map<std::string, ptr_t>::iterator iter; + for (iter = mSessions.begin(); iter != mSessions.end();) + { + if (iter->second->isEmpty()) + { + iter = mSessions.erase(iter); + } + else + { + ++iter; + } + } +} + +/*static*/ +void LLWebRTCVoiceClient::sessionState::for_eachPredicate(const std::pair<std::string, LLWebRTCVoiceClient::sessionState::wptr_t> &a, sessionFunc_t func) +{ + ptr_t aLock(a.second.lock()); + + if (aLock) + func(aLock); + else + { + LL_WARNS("Voice") << "Stale handle in session map!" << LL_ENDL; + } +} + +LLWebRTCVoiceClient::sessionStatePtr_t LLWebRTCVoiceClient::addSession(const std::string &channel_id, sessionState::ptr_t session) +{ + sessionStatePtr_t existingSession = sessionState::matchSessionByChannelID(channel_id); + if (!existingSession) + { + // No existing session found. + + LL_DEBUGS("Voice") << "adding new session with channel: " << channel_id << LL_ENDL; + session->setMuteMic(mMuteMic); + session->setSpeakerVolume(mSpeakerVolume); + + sessionState::addSession(channel_id, session); + return session; + } + else + { + // Found an existing session + LL_DEBUGS("Voice") << "Attempting to add already-existing session " << channel_id << LL_ENDL; + existingSession->revive(); + + return existingSession; + } +} + +LLWebRTCVoiceClient::sessionStatePtr_t LLWebRTCVoiceClient::findP2PSession(const LLUUID &agent_id) +{ + sessionStatePtr_t result = sessionState::matchSessionByChannelID(agent_id.asString()); + if (result && !result->isSpatial()) + { + return result; + } + + result.reset(); + return result; +} + +void LLWebRTCVoiceClient::sessionState::shutdownAllConnections() +{ + mShuttingDown = true; + for (auto &&connection : mWebRTCConnections) + { + connection->shutDown(); + } +} + +// in case we drop into a session (spatial, etc.) right after +// telling the session to shut down, revive it so it reconnects. +void LLWebRTCVoiceClient::sessionState::revive() +{ + mShuttingDown = false; +} + +//========================================================================= +// the following are methods to support the coroutine implementation of the +// voice connection and processing. They should only be called in the context +// of a coroutine. +// +// + +void LLWebRTCVoiceClient::sessionState::processSessionStates() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE + + auto iter = mSessions.begin(); + while (iter != mSessions.end()) + { + if (!iter->second->processConnectionStates() && iter->second->mShuttingDown) + { + // if the connections associated with a session are gone, + // and this session is shutting down, remove it. + iter = mSessions.erase(iter); + } + else + { + iter++; + } + } +} + +// process the states on each connection associated with a session. +bool LLWebRTCVoiceClient::sessionState::processConnectionStates() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE + + std::list<connectionPtr_t>::iterator iter = mWebRTCConnections.begin(); + while (iter != mWebRTCConnections.end()) + { + if (!iter->get()->connectionStateMachine()) + { + // if the state machine returns false, the connection is shut down + // so delete it. + iter = mWebRTCConnections.erase(iter); + } + else + { + ++iter; + } + } + return !mWebRTCConnections.empty(); +} + +// processing of spatial voice connection states requires special handling. +// as neighboring regions need to be started up or shut down depending +// on our location. +bool LLWebRTCVoiceClient::estateSessionState::processConnectionStates() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE + + if (!mShuttingDown) + { + // Estate voice requires connection to neighboring regions. + std::set<LLUUID> neighbor_ids = LLWebRTCVoiceClient::getInstance()->getNeighboringRegions(); + + for (auto &connection : mWebRTCConnections) + { + boost::shared_ptr<LLVoiceWebRTCSpatialConnection> spatialConnection = + boost::static_pointer_cast<LLVoiceWebRTCSpatialConnection>(connection); + + LLUUID regionID = spatialConnection.get()->getRegionID(); + + if (neighbor_ids.find(regionID) == neighbor_ids.end()) + { + // shut down connections to neighbors that are too far away. + spatialConnection.get()->shutDown(); + } + neighbor_ids.erase(regionID); + } + + // add new connections for new neighbors + for (auto &neighbor : neighbor_ids) + { + connectionPtr_t connection(new LLVoiceWebRTCSpatialConnection(neighbor, INVALID_PARCEL_ID, mChannelID)); + + mWebRTCConnections.push_back(connection); + connection->setMuteMic(mMuted); + connection->setSpeakerVolume(mSpeakerVolume); + } + } + return LLWebRTCVoiceClient::sessionState::processConnectionStates(); +} + +// Various session state constructors. + +LLWebRTCVoiceClient::estateSessionState::estateSessionState() +{ + mHangupOnLastLeave = false; + mNotifyOnFirstJoin = false; + mChannelID = "Estate"; + LLUUID region_id = gAgent.getRegion()->getRegionID(); + + mWebRTCConnections.emplace_back(new LLVoiceWebRTCSpatialConnection(region_id, INVALID_PARCEL_ID, "Estate")); +} + +LLWebRTCVoiceClient::parcelSessionState::parcelSessionState(const std::string &channelID, S32 parcel_local_id) +{ + mHangupOnLastLeave = false; + mNotifyOnFirstJoin = false; + LLUUID region_id = gAgent.getRegion()->getRegionID(); + mChannelID = channelID; + mWebRTCConnections.emplace_back(new LLVoiceWebRTCSpatialConnection(region_id, parcel_local_id, channelID)); +} + +LLWebRTCVoiceClient::adhocSessionState::adhocSessionState(const std::string &channelID, + const std::string &credentials, + bool notify_on_first_join, + bool hangup_on_last_leave) : + mCredentials(credentials) +{ + mHangupOnLastLeave = hangup_on_last_leave; + mNotifyOnFirstJoin = notify_on_first_join; + LLUUID region_id = gAgent.getRegion()->getRegionID(); + mChannelID = channelID; + mWebRTCConnections.emplace_back(new LLVoiceWebRTCAdHocConnection(region_id, channelID, credentials)); +} + +void LLWebRTCVoiceClient::predShutdownSession(const LLWebRTCVoiceClient::sessionStatePtr_t& session) +{ + session->shutdownAllConnections(); +} + +void LLWebRTCVoiceClient::deleteSession(const sessionStatePtr_t &session) +{ + if (!session) + { + return; + } + + // At this point, the session should be unhooked from all lists and all state should be consistent. + session->shutdownAllConnections(); + // If this is the current audio session, clean up the pointer which will soon be dangling. + bool deleteAudioSession = mSession == session; + bool deleteNextAudioSession = mNextSession == session; + if (deleteAudioSession) + { + mSession.reset(); + } + + // ditto for the next audio session + if (deleteNextAudioSession) + { + mNextSession.reset(); + } +} + + +// Name resolution +void LLWebRTCVoiceClient::lookupName(const LLUUID &id) +{ + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } + mAvatarNameCacheConnection = LLAvatarNameCache::get(id, boost::bind(&LLWebRTCVoiceClient::onAvatarNameCache, this, _1, _2)); +} + +void LLWebRTCVoiceClient::onAvatarNameCache(const LLUUID& agent_id, + const LLAvatarName& av_name) +{ + mAvatarNameCacheConnection.disconnect(); + std::string display_name = av_name.getDisplayName(); + avatarNameResolved(agent_id, display_name); +} + +void LLWebRTCVoiceClient::predAvatarNameResolution(const LLWebRTCVoiceClient::sessionStatePtr_t &session, LLUUID id, std::string name) +{ + participantStatePtr_t participant(session->findParticipantByID(id)); + if (participant) + { + // Found -- fill in the name + participant->mDisplayName = name; + // and post a "participants updated" message to listeners later. + LLWebRTCVoiceClient::getInstance()->notifyParticipantObservers(); + } +} + +void LLWebRTCVoiceClient::avatarNameResolved(const LLUUID &id, const std::string &name) +{ + sessionState::for_each(boost::bind(predAvatarNameResolution, _1, id, name)); +} + +// Leftover from vivox PTSN +std::string LLWebRTCVoiceClient::sipURIFromID(const LLUUID& id) +{ + return id.asString(); +} + + +///////////////////////////// +// LLVoiceWebRTCConnection +// These connections manage state transitions, negotiating webrtc connections, +// and other such things for a single connection to a Secondlife WebRTC server. +// Multiple of these connections may be active at once, in the case of +// cross-region voice, or when a new connection is being created before the old +// has a chance to shut down. +LLVoiceWebRTCConnection::LLVoiceWebRTCConnection(const LLUUID ®ionID, const std::string &channelID) : + mWebRTCAudioInterface(nullptr), + mWebRTCDataInterface(nullptr), + mVoiceConnectionState(VOICE_STATE_START_SESSION), + mCurrentStatus(LLVoiceClientStatusObserver::STATUS_VOICE_ENABLED), + mMuted(true), + mShutDown(false), + mIceCompleted(false), + mSpeakerVolume(0.0), + mOutstandingRequests(0), + mChannelID(channelID), + mRegionID(regionID), + mRetryWaitPeriod(0) +{ + // retries wait a short period...randomize it so + // all clients don't try to reconnect at once. + mRetryWaitSecs = ((F32) rand() / (RAND_MAX)) + 0.5; + + mWebRTCPeerConnectionInterface = llwebrtc::newPeerConnection(); + mWebRTCPeerConnectionInterface->setSignalingObserver(this); + mMainQueue = LL::WorkQueue::getInstance("mainloop"); +} + +LLVoiceWebRTCConnection::~LLVoiceWebRTCConnection() +{ + if (LLWebRTCVoiceClient::isShuttingDown()) + { + // peer connection and observers will be cleaned up + // by llwebrtc::terminate() on shutdown. + return; + } + mWebRTCPeerConnectionInterface->unsetSignalingObserver(this); + llwebrtc::freePeerConnection(mWebRTCPeerConnectionInterface); +} + + +// ICE (Interactive Connectivity Establishment) +// When WebRTC tries to negotiate a connection to the Secondlife WebRTC Server, +// the negotiation will result in a few updates about the best path +// to which to connect. +// The Secondlife servers are configured for ICE trickling, where, after a session is partially +// negotiated, updates about the best connectivity paths may trickle in. These need to be +// sent to the Secondlife WebRTC server via the simulator so that both sides have a clear +// view of the network environment. + +// callback from llwebrtc +void LLVoiceWebRTCConnection::OnIceGatheringState(llwebrtc::LLWebRTCSignalingObserver::EIceGatheringState state) +{ + LL::WorkQueue::postMaybe(mMainQueue, + [=] { + LL_DEBUGS("Voice") << "Ice Gathering voice account. " << state << LL_ENDL; + + switch (state) + { + case llwebrtc::LLWebRTCSignalingObserver::EIceGatheringState::ICE_GATHERING_COMPLETE: + { + mIceCompleted = true; + break; + } + case llwebrtc::LLWebRTCSignalingObserver::EIceGatheringState::ICE_GATHERING_NEW: + { + mIceCompleted = false; + } + default: + break; + } + }); +} + +// callback from llwebrtc +void LLVoiceWebRTCConnection::OnIceCandidate(const llwebrtc::LLWebRTCIceCandidate& candidate) +{ + LL::WorkQueue::postMaybe(mMainQueue, [=] { mIceCandidates.push_back(candidate); }); +} + +void LLVoiceWebRTCConnection::processIceUpdates() +{ + mOutstandingRequests++; + LLCoros::getInstance()->launch("LLVoiceWebRTCConnection::processIceUpdatesCoro", + boost::bind(&LLVoiceWebRTCConnection::processIceUpdatesCoro, this)); +} + +// Ice candidates may be streamed in before or after the SDP offer is available (see below) +// This function determines whether candidates are available to send to the Secondlife WebRTC +// server via the simulator. If so, and there are no more candidates, this code +// will make the cap call to the server sending up the ICE candidates. +void LLVoiceWebRTCConnection::processIceUpdatesCoro() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE + + if (mShutDown || LLWebRTCVoiceClient::isShuttingDown()) + { + mOutstandingRequests--; + return; + } + + //bool iceCompleted = false; + LLSD body; + if (!mIceCandidates.empty() || mIceCompleted) + { + LLViewerRegion *regionp = LLWorld::instance().getRegionFromID(mRegionID); + if (!regionp || !regionp->capabilitiesReceived()) + { + LL_DEBUGS("Voice") << "no capabilities for ice gathering; waiting " << LL_ENDL; + mOutstandingRequests--; + return; + } + + std::string url = regionp->getCapability("VoiceSignalingRequest"); + if (url.empty()) + { + mOutstandingRequests--; + return; + } + + LL_DEBUGS("Voice") << "region ready to complete voice signaling; url=" << url << LL_ENDL; + if (!mIceCandidates.empty()) + { + LLSD candidates = LLSD::emptyArray(); + for (auto &ice_candidate : mIceCandidates) + { + LLSD body_candidate; + body_candidate["sdpMid"] = ice_candidate.mSdpMid; + body_candidate["sdpMLineIndex"] = ice_candidate.mMLineIndex; + body_candidate["candidate"] = ice_candidate.mCandidate; + candidates.append(body_candidate); + } + body["candidates"] = candidates; + mIceCandidates.clear(); + } + else if (mIceCompleted) + { + LLSD body_candidate; + body_candidate["completed"] = true; + body["candidate"] = body_candidate; + //iceCompleted = mIceCompleted; + mIceCompleted = false; + } + + body["viewer_session"] = mViewerSession; + body["voice_server_type"] = WEBRTC_VOICE_SERVER_TYPE; + + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter( + new LLCoreHttpUtil::HttpCoroutineAdapter("LLVoiceWebRTCAdHocConnection::processIceUpdatesCoro", + LLCore::HttpRequest::DEFAULT_POLICY_ID)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); + + httpOpts->setWantHeaders(true); + + LLSD result = httpAdapter->postAndSuspend(httpRequest, url, body, httpOpts); + + if (LLWebRTCVoiceClient::isShuttingDown()) + { + mOutstandingRequests--; + return; + } + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + // couldn't trickle the candidates, so restart the session. + setVoiceConnectionState(VOICE_STATE_SESSION_RETRY); + } + } + mOutstandingRequests--; +} + + +// An 'Offer' comes in the form of a SDP (Session Description Protocol) +// which contains all sorts of info about the session, from network paths +// to the type of session (audio, video) to characteristics (the encoder type.) +// This SDP also serves as the 'ticket' to the server, security-wise. +// The Offer is retrieved from the WebRTC library on the client, +// and is passed to the simulator via a CAP, which then passes +// it on to the Secondlife WebRTC server. + +// +// The LLWebRTCVoiceConnection object will not be deleted +// before the webrtc connection itself is shut down, so +// we shouldn't be getting this callback on a nonexistant +// this pointer. + +// callback from llwebrtc +void LLVoiceWebRTCConnection::OnOfferAvailable(const std::string &sdp) +{ + LL::WorkQueue::postMaybe(mMainQueue, + [=] { + if (mShutDown) + { + return; + } + LL_DEBUGS("Voice") << "On Offer Available." << LL_ENDL; + mChannelSDP = sdp; + if (mVoiceConnectionState == VOICE_STATE_WAIT_FOR_SESSION_START) + { + mVoiceConnectionState = VOICE_STATE_REQUEST_CONNECTION; + } + }); +} + + +// +// The LLWebRTCVoiceConnection object will not be deleted +// before the webrtc connection itself is shut down, so +// we shouldn't be getting this callback on a nonexistant +// this pointer. +// nor should audio_interface be invalid if the LLWebRTCVoiceConnection +// is shut down. + +// callback from llwebrtc +void LLVoiceWebRTCConnection::OnAudioEstablished(llwebrtc::LLWebRTCAudioInterface* audio_interface) +{ + LL::WorkQueue::postMaybe(mMainQueue, + [=] { + if (mShutDown) + { + return; + } + LL_DEBUGS("Voice") << "On AudioEstablished." << LL_ENDL; + mWebRTCAudioInterface = audio_interface; + setVoiceConnectionState(VOICE_STATE_SESSION_ESTABLISHED); + }); +} + + +// +// The LLWebRTCVoiceConnection object will not be deleted +// before the webrtc connection itself is shut down, so +// we shouldn't be getting this callback on a nonexistant +// this pointer. + +// callback from llwebrtc +void LLVoiceWebRTCConnection::OnRenegotiationNeeded() +{ + LL::WorkQueue::postMaybe(mMainQueue, + [=] { + LL_DEBUGS("Voice") << "Voice channel requires renegotiation." << LL_ENDL; + if (!mShutDown) + { + setVoiceConnectionState(VOICE_STATE_SESSION_RETRY); + } + mCurrentStatus = LLVoiceClientStatusObserver::ERROR_UNKNOWN; + }); +} + +// callback from llwebrtc +void LLVoiceWebRTCConnection::OnPeerConnectionClosed() +{ + LL::WorkQueue::postMaybe(mMainQueue, + [=] { + LL_DEBUGS("Voice") << "Peer connection has closed." << LL_ENDL; + if (mVoiceConnectionState == VOICE_STATE_WAIT_FOR_CLOSE) + { + setVoiceConnectionState(VOICE_STATE_CLOSED); + mOutstandingRequests--; + } + }); +} + +void LLVoiceWebRTCConnection::setMuteMic(bool muted) +{ + mMuted = muted; + if (mWebRTCAudioInterface) + { + mWebRTCAudioInterface->setMute(muted); + } +} + +void LLVoiceWebRTCConnection::setSpeakerVolume(F32 volume) +{ + mSpeakerVolume = volume; + if (mWebRTCAudioInterface) + { + mWebRTCAudioInterface->setReceiveVolume(volume); + } +} + +void LLVoiceWebRTCConnection::setUserVolume(const LLUUID& id, F32 volume) +{ + Json::Value root = Json::objectValue; + Json::Value user_gain = Json::objectValue; + user_gain[id.asString()] = (uint32_t)(volume*200); // give it two decimal places with a range from 0-200, where 100 is normal + root["ug"] = user_gain; + Json::FastWriter writer; + std::string json_data = writer.write(root); + if (mWebRTCDataInterface) + { + mWebRTCDataInterface->sendData(json_data, false); + } +} + +void LLVoiceWebRTCConnection::setUserMute(const LLUUID& id, bool mute) +{ + Json::Value root = Json::objectValue; + Json::Value muted = Json::objectValue; + muted[id.asString()] = mute; + root["m"] = muted; + Json::FastWriter writer; + std::string json_data = writer.write(root); + if (mWebRTCDataInterface) + { + mWebRTCDataInterface->sendData(json_data, false); + } +} + + +// Send data to the Secondlife WebRTC server via the webrtc +// data channel. +void LLVoiceWebRTCConnection::sendData(const std::string &data) +{ + if (getVoiceConnectionState() == VOICE_STATE_SESSION_UP && mWebRTCDataInterface) + { + mWebRTCDataInterface->sendData(data, false); + } +} + +// Tell the simulator that we're shutting down a voice connection. +// The simulator will pass this on to the Secondlife WebRTC server. +void LLVoiceWebRTCConnection::breakVoiceConnectionCoro() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE + + LL_DEBUGS("Voice") << "Disconnecting voice." << LL_ENDL; + if (mWebRTCDataInterface) + { + mWebRTCDataInterface->unsetDataObserver(this); + mWebRTCDataInterface = nullptr; + } + mWebRTCAudioInterface = nullptr; + LLViewerRegion *regionp = LLWorld::instance().getRegionFromID(mRegionID); + if (!regionp || !regionp->capabilitiesReceived()) + { + LL_DEBUGS("Voice") << "no capabilities for voice provisioning; waiting " << LL_ENDL; + setVoiceConnectionState(VOICE_STATE_SESSION_RETRY); + mOutstandingRequests--; + return; + } + + std::string url = regionp->getCapability("ProvisionVoiceAccountRequest"); + if (url.empty()) + { + setVoiceConnectionState(VOICE_STATE_SESSION_RETRY); + mOutstandingRequests--; + return; + } + + LL_DEBUGS("Voice") << "region ready for voice break; url=" << url << LL_ENDL; + + LLVoiceWebRTCStats::getInstance()->provisionAttemptStart(); + LLSD body; + body["logout"] = TRUE; + body["viewer_session"] = mViewerSession; + body["voice_server_type"] = WEBRTC_VOICE_SERVER_TYPE; + + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter( + new LLCoreHttpUtil::HttpCoroutineAdapter("LLVoiceWebRTCAdHocConnection::breakVoiceConnection", + LLCore::HttpRequest::DEFAULT_POLICY_ID)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); + + httpOpts->setWantHeaders(true); + + mOutstandingRequests++; + + // tell the server to shut down the connection as a courtesy. + // shutdownConnection will drop the WebRTC connection which will + // also shut things down. + LLSD result = httpAdapter->postAndSuspend(httpRequest, url, body, httpOpts); + + mOutstandingRequests--; + setVoiceConnectionState(VOICE_STATE_SESSION_EXIT); +} + +// Tell the simulator to tell the Secondlife WebRTC server that we want a voice +// connection. The SDP is sent up as part of this, and the simulator will respond +// with an 'answer' which is in the form of another SDP. The webrtc library +// will use the offer and answer to negotiate the session. +void LLVoiceWebRTCSpatialConnection::requestVoiceConnection() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE + + LLViewerRegion *regionp = LLWorld::instance().getRegionFromID(mRegionID); + + LL_DEBUGS("Voice") << "Requesting voice connection." << LL_ENDL; + if (!regionp || !regionp->capabilitiesReceived()) + { + LL_DEBUGS("Voice") << "no capabilities for voice provisioning; waiting " << LL_ENDL; + + // try again. + setVoiceConnectionState(VOICE_STATE_REQUEST_CONNECTION); + return; + } + + std::string url = regionp->getCapability("ProvisionVoiceAccountRequest"); + if (url.empty()) + { + setVoiceConnectionState(VOICE_STATE_SESSION_RETRY); + return; + } + + LL_DEBUGS("Voice") << "region ready for voice provisioning; url=" << url << LL_ENDL; + + LLVoiceWebRTCStats::getInstance()->provisionAttemptStart(); + LLSD body; + LLSD jsep; + jsep["type"] = "offer"; + jsep["sdp"] = mChannelSDP; + body["jsep"] = jsep; + if (mParcelLocalID != INVALID_PARCEL_ID) + { + body["parcel_local_id"] = mParcelLocalID; + } + body["channel_type"] = "local"; + body["voice_server_type"] = WEBRTC_VOICE_SERVER_TYPE; + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter( + new LLCoreHttpUtil::HttpCoroutineAdapter("LLVoiceWebRTCAdHocConnection::requestVoiceConnection", + LLCore::HttpRequest::DEFAULT_POLICY_ID)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); + + httpOpts->setWantHeaders(true); + mOutstandingRequests++; + LLSD result = httpAdapter->postAndSuspend(httpRequest, url, body, httpOpts); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (status) + { + OnVoiceConnectionRequestSuccess(result); + } + else + { + switch (status.getType()) + { + case HTTP_CONFLICT: + mCurrentStatus = LLVoiceClientStatusObserver::ERROR_CHANNEL_FULL; + break; + case HTTP_UNAUTHORIZED: + mCurrentStatus = LLVoiceClientStatusObserver::ERROR_CHANNEL_LOCKED; + break; + default: + mCurrentStatus = LLVoiceClientStatusObserver::ERROR_UNKNOWN; + break; + } + setVoiceConnectionState(VOICE_STATE_SESSION_EXIT); + } + mOutstandingRequests--; +} + +void LLVoiceWebRTCConnection::OnVoiceConnectionRequestSuccess(const LLSD &result) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE + + if (LLWebRTCVoiceClient::isShuttingDown()) + { + return; + } + LLVoiceWebRTCStats::getInstance()->provisionAttemptEnd(true); + + if (result.has("viewer_session") && + result.has("jsep") && + result["jsep"].has("type") && + result["jsep"]["type"] == "answer" && + result["jsep"].has("sdp")) + { + mRemoteChannelSDP = result["jsep"]["sdp"].asString(); + mViewerSession = result["viewer_session"]; + } + else + { + LL_WARNS("Voice") << "Invalid voice provision request result:" << result << LL_ENDL; + setVoiceConnectionState(VOICE_STATE_SESSION_EXIT); + return; + } + + LL_DEBUGS("Voice") << "ProvisionVoiceAccountRequest response" + << " channel sdp " << mRemoteChannelSDP << LL_ENDL; + mWebRTCPeerConnectionInterface->AnswerAvailable(mRemoteChannelSDP); +} + +static llwebrtc::LLWebRTCPeerConnectionInterface::InitOptions getConnectionOptions() +{ + llwebrtc::LLWebRTCPeerConnectionInterface::InitOptions options; + llwebrtc::LLWebRTCPeerConnectionInterface::InitOptions::IceServers servers; + + // TODO: Pull these from login + std::string grid = LLGridManager::getInstance()->getGridLoginID(); + std::transform(grid.begin(), grid.end(), grid.begin(), [](unsigned char c){ return std::tolower(c); }); + int num_servers = 2; + if (grid == "agni") + { + num_servers = 3; + } + for (int i=1; i <= num_servers; i++) + { + servers.mUrls.push_back(llformat("stun:stun%d.%s.secondlife.io:3478", i, grid.c_str())); + } + options.mServers.push_back(servers); + return options; +} + + +// Primary state machine for negotiating a single voice connection to the +// Secondlife WebRTC server. +bool LLVoiceWebRTCConnection::connectionStateMachine() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE + + processIceUpdates(); + + switch (getVoiceConnectionState()) + { + case VOICE_STATE_START_SESSION: + { + LL_PROFILE_ZONE_NAMED_CATEGORY_VOICE("VOICE_STATE_START_SESSION") + if (mShutDown) + { + setVoiceConnectionState(VOICE_STATE_SESSION_EXIT); + break; + } + mIceCompleted = false; + setVoiceConnectionState(VOICE_STATE_WAIT_FOR_SESSION_START); + + // tell the webrtc library that we want a connection. The library will + // respond with an offer on a separate thread, which will cause + // the session state to change. + if (!mWebRTCPeerConnectionInterface->initializeConnection(getConnectionOptions())) + { + setVoiceConnectionState(VOICE_STATE_SESSION_RETRY); + } + break; + } + + case VOICE_STATE_WAIT_FOR_SESSION_START: + { + if (mShutDown) + { + setVoiceConnectionState(VOICE_STATE_SESSION_EXIT); + } + break; + } + + case VOICE_STATE_REQUEST_CONNECTION: + if (mShutDown) + { + setVoiceConnectionState(VOICE_STATE_SESSION_EXIT); + break; + } + // Ask the sim to ask the Secondlife WebRTC server for a connection to + // a given voice channel. On completion, we'll move on to the + // VOICE_STATE_SESSION_ESTABLISHED via a callback on a webrtc thread. + setVoiceConnectionState(VOICE_STATE_CONNECTION_WAIT); + LLCoros::getInstance()->launch("LLVoiceWebRTCConnection::requestVoiceConnectionCoro", + boost::bind(&LLVoiceWebRTCConnection::requestVoiceConnectionCoro, this)); + break; + + case VOICE_STATE_CONNECTION_WAIT: + if (mShutDown) + { + setVoiceConnectionState(VOICE_STATE_DISCONNECT); + } + break; + + case VOICE_STATE_SESSION_ESTABLISHED: + { + if (mShutDown) + { + setVoiceConnectionState(VOICE_STATE_DISCONNECT); + break; + } + // update the peer connection with the various characteristics of + // this connection. + mWebRTCAudioInterface->setMute(mMuted); + mWebRTCAudioInterface->setReceiveVolume(mSpeakerVolume); + LLWebRTCVoiceClient::getInstance()->OnConnectionEstablished(mChannelID, mRegionID); + setVoiceConnectionState(VOICE_STATE_WAIT_FOR_DATA_CHANNEL); + break; + } + + case VOICE_STATE_WAIT_FOR_DATA_CHANNEL: + { + if (mShutDown) + { + setVoiceConnectionState(VOICE_STATE_DISCONNECT); + break; + } + if (mWebRTCDataInterface) // the interface will be set when the session is negotiated. + { + sendJoin(); // tell the Secondlife WebRTC server that we're here via the data channel. + setVoiceConnectionState(VOICE_STATE_SESSION_UP); + if (isSpatial()) + { + LLWebRTCVoiceClient::getInstance()->updatePosition(); + LLWebRTCVoiceClient::getInstance()->sendPositionUpdate(true); + } + } + break; + } + + case VOICE_STATE_SESSION_UP: + { + mRetryWaitPeriod = 0; + mRetryWaitSecs = ((F32) rand() / (RAND_MAX)) + 0.5; + + // we'll stay here as long as the session remains up. + if (mShutDown) + { + setVoiceConnectionState(VOICE_STATE_DISCONNECT); + } + break; + } + + case VOICE_STATE_SESSION_RETRY: + // only retry ever 'n' seconds + if (mRetryWaitPeriod++ * UPDATE_THROTTLE_SECONDS > mRetryWaitSecs) + { + // something went wrong, so notify that the connection has failed. + LLWebRTCVoiceClient::getInstance()->OnConnectionFailure(mChannelID, mRegionID, mCurrentStatus); + setVoiceConnectionState(VOICE_STATE_DISCONNECT); + mRetryWaitPeriod = 0; + if (mRetryWaitSecs < MAX_RETRY_WAIT_SECONDS) + { + // back off the retry period, and do it by a small random + // bit so all clients don't reconnect at once. + mRetryWaitSecs += ((F32) rand() / (RAND_MAX)) + 0.5; + mRetryWaitPeriod = 0; + } + } + break; + + case VOICE_STATE_DISCONNECT: + setVoiceConnectionState(VOICE_STATE_WAIT_FOR_EXIT); + LLCoros::instance().launch("LLVoiceWebRTCConnection::breakVoiceConnectionCoro", + boost::bind(&LLVoiceWebRTCConnection::breakVoiceConnectionCoro, this)); + break; + + case VOICE_STATE_WAIT_FOR_EXIT: + break; + + case VOICE_STATE_SESSION_EXIT: + { + setVoiceConnectionState(VOICE_STATE_WAIT_FOR_CLOSE); + mOutstandingRequests++; + mWebRTCPeerConnectionInterface->shutdownConnection(); + break; + case VOICE_STATE_WAIT_FOR_CLOSE: + break; + case VOICE_STATE_CLOSED: + if (!mShutDown) + { + mVoiceConnectionState = VOICE_STATE_START_SESSION; + } + else + { + // if we still have outstanding http or webrtc calls, wait for them to + // complete so we don't delete objects while they still may be used. + if (mOutstandingRequests <= 0) + { + LLWebRTCVoiceClient::getInstance()->OnConnectionShutDown(mChannelID, mRegionID); + return false; + } + } + break; + } + + default: + { + LL_WARNS("Voice") << "Unknown voice control state " << getVoiceConnectionState() << LL_ENDL; + return false; + } + } + return true; +} + +// Data has been received on the webrtc data channel +// incoming data will be a json structure (if it's not binary.) We may pack +// binary for size reasons. Most of the keys in the json objects are +// single or double characters for size reasons. +// The primary element is: +// An object where each key is an agent id. (in the future, we may allow +// integer indices into an agentid list, populated on join commands. For size. +// Each key will point to a json object with keys identifying what's updated. +// 'p' - audio source power (level/volume) (int8 as int) +// 'j' - object of join data (currently only a boolean 'p' marking a primary participant) +// 'l' - boolean, always true if exists. +// 'v' - boolean - voice activity has been detected. + +// llwebrtc callback +void LLVoiceWebRTCConnection::OnDataReceived(const std::string& data, bool binary) +{ + LL::WorkQueue::postMaybe(mMainQueue, [=] { LLVoiceWebRTCConnection::OnDataReceivedImpl(data, binary); }); +} + +// +// The LLWebRTCVoiceConnection object will not be deleted +// before the webrtc connection itself is shut down, so +// we shouldn't be getting this callback on a nonexistant +// this pointer. +void LLVoiceWebRTCConnection::OnDataReceivedImpl(const std::string &data, bool binary) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE + + if (mShutDown) + { + return; + } + + if (binary) + { + LL_WARNS("Voice") << "Binary data received from data channel." << LL_ENDL; + return; + } + + Json::Reader reader; + Json::Value voice_data; + if (reader.parse(data, voice_data, false)) // don't collect comments + { + if (!voice_data.isObject()) + { + LL_WARNS("Voice") << "Expected object from data channel:" << data << LL_ENDL; + return; + } + bool new_participant = false; + Json::Value mute = Json::objectValue; + Json::Value user_gain = Json::objectValue; + for (auto &participant_id : voice_data.getMemberNames()) + { + LLUUID agent_id(participant_id); + if (agent_id.isNull()) + { + // probably a test client. + continue; + } + + LLWebRTCVoiceClient::participantStatePtr_t participant = + LLWebRTCVoiceClient::getInstance()->findParticipantByID(mChannelID, agent_id); + bool joined = false; + bool primary = false; // we ignore any 'joins' reported about participants + // that come from voice servers that aren't their primary + // voice server. This will happen with cross-region voice + // where a participant on a neighboring region may be + // connected to multiple servers. We don't want to + // add new identical participants from all of those servers. + if (voice_data[participant_id].isMember("j")) + { + // a new participant has announced that they're joining. + joined = true; + primary = voice_data[participant_id]["j"].get("p", Json::Value(false)).asBool(); + + // track incoming participants that are muted so we can mute their connections (or set their volume) + bool isMuted = LLMuteList::getInstance()->isMuted(agent_id, LLMute::flagVoiceChat); + if (isMuted) + { + mute[participant_id] = true; + } + F32 volume; + if(LLSpeakerVolumeStorage::getInstance()->getSpeakerVolume(agent_id, volume)) + { + user_gain[participant_id] = (uint32_t)(volume * 200); + } + } + + new_participant |= joined; + if (!participant && joined && (primary || !isSpatial())) + { + participant = LLWebRTCVoiceClient::getInstance()->addParticipantByID(mChannelID, agent_id, mRegionID); + } + + if (participant) + { + if (voice_data[participant_id].get("l", Json::Value(false)).asBool()) + { + // an existing participant is leaving. + if (agent_id != gAgentID) + { + LLWebRTCVoiceClient::getInstance()->removeParticipantByID(mChannelID, agent_id, mRegionID); + } + } + else + { + // we got a 'power' update. + F32 level = (F32) (voice_data[participant_id].get("p", Json::Value(participant->mLevel)).asInt()) / 128; + // convert to decibles + participant->mLevel = level; + + if (voice_data[participant_id].isMember("v")) + { + participant->mIsSpeaking = voice_data[participant_id].get("v", Json::Value(false)).asBool(); + } + + if (voice_data[participant_id].isMember("m")) + { + participant->mIsModeratorMuted = voice_data[participant_id].get("m", Json::Value(false)).asBool(); + } + } + } + } + + // tell the simulator to set the mute and volume data for this + // participant, if there are any updates. + Json::FastWriter writer; + Json::Value root = Json::objectValue; + if (mute.size() > 0) + { + root["m"] = mute; + } + if (user_gain.size() > 0) + { + root["ug"] = user_gain; + } + if (root.size() > 0) + { + std::string json_data = writer.write(root); + mWebRTCDataInterface->sendData(json_data, false); + } + } +} + +// +// The LLWebRTCVoiceConnection object will not be deleted +// before the webrtc connection itself is shut down, so +// we shouldn't be getting this callback on a nonexistant +// this pointer. +// nor should data_interface be invalid if the LLWebRTCVoiceConnection +// is shut down. + +// llwebrtc callback +void LLVoiceWebRTCConnection::OnDataChannelReady(llwebrtc::LLWebRTCDataInterface *data_interface) +{ + LL::WorkQueue::postMaybe(mMainQueue, + [=] { + if (mShutDown) + { + return; + } + + if (data_interface) + { + mWebRTCDataInterface = data_interface; + mWebRTCDataInterface->setDataObserver(this); + } + }); +} + +// tell the Secondlife WebRTC server that +// we're joining and whether we're +// joining a server associated with the +// the region we currently occupy or not (primary) +// The WebRTC voice server will pass this info +// to peers. +void LLVoiceWebRTCConnection::sendJoin() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE + + Json::FastWriter writer; + Json::Value root = Json::objectValue; + Json::Value join_obj = Json::objectValue; + LLUUID regionID = gAgent.getRegion()->getRegionID(); + if ((regionID == mRegionID) || !isSpatial()) + { + join_obj["p"] = true; + } + root["j"] = join_obj; + std::string json_data = writer.write(root); + mWebRTCDataInterface->sendData(json_data, false); +} + +///////////////////////////// +// WebRTC Spatial Connection + +LLVoiceWebRTCSpatialConnection::LLVoiceWebRTCSpatialConnection(const LLUUID ®ionID, + S32 parcelLocalID, + const std::string &channelID) : + LLVoiceWebRTCConnection(regionID, channelID), + mParcelLocalID(parcelLocalID) +{ +} + +LLVoiceWebRTCSpatialConnection::~LLVoiceWebRTCSpatialConnection() +{ +} + +void LLVoiceWebRTCSpatialConnection::setMuteMic(bool muted) +{ + if (mMuted != muted) + { + mMuted = muted; + if (mWebRTCAudioInterface) + { + LLViewerRegion *regionp = gAgent.getRegion(); + if (regionp && mRegionID == regionp->getRegionID()) + { + mWebRTCAudioInterface->setMute(muted); + } + else + { + // Always mute this agent with respect to neighboring regions. + // Peers don't want to hear this agent from multiple regions + // as that'll echo. + mWebRTCAudioInterface->setMute(true); + } + } + } +} + +///////////////////////////// +// WebRTC Spatial Connection + +LLVoiceWebRTCAdHocConnection::LLVoiceWebRTCAdHocConnection(const LLUUID ®ionID, + const std::string& channelID, + const std::string& credentials) : + LLVoiceWebRTCConnection(regionID, channelID), + mCredentials(credentials) +{ +} + +LLVoiceWebRTCAdHocConnection::~LLVoiceWebRTCAdHocConnection() +{ +} + +// Add-hoc connections require a different channel type +// as they go to a different set of Secondlife WebRTC servers. +// They also require credentials for the given channels. +// So, we have a separate requestVoiceConnection call. +void LLVoiceWebRTCAdHocConnection::requestVoiceConnection() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE + + LLViewerRegion *regionp = LLWorld::instance().getRegionFromID(mRegionID); + + LL_DEBUGS("Voice") << "Requesting voice connection." << LL_ENDL; + if (!regionp || !regionp->capabilitiesReceived()) + { + LL_DEBUGS("Voice") << "no capabilities for voice provisioning; retrying " << LL_ENDL; + // try again. + setVoiceConnectionState(VOICE_STATE_REQUEST_CONNECTION); + return; + } + + std::string url = regionp->getCapability("ProvisionVoiceAccountRequest"); + if (url.empty()) + { + setVoiceConnectionState(VOICE_STATE_SESSION_RETRY); + return; + } + + LLVoiceWebRTCStats::getInstance()->provisionAttemptStart(); + LLSD body; + LLSD jsep; + jsep["type"] = "offer"; + { + jsep["sdp"] = mChannelSDP; + } + body["jsep"] = jsep; + body["credentials"] = mCredentials; + body["channel"] = mChannelID; + body["channel_type"] = "multiagent"; + body["voice_server_type"] = WEBRTC_VOICE_SERVER_TYPE; + + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter( + new LLCoreHttpUtil::HttpCoroutineAdapter("LLVoiceWebRTCAdHocConnection::requestVoiceConnection", + LLCore::HttpRequest::DEFAULT_POLICY_ID)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); + + httpOpts->setWantHeaders(true); + mOutstandingRequests++; + LLSD result = httpAdapter->postAndSuspend(httpRequest, url, body, httpOpts); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + switch (status.getType()) + { + case HTTP_CONFLICT: + mCurrentStatus = LLVoiceClientStatusObserver::ERROR_CHANNEL_FULL; + break; + case HTTP_UNAUTHORIZED: + mCurrentStatus = LLVoiceClientStatusObserver::ERROR_CHANNEL_LOCKED; + break; + default: + mCurrentStatus = LLVoiceClientStatusObserver::ERROR_UNKNOWN; + break; + } + setVoiceConnectionState(VOICE_STATE_SESSION_EXIT); + } + else + { + OnVoiceConnectionRequestSuccess(result); + } + mOutstandingRequests--; +} diff --git a/indra/newview/llvoicewebrtc.h b/indra/newview/llvoicewebrtc.h new file mode 100644 index 0000000000..7042bbae00 --- /dev/null +++ b/indra/newview/llvoicewebrtc.h @@ -0,0 +1,743 @@ +/** + * @file llvoicewebrtc.h + * @brief Declaration of LLWebRTCVoiceClient class which is the interface to the voice client process. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#ifndef LL_VOICE_WEBRTC_H +#define LL_VOICE_WEBRTC_H + +class LLWebRTCProtocolParser; + +#include "lliopipe.h" +#include "llpumpio.h" +#include "llchainio.h" +#include "lliosocket.h" +#include "v3math.h" +#include "llframetimer.h" +#include "llviewerregion.h" +#include "llcallingcard.h" // for LLFriendObserver +#include "lleventcoro.h" +#include "llcoros.h" +#include "llparcel.h" +#include "llmutelist.h" +#include <queue> +#include "json/reader.h" + +#ifdef LL_USESYSTEMLIBS +# include "expat.h" +#else +# include "expat/expat.h" +#endif +#include "llvoiceclient.h" + +// WebRTC Includes +#include <llwebrtc.h> + +class LLAvatarName; +class LLVoiceWebRTCConnection; +typedef boost::shared_ptr<LLVoiceWebRTCConnection> connectionPtr_t; + +extern const std::string WEBRTC_VOICE_SERVER_TYPE; + +class LLWebRTCVoiceClient : public LLSingleton<LLWebRTCVoiceClient>, + virtual public LLVoiceModuleInterface, + public llwebrtc::LLWebRTCDevicesObserver, + public LLMuteListObserver +{ + LLSINGLETON_C11(LLWebRTCVoiceClient); + LOG_CLASS(LLWebRTCVoiceClient); + virtual ~LLWebRTCVoiceClient(); + +public: + /// @name LLVoiceModuleInterface virtual implementations + /// @see LLVoiceModuleInterface + //@{ + void init(LLPumpIO *pump) override; // Call this once at application startup (creates connector) + void terminate() override; // Call this to clean up during shutdown + + static bool isShuttingDown() { return sShuttingDown; } + + const LLVoiceVersionInfo& getVersion() override; + + void updateSettings() override; // call after loading settings and whenever they change + + // Returns true if WebRTC has successfully logged in and is not in error state + bool isVoiceWorking() const override; + + std::string sipURIFromID(const LLUUID &id) override; + + ///////////////////// + /// @name Tuning + //@{ + void tuningStart() override; + void tuningStop() override; + bool inTuningMode() override; + + void tuningSetMicVolume(float volume) override; + void tuningSetSpeakerVolume(float volume) override; + float tuningGetEnergy(void) override; + //@} + + ///////////////////// + /// @name Devices + //@{ + // This returns true when it's safe to bring up the "device settings" dialog in the prefs. + bool deviceSettingsAvailable() override; + bool deviceSettingsUpdated() override; //return if the list has been updated and never fetched, only to be called from the voicepanel. + + // Requery the WebRTC daemon for the current list of input/output devices. + // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed + // (use this if you want to know when it's done). + // If you pass false, you'll have no way to know when the query finishes, but the device lists will not appear empty in the interim. + void refreshDeviceLists(bool clearCurrentList = true) override; + + void setCaptureDevice(const std::string& name) override; + void setRenderDevice(const std::string& name) override; + + LLVoiceDeviceList& getCaptureDevices() override; + LLVoiceDeviceList& getRenderDevices() override; + //@} + + void getParticipantList(std::set<LLUUID> &participants) override; + bool isParticipant(const LLUUID& speaker_id) override; + + // Send a text message to the specified user, initiating the session if necessary. + // virtual BOOL sendTextMessage(const LLUUID& participant_id, const std::string& message) const {return false;}; + + // Returns true if calling back the session URI after the session has closed is possible. + // Currently this will be false only for PSTN P2P calls. + // NOTE: this will return true if the session can't be found. + BOOL isSessionCallBackPossible(const LLUUID &session_id) override; + + // WebRTC doesn't preclude text im + BOOL isSessionTextIMPossible(const LLUUID &session_id) override { return TRUE; } + + //////////////////////////// + /// @name Channel stuff + //@{ + // returns true iff the user is currently in a proximal (local spatial) channel. + // Note that gestures should only fire if this returns true. + bool inProximalChannel() override; + + void setNonSpatialChannel(const LLSD& channelInfo, bool notify_on_first_join, bool hangup_on_last_leave) override + { + startAdHocSession(channelInfo, notify_on_first_join, hangup_on_last_leave); + } + + bool setSpatialChannel(const LLSD &channelInfo) override; + + void leaveNonSpatialChannel() override; + + void processChannels(bool process) override; + + void leaveChannel(bool stopTalking); + + bool isCurrentChannel(const LLSD &channelInfo) override; + bool compareChannels(const LLSD &channelInfo1, const LLSD &channelInfo2) override; + //@} + + LLVoiceP2POutgoingCallInterface *getOutgoingCallInterface() override { return nullptr; } + + LLVoiceP2PIncomingCallInterfacePtr getIncomingCallInterface(const LLSD &voice_call_info) override { return nullptr; } + + ///////////////////////// + /// @name Volume/gain + //@{ + void setVoiceVolume(F32 volume) override; + void setMicGain(F32 volume) override; + //@} + + ///////////////////////// + /// @name enable disable voice and features + //@{ + void setVoiceEnabled(bool enabled) override; + void setMuteMic(bool muted) override; // Set the mute state of the local mic. + //@} + + ////////////////////////// + /// @name nearby speaker accessors + std::string getDisplayName(const LLUUID& id) override; + BOOL isParticipantAvatar(const LLUUID &id) override; + BOOL getIsSpeaking(const LLUUID& id) override; + BOOL getIsModeratorMuted(const LLUUID& id) override; + F32 getCurrentPower(const LLUUID& id) override; // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is... + F32 getUserVolume(const LLUUID& id) override; + void setUserVolume(const LLUUID& id, F32 volume) override; // set's volume for specified agent, from 0-1 (where .5 is nominal) + //@} + + ////////////////// + /// @name LLMuteListObserver + //@{ + void onChange() override; + void onChangeDetailed(const LLMute& ) override; + //@} + + // authorize the user + void userAuthorized(const std::string &user_id, const LLUUID &agentID) override {}; + + + void OnConnectionEstablished(const std::string& channelID, const LLUUID& regionID); + void OnConnectionShutDown(const std::string &channelID, const LLUUID ®ionID); + void OnConnectionFailure(const std::string &channelID, + const LLUUID ®ionID, + LLVoiceClientStatusObserver::EStatusType status_type = LLVoiceClientStatusObserver::ERROR_UNKNOWN); + void updatePosition(void); // update the internal position state + void sendPositionUpdate(bool force); // send the position to the voice server. + void updateOwnVolume(); + + ////////////////////////////// + /// @name Status notification + //@{ + void addObserver(LLVoiceClientStatusObserver* observer) override; + void removeObserver(LLVoiceClientStatusObserver* observer) override; + void addObserver(LLFriendObserver* observer) override; + void removeObserver(LLFriendObserver* observer) override; + void addObserver(LLVoiceClientParticipantObserver* observer) override; + void removeObserver(LLVoiceClientParticipantObserver* observer) override; + //@} + + ////////////////////////////// + /// @name Devices change notification + // LLWebRTCDevicesObserver + //@{ + void OnDevicesChanged(const llwebrtc::LLWebRTCVoiceDeviceList &render_devices, + const llwebrtc::LLWebRTCVoiceDeviceList &capture_devices) override; + //@} + void OnDevicesChangedImpl(const llwebrtc::LLWebRTCVoiceDeviceList &render_devices, + const llwebrtc::LLWebRTCVoiceDeviceList &capture_devices); + + struct participantState + { + public: + participantState(const LLUUID& agent_id, const LLUUID& region); + + bool isAvatar(); + + std::string mURI; + LLUUID mAvatarID; + std::string mDisplayName; + LLFrameTimer mSpeakingTimeout; + F32 mLevel; // the current audio level of the participant + F32 mVolume; // the gain applied to the participant + bool mIsSpeaking; + bool mIsModeratorMuted; + LLUUID mRegion; + }; + typedef boost::shared_ptr<participantState> participantStatePtr_t; + + participantStatePtr_t findParticipantByID(const std::string &channelID, const LLUUID &id); + participantStatePtr_t addParticipantByID(const std::string& channelID, const LLUUID &id, const LLUUID& region); + void removeParticipantByID(const std::string& channelID, const LLUUID &id, const LLUUID& region); + + protected: + + typedef std::map<const LLUUID, participantStatePtr_t> participantUUIDMap; + + class sessionState + { + public: + typedef boost::shared_ptr<sessionState> ptr_t; + typedef boost::weak_ptr<sessionState> wptr_t; + + typedef boost::function<void(const ptr_t &)> sessionFunc_t; + + static void addSession(const std::string &channelID, ptr_t& session); + virtual ~sessionState(); + + participantStatePtr_t addParticipant(const LLUUID& agent_id, const LLUUID& region); + void removeParticipant(const participantStatePtr_t &participant); + void removeAllParticipants(const LLUUID& region = LLUUID()); + + participantStatePtr_t findParticipantByID(const LLUUID& id); + + static ptr_t matchSessionByChannelID(const std::string& channel_id); + + void shutdownAllConnections(); + void revive(); + + static void processSessionStates(); + + virtual bool processConnectionStates(); + + virtual void sendData(const std::string &data); + + void setMuteMic(bool muted); + void setSpeakerVolume(F32 volume); + void setUserVolume(const LLUUID& id, F32 volume); + + void setUserMute(const LLUUID& id, bool mute); + + static void for_each(sessionFunc_t func); + + static void reapEmptySessions(); + + bool isEmpty() { return mWebRTCConnections.empty(); } + + virtual bool isSpatial() = 0; + virtual bool isEstate() = 0; + virtual bool isCallbackPossible() = 0; + + std::string mHandle; + std::string mChannelID; + std::string mName; + + bool mMuted; // this session is muted. + F32 mSpeakerVolume; // volume for this session. + + bool mShuttingDown; + + participantUUIDMap mParticipantsByUUID; + + static bool hasSession(const std::string &sessionID) + { return mSessions.find(sessionID) != mSessions.end(); } + + bool mHangupOnLastLeave; // notify observers after the session becomes empty. + bool mNotifyOnFirstJoin; // notify observers when the first peer joins. + + protected: + sessionState(); + std::list<connectionPtr_t> mWebRTCConnections; + + private: + + static std::map<std::string, ptr_t> mSessions; // canonical list of outstanding sessions. + + static void for_eachPredicate(const std::pair<std::string, + LLWebRTCVoiceClient::sessionState::wptr_t> &a, + sessionFunc_t func); + }; + + typedef boost::shared_ptr<sessionState> sessionStatePtr_t; + typedef std::map<std::string, sessionStatePtr_t> sessionMap; + + class estateSessionState : public sessionState + { + public: + estateSessionState(); + bool processConnectionStates() override; + + bool isSpatial() override { return true; } + bool isEstate() override { return true; } + bool isCallbackPossible() override { return false; } + }; + + class parcelSessionState : public sessionState + { + public: + parcelSessionState(const std::string& channelID, S32 parcel_local_id); + + bool isSpatial() override { return true; } + bool isEstate() override { return false; } + bool isCallbackPossible() override { return false; } + }; + + class adhocSessionState : public sessionState + { + public: + adhocSessionState(const std::string &channelID, + const std::string& credentials, + bool notify_on_first_join, + bool hangup_on_last_leave); + + bool isSpatial() override { return false; } + bool isEstate() override { return false; } + + // only p2p-type adhoc sessions allow callback + bool isCallbackPossible() override { return mNotifyOnFirstJoin && mHangupOnLastLeave; } + + // don't send spatial data to adhoc sessions. + void sendData(const std::string &data) override { } + + protected: + std::string mCredentials; + }; + + + /////////////////////////////////////////////////////// + // Private Member Functions + ////////////////////////////////////////////////////// + + static void predSendData(const LLWebRTCVoiceClient::sessionStatePtr_t &session, const std::string& spatial_data); + static void predUpdateOwnVolume(const LLWebRTCVoiceClient::sessionStatePtr_t &session, F32 audio_level); + static void predSetMuteMic(const LLWebRTCVoiceClient::sessionStatePtr_t &session, bool mute); + static void predSetSpeakerVolume(const LLWebRTCVoiceClient::sessionStatePtr_t &session, F32 volume); + static void predShutdownSession(const LLWebRTCVoiceClient::sessionStatePtr_t &session); + static void predSetUserMute(const LLWebRTCVoiceClient::sessionStatePtr_t &session, const LLUUID& id, bool mute); + static void predSetUserVolume(const LLWebRTCVoiceClient::sessionStatePtr_t &session, const LLUUID& id, F32 volume); + + //---------------------------------- + // devices + void clearCaptureDevices(); + void addCaptureDevice(const LLVoiceDevice& device); + + void clearRenderDevices(); + void addRenderDevice(const LLVoiceDevice& device); + void setDevicesListUpdated(bool state); + + ///////////////////////////// + // Sending updates of current state + void setListenerPosition(const LLVector3d &position, const LLVector3 &velocity, const LLQuaternion &rot); + void setAvatarPosition(const LLVector3d &position, const LLVector3 &velocity, const LLQuaternion &rot); + + LLVector3d getListenerPosition() { return mListenerPosition; } + LLVector3d getSpeakerPosition() { return mAvatarPosition; } + + void setEarLocation(S32 loc); + + + ///////////////////////////// + // Accessors for data related to nearby speakers + + ///////////////////////////// + sessionStatePtr_t findP2PSession(const LLUUID &agent_id); + + sessionStatePtr_t addSession(const std::string &channel_id, sessionState::ptr_t session); + void deleteSession(const sessionStatePtr_t &session); + + // Does the actual work to get out of the audio session + void leaveAudioSession(); + + friend class LLWebRTCVoiceClientCapResponder; + + + void lookupName(const LLUUID &id); + void onAvatarNameCache(const LLUUID& id, const LLAvatarName& av_name); + void avatarNameResolved(const LLUUID &id, const std::string &name); + static void predAvatarNameResolution(const LLWebRTCVoiceClient::sessionStatePtr_t &session, LLUUID id, std::string name); + + boost::signals2::connection mAvatarNameCacheConnection; + +private: + + // helper function to retrieve the audio level + // Used in multiple places. + float getAudioLevel(); + + // Coroutine support methods + //--- + void voiceConnectionCoro(); + + //--- + /// Clean up objects created during a voice session. + void cleanUp(); + + LL::WorkQueue::weak_t mMainQueue; + + bool mTuningMode; + F32 mTuningMicGain; + int mTuningSpeakerVolume; + bool mDevicesListUpdated; // set to true when the device list has been updated + // and false when the panelvoicedevicesettings has queried for an update status. + std::string mSpatialSessionCredentials; + + std::string mMainSessionGroupHandle; // handle of the "main" session group. + + sessionStatePtr_t mSession; // Session state for the current session + + sessionStatePtr_t mNextSession; // Session state for the session we're trying to join + + llwebrtc::LLWebRTCDeviceInterface *mWebRTCDeviceInterface; + + LLVoiceDeviceList mCaptureDevices; + LLVoiceDeviceList mRenderDevices; + + bool startEstateSession(); + bool startParcelSession(const std::string& channelID, S32 parcelID); + bool startAdHocSession(const LLSD &channelInfo, bool notify_on_first_join, bool hangup_on_last_leave); + + bool inSpatialChannel(); + bool inOrJoiningChannel(const std::string &channelID); + bool inEstateChannel(); + + LLSD getAudioSessionChannelInfo(); + + void setHidden(bool hidden) override; //virtual + + void enforceTether(); + + void updateNeighboringRegions(); + std::set<LLUUID> getNeighboringRegions() { return mNeighboringRegions; } + + LLVoiceVersionInfo mVoiceVersion; + + bool mSpatialCoordsDirty; + + LLVector3d mListenerPosition; + LLVector3d mListenerRequestedPosition; + LLVector3 mListenerVelocity; + LLQuaternion mListenerRot; + + LLVector3d mAvatarPosition; + LLVector3 mAvatarVelocity; + LLQuaternion mAvatarRot; + + std::set<LLUUID> mNeighboringRegions; // includes current region + + bool mMuteMic; + bool mHidden; //Set to true during teleport to hide the agent's position. + + enum + { + earLocCamera = 0, // ear at camera + earLocAvatar, // ear at avatar + earLocMixed // ear at avatar location/camera direction + }; + + S32 mEarLocation; + + float mSpeakerVolume; + + F32 mMicGain; + + bool mVoiceEnabled; + bool mProcessChannels; + + typedef std::set<LLVoiceClientParticipantObserver*> observer_set_t; + observer_set_t mParticipantObservers; + + void notifyParticipantObservers(); + + typedef std::set<LLVoiceClientStatusObserver*> status_observer_set_t; + status_observer_set_t mStatusObservers; + + void notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status); + + bool mIsInTuningMode; + bool mIsProcessingChannels; + bool mIsCoroutineActive; + + // These variables can last longer than WebRTC in coroutines so we need them as static + static bool sShuttingDown; + + LLEventMailDrop mWebRTCPump; +}; + + +class LLVoiceWebRTCStats : public LLSingleton<LLVoiceWebRTCStats> +{ + LLSINGLETON(LLVoiceWebRTCStats); + LOG_CLASS(LLVoiceWebRTCStats); + virtual ~LLVoiceWebRTCStats(); + + private: + F64SecondsImplicit mStartTime; + + U32 mConnectCycles; + + F64 mConnectTime; + U32 mConnectAttempts; + + F64 mProvisionTime; + U32 mProvisionAttempts; + + F64 mEstablishTime; + U32 mEstablishAttempts; + + public: + + void reset(); + void connectionAttemptStart(); + void connectionAttemptEnd(bool success); + void provisionAttemptStart(); + void provisionAttemptEnd(bool success); + void establishAttemptStart(); + void establishAttemptEnd(bool success); + LLSD read(); +}; + +class LLVoiceWebRTCConnection : + public llwebrtc::LLWebRTCSignalingObserver, + public llwebrtc::LLWebRTCDataObserver +{ + public: + LLVoiceWebRTCConnection(const LLUUID ®ionID, const std::string &channelID); + + virtual ~LLVoiceWebRTCConnection() = 0; + + ////////////////////////////// + /// @name Signaling notification + // LLWebRTCSignalingObserver + //@{ + void OnIceGatheringState(EIceGatheringState state) override; + void OnIceCandidate(const llwebrtc::LLWebRTCIceCandidate &candidate) override; + void OnOfferAvailable(const std::string &sdp) override; + void OnRenegotiationNeeded() override; + void OnPeerConnectionClosed() override; + void OnAudioEstablished(llwebrtc::LLWebRTCAudioInterface *audio_interface) override; + //@} + + ///////////////////////// + /// @name Data Notification + /// LLWebRTCDataObserver + //@{ + void OnDataReceived(const std::string &data, bool binary) override; + void OnDataChannelReady(llwebrtc::LLWebRTCDataInterface *data_interface) override; + //@} + + void OnDataReceivedImpl(const std::string &data, bool binary); + + void sendJoin(); + void sendData(const std::string &data); + + void processIceUpdates(); + + void processIceUpdatesCoro(); + + virtual void setMuteMic(bool muted); + virtual void setSpeakerVolume(F32 volume); + + void setUserVolume(const LLUUID& id, F32 volume); + void setUserMute(const LLUUID& id, bool mute); + + bool connectionStateMachine(); + + virtual bool isSpatial() = 0; + + LLUUID getRegionID() { return mRegionID; } + + void shutDown() + { + mShutDown = true; + } + + void OnVoiceConnectionRequestSuccess(const LLSD &body); + + protected: + typedef enum e_voice_connection_state + { + VOICE_STATE_ERROR = 0x0, + VOICE_STATE_START_SESSION = 0x1, + VOICE_STATE_WAIT_FOR_SESSION_START = 0x2, + VOICE_STATE_REQUEST_CONNECTION = 0x4, + VOICE_STATE_CONNECTION_WAIT = 0x8, + VOICE_STATE_SESSION_ESTABLISHED = 0x10, + VOICE_STATE_WAIT_FOR_DATA_CHANNEL = 0x20, + VOICE_STATE_SESSION_UP = 0x40, + VOICE_STATE_SESSION_RETRY = 0x80, + VOICE_STATE_DISCONNECT = 0x100, + VOICE_STATE_WAIT_FOR_EXIT = 0x200, + VOICE_STATE_SESSION_EXIT = 0x400, + VOICE_STATE_WAIT_FOR_CLOSE = 0x800, + VOICE_STATE_CLOSED = 0x1000, + VOICE_STATE_SESSION_STOPPING = 0x1F80 + } EVoiceConnectionState; + + EVoiceConnectionState mVoiceConnectionState; + LL::WorkQueue::weak_t mMainQueue; + + void setVoiceConnectionState(EVoiceConnectionState new_voice_connection_state) + { + if (new_voice_connection_state & VOICE_STATE_SESSION_STOPPING) + { + // the new state is shutdown or restart. + mVoiceConnectionState = new_voice_connection_state; + return; + } + if (mVoiceConnectionState & VOICE_STATE_SESSION_STOPPING) + { + // we're currently shutting down or restarting, so ignore any + // state changes. + return; + } + + mVoiceConnectionState = new_voice_connection_state; + } + EVoiceConnectionState getVoiceConnectionState() + { + return mVoiceConnectionState; + } + + virtual void requestVoiceConnection() = 0; + void requestVoiceConnectionCoro() { requestVoiceConnection(); } + + void breakVoiceConnectionCoro(); + + LLVoiceClientStatusObserver::EStatusType mCurrentStatus; + + LLUUID mRegionID; + LLUUID mViewerSession; + std::string mChannelID; + + std::string mChannelSDP; + std::string mRemoteChannelSDP; + + bool mMuted; + F32 mSpeakerVolume; + + bool mShutDown; + S32 mOutstandingRequests; + + S32 mRetryWaitPeriod; // number of UPDATE_THROTTLE_SECONDS we've + // waited since our last attempt to connect. + F32 mRetryWaitSecs; // number of seconds to wait before next retry + + + + std::vector<llwebrtc::LLWebRTCIceCandidate> mIceCandidates; + bool mIceCompleted; + + llwebrtc::LLWebRTCPeerConnectionInterface *mWebRTCPeerConnectionInterface; + llwebrtc::LLWebRTCAudioInterface *mWebRTCAudioInterface; + llwebrtc::LLWebRTCDataInterface *mWebRTCDataInterface; +}; + + +class LLVoiceWebRTCSpatialConnection : + public LLVoiceWebRTCConnection +{ + public: + LLVoiceWebRTCSpatialConnection(const LLUUID ®ionID, S32 parcelLocalID, const std::string &channelID); + + virtual ~LLVoiceWebRTCSpatialConnection(); + + void setMuteMic(bool muted) override; + + bool isSpatial() override { return true; } + + +protected: + + void requestVoiceConnection() override; + + S32 mParcelLocalID; +}; + +class LLVoiceWebRTCAdHocConnection : public LLVoiceWebRTCConnection +{ + public: + LLVoiceWebRTCAdHocConnection(const LLUUID ®ionID, const std::string &channelID, const std::string& credentials); + + virtual ~LLVoiceWebRTCAdHocConnection(); + + bool isSpatial() override { return false; } + + protected: + void requestVoiceConnection() override; + + std::string mCredentials; +}; + +#define VOICE_ELAPSED LLVoiceTimer(__FUNCTION__); + +#endif //LL_WebRTC_VOICE_CLIENT_H + diff --git a/indra/newview/skins/default/xui/de/menu_viewer.xml b/indra/newview/skins/default/xui/de/menu_viewer.xml index f6724d4993..8c446c1975 100644 --- a/indra/newview/skins/default/xui/de/menu_viewer.xml +++ b/indra/newview/skins/default/xui/de/menu_viewer.xml @@ -42,12 +42,6 @@ <menu_item_call label="Facebook..." name="Facebook"/> <menu_item_call label="Twitter..." name="Twitter"/> <menu_item_call label="Flickr..." name="Flickr"/> - <menu label="Voice-Morphing" name="VoiceMorphing"> - <menu_item_check label="Kein Voice-Morphing" name="NoVoiceMorphing"/> - <menu_item_check label="Vorschau..." name="Preview"/> - <menu_item_call label="Abonnieren..." name="Subscribe"/> - <menu_item_call label="Premium-Vorteil..." name="PremiumPerk"/> - </menu> <menu_item_check label="Gesten..." name="Gestures"/> <menu_item_check label="Freunde" name="My Friends"/> <menu_item_check label="Gruppen" name="My Groups"/> diff --git a/indra/newview/skins/default/xui/en/floater_incoming_call.xml b/indra/newview/skins/default/xui/en/floater_incoming_call.xml index 169d4c9d24..2d8089ee35 100644 --- a/indra/newview/skins/default/xui/en/floater_incoming_call.xml +++ b/indra/newview/skins/default/xui/en/floater_incoming_call.xml @@ -12,7 +12,7 @@ width="550"> <floater.string name="lifetime"> - 5 + 30 </floater.string> <floater.string name="localchat"> diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index f3d44cf647..d17aec0f6a 100644 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -561,42 +561,6 @@ parameter="conversation" /> </menu_item_check> <menu_item_separator/> - <menu - label="Voice morphing" - name="VoiceMorphing" - visibility_control="VoiceMorphingEnabled"> - <menu_item_check - label="No voice morphing" - name="NoVoiceMorphing"> - <menu_item_check.on_check - function="Communicate.VoiceMorphing.NoVoiceMorphing.Check" /> - <menu_item_check.on_click - function="Communicate.VoiceMorphing.NoVoiceMorphing.Click" /> - </menu_item_check> - <menu_item_separator/> - <menu_item_check - label="Preview..." - name="Preview"> - <menu_item_check.on_check - function="Floater.Visible" - parameter="voice_effect" /> - <menu_item_check.on_click - function="Floater.Toggle" - parameter="voice_effect" /> - </menu_item_check> - <menu_item_call - label="Subscribe..." - name="Subscribe"> - <menu_item_call.on_click - function="Communicate.VoiceMorphing.Subscribe" /> - </menu_item_call> - <menu_item_call - label="Premium perk..." - name="PremiumPerk"> - <menu_item_call.on_click - function="Communicate.VoiceMorphing.PremiumPerk" /> - </menu_item_call> - </menu> <menu_item_check label="Gestures..." name="Gestures" diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index b9d0ef0cc7..0ccb980803 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -8831,6 +8831,20 @@ New Voice Morphs are available! </notification> <notification + icon="alertmodal.tga" + name="VoiceEffectsNotSupported" + sound="UISndAlert" + persist="true" + type="alertmodal"> +Voice Morphs are not supported by this viewer. + <usetemplate + notext="Cancel" + name="okcancelbuttons" + yestext="Disable Voice Morphing"/> + <tag>voice</tag> + </notification> + + <notification icon="notifytip.tga" name="Cannot enter parcel: not a group member" type="notifytip"> diff --git a/indra/newview/skins/default/xui/en/panel_preferences_sound.xml b/indra/newview/skins/default/xui/en/panel_preferences_sound.xml index 7eaaaee536..eb38f8bff3 100644 --- a/indra/newview/skins/default/xui/en/panel_preferences_sound.xml +++ b/indra/newview/skins/default/xui/en/panel_preferences_sound.xml @@ -340,7 +340,7 @@ follows="left|top" top_delta="-6" layout="topleft" - left_pad="20" + left_pad="10" width="360" height="40" name="media_ear_location"> @@ -422,7 +422,7 @@ control_name="VoiceEarLocation" follows="left|top" layout="topleft" - left_pad="20" + left_pad="10" top_delta="-6" width="360" height="40" @@ -455,11 +455,21 @@ top_pad="10" width="237"/> <check_box + control_name="VoiceEchoCancellation" + height="15" + tool_tip="Check to enable voice echo cancellation" + label="Echo Cancellation" + layout="topleft" + left="260" + name="enable_echo_cancellation" + top_pad="-15" + width="200"/> + <check_box follows="top|left" enabled_control="EnableVoiceChat" control_name="PushToTalkToggle" height="15" - label="Toggle speak on/off when I press button in toolbar" + label="Toggle speak on/off with toolbar button" layout="topleft" left="20" name="push_to_talk_toggle_check" @@ -467,6 +477,16 @@ tool_tip="When in toggle mode, press and release the trigger key ONCE to switch your microphone on or off. When not in toggle mode, the microphone broadcasts your voice only while the trigger is being held down." top_pad="5"/> <check_box + control_name="VoiceAutomaticGainControl" + height="15" + tool_tip="Check to enable automatic gain control" + label="Automatic Gain Control" + layout="topleft" + name="voice_automatic_gain_control" + left="260" + top_pad="-15" + width="200"/> + <check_box name="gesture_audio_play_btn" control_name="EnableGestureSounds" disabled_control="MuteAudio" @@ -477,6 +497,44 @@ label="Play sounds from gestures" top_pad="5" left="20"/> + <text + layout="topleft" + height="15" + left="260" + top_pad="-12" + width="100" + name="noise_suppression_label"> + Noise Suppression + </text> + <combo_box + control_name="VoiceNoiseSuppressionLevel" + layout="topleft" + height="23" + left_pad="10" + top_pad="-18" + name="noise_suppression_combo" + width="80"> + <item + label="Off" + name="noise_suppression_none" + value="0"/> + <item + label="Low" + name="noise_suppression_low" + value="1"/> + <item + label="Moderate" + name="noise_suppression_moderate" + value="2"/> + <item + label="High" + name="noise_suppression_high" + value="3"/> + <item + label="Max" + name="noise_suppression_max" + value="4"/> + </combo_box> <button control_name="ShowDeviceSettings" follows="left|top" @@ -485,9 +543,9 @@ label="Voice Input/Output devices" layout="topleft" left="20" - top_pad="9" + top_pad="0" name="device_settings_btn" - width="230"> + width="200"> </button> <panel layout="topleft" diff --git a/indra/newview/skins/default/xui/es/menu_viewer.xml b/indra/newview/skins/default/xui/es/menu_viewer.xml index c22311f882..04e43f01da 100644 --- a/indra/newview/skins/default/xui/es/menu_viewer.xml +++ b/indra/newview/skins/default/xui/es/menu_viewer.xml @@ -42,12 +42,6 @@ <menu_item_call label="Facebook..." name="Facebook"/> <menu_item_call label="Twitter..." name="Twitter"/> <menu_item_call label="Flickr..." name="Flickr"/> - <menu label="Transformación de voz" name="VoiceMorphing"> - <menu_item_check label="Sin transformación de voz" name="NoVoiceMorphing"/> - <menu_item_check label="Probar..." name="Preview"/> - <menu_item_call label="Suscribir..." name="Subscribe"/> - <menu_item_call label="Ventaja Premium..." name="PremiumPerk"/> - </menu> <menu_item_check label="Gestos..." name="Gestures"/> <menu_item_check label="Amigos" name="My Friends"/> <menu_item_check label="Grupos" name="My Groups"/> diff --git a/indra/newview/skins/default/xui/it/menu_viewer.xml b/indra/newview/skins/default/xui/it/menu_viewer.xml index 043fd28ddb..85999ccbe0 100644 --- a/indra/newview/skins/default/xui/it/menu_viewer.xml +++ b/indra/newview/skins/default/xui/it/menu_viewer.xml @@ -42,12 +42,6 @@ <menu_item_call label="Facebook..." name="Facebook"/> <menu_item_call label="Twitter..." name="Twitter"/> <menu_item_call label="Flickr..." name="Flickr"/> - <menu label="Manipolazione voce" name="VoiceMorphing"> - <menu_item_check label="Nessuna manipolazione voce" name="NoVoiceMorphing"/> - <menu_item_check label="Anteprima..." name="Preview"/> - <menu_item_call label="Abbonati..." name="Subscribe"/> - <menu_item_call label="Vantaggio Premium..." name="PremiumPerk"/> - </menu> <menu_item_check label="Gesture..." name="Gestures"/> <menu_item_check label="Amici" name="My Friends"/> <menu_item_check label="Gruppi" name="My Groups"/> diff --git a/indra/newview/skins/default/xui/ja/menu_viewer.xml b/indra/newview/skins/default/xui/ja/menu_viewer.xml index a1f3980df4..f6b10bb121 100644 --- a/indra/newview/skins/default/xui/ja/menu_viewer.xml +++ b/indra/newview/skins/default/xui/ja/menu_viewer.xml @@ -74,7 +74,6 @@ <menu_item_check label="ボイスチャット" name="Speak"/> <menu_item_check name="Conversation Log..." label="会話ログ…"/> <menu_item_separator/> - <menu label="ボイスモーフィング" name="VoiceMorphing"/> <menu_item_check label="ジェスチャー…" name="Gestures"/> <menu_item_separator/> <menu_item_check label="フレンド" name="My Friends"/> diff --git a/indra/newview/skins/default/xui/pl/menu_viewer.xml b/indra/newview/skins/default/xui/pl/menu_viewer.xml index 4d03e7c780..ee162addd2 100644 --- a/indra/newview/skins/default/xui/pl/menu_viewer.xml +++ b/indra/newview/skins/default/xui/pl/menu_viewer.xml @@ -35,11 +35,6 @@ <menu_item_check label="Czat lokalny..." name="Nearby Chat" /> <menu_item_check label="Mowa" name="Speak" /> <menu_item_check name="Conversation Log..." label="Dziennik rozmów..." /> - <menu label="Przekształcanie głosu" name="VoiceMorphing"> - <menu_item_check label="Bez przekształcania" name="NoVoiceMorphing" /> - <menu_item_check label="Podgląd..." name="Preview" /> - <menu_item_call label="Subskrybuj..." name="Subscribe" /> - </menu> <menu_item_check label="Gesty..." name="Gestures" /> <menu_item_check label="Znajomi" name="My Friends" /> <menu_item_check label="Grupy" name="My Groups" /> diff --git a/indra/newview/skins/default/xui/pt/menu_viewer.xml b/indra/newview/skins/default/xui/pt/menu_viewer.xml index b70adc3ad2..0f4873d11c 100644 --- a/indra/newview/skins/default/xui/pt/menu_viewer.xml +++ b/indra/newview/skins/default/xui/pt/menu_viewer.xml @@ -42,12 +42,6 @@ <menu_item_call label="Facebook..." name="Facebook"/> <menu_item_call label="Twitter..." name="Twitter"/> <menu_item_call label="Flickr..." name="Flickr"/> - <menu label="Distorção de voz" name="VoiceMorphing"> - <menu_item_check label="Não distorcer voz" name="NoVoiceMorphing"/> - <menu_item_check label="Visualizar..." name="Preview"/> - <menu_item_call label="Assinar..." name="Subscribe"/> - <menu_item_call label="Benefício Premium..." name="PremiumPerk"/> - </menu> <menu_item_check label="Gestos..." name="Gestures"/> <menu_item_check label="Amigos" name="My Friends"/> <menu_item_check label="Grupos" name="My Groups"/> diff --git a/indra/newview/skins/default/xui/ru/menu_viewer.xml b/indra/newview/skins/default/xui/ru/menu_viewer.xml index 8361464f4c..4a6390329d 100644 --- a/indra/newview/skins/default/xui/ru/menu_viewer.xml +++ b/indra/newview/skins/default/xui/ru/menu_viewer.xml @@ -40,12 +40,6 @@ <menu_item_call label="Facebook..." name="Facebook"/> <menu_item_call label="Twitter..." name="Twitter"/> <menu_item_call label="Flickr..." name="Flickr"/> - <menu label="Изменение голоса" name="VoiceMorphing"> - <menu_item_check label="Без изменения голоса" name="NoVoiceMorphing"/> - <menu_item_check label="Просмотр..." name="Preview"/> - <menu_item_call label="Подписаться..." name="Subscribe"/> - <menu_item_call label="Премиум-бонус..." name="PremiumPerk"/> - </menu> <menu_item_check label="Жесты..." name="Gestures"/> <menu_item_check label="Друзья" name="My Friends"/> <menu_item_check label="Группы" name="My Groups"/> diff --git a/indra/newview/skins/default/xui/tr/menu_viewer.xml b/indra/newview/skins/default/xui/tr/menu_viewer.xml index 1c977ba5ce..fb6111248c 100644 --- a/indra/newview/skins/default/xui/tr/menu_viewer.xml +++ b/indra/newview/skins/default/xui/tr/menu_viewer.xml @@ -40,12 +40,6 @@ <menu_item_call label="Facebook..." name="Facebook"/> <menu_item_call label="Twitter..." name="Twitter"/> <menu_item_call label="Flickr..." name="Flickr"/> - <menu label="Ses şekillendirme" name="VoiceMorphing"> - <menu_item_check label="Ses şekillendirme yok" name="NoVoiceMorphing"/> - <menu_item_check label="Önizleme..." name="Preview"/> - <menu_item_call label="Abone ol..." name="Subscribe"/> - <menu_item_call label="Özel üye avantajı..." name="PremiumPerk"/> - </menu> <menu_item_check label="Mimikler..." name="Gestures"/> <menu_item_check label="Arkadaşlar" name="My Friends"/> <menu_item_check label="Gruplar" name="My Groups"/> diff --git a/indra/newview/skins/default/xui/zh/menu_viewer.xml b/indra/newview/skins/default/xui/zh/menu_viewer.xml index 972434dfc5..a048af7b68 100644 --- a/indra/newview/skins/default/xui/zh/menu_viewer.xml +++ b/indra/newview/skins/default/xui/zh/menu_viewer.xml @@ -40,12 +40,6 @@ <menu_item_call label="臉書…" name="Facebook"/> <menu_item_call label="推特…" name="Twitter"/> <menu_item_call label="Flickr…" name="Flickr"/> - <menu label="語音變聲" name="VoiceMorphing"> - <menu_item_check label="沒有變聲效果" name="NoVoiceMorphing"/> - <menu_item_check label="預覽……" name="Preview"/> - <menu_item_call label="訂閱……" name="Subscribe"/> - <menu_item_call label="付費會員獨享…" name="PremiumPerk"/> - </menu> <menu_item_check label="姿勢…" name="Gestures"/> <menu_item_check label="朋友" name="My Friends"/> <menu_item_check label="群組" name="My Groups"/> diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 8359fc5cf9..30fe24506e 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -547,6 +547,12 @@ class Windows_x86_64_Manifest(ViewerManifest): # Get shared libs from the shared libs staging directory with self.prefix(src=os.path.join(self.args['build'], os.pardir, 'sharedlibs', self.args['buildtype'])): + # WebRTC libraries + for libfile in ( + 'llwebrtc.dll', + ): + self.path(libfile) + # Get fmodstudio dll if needed if self.args['fmodstudio'] == 'ON': if(self.args['buildtype'].lower() == 'debug'): @@ -990,6 +996,20 @@ class Darwin_x86_64_Manifest(ViewerManifest): print("Skipping %s" % dst) return added + # WebRTC libraries + with self.prefix(src=os.path.join(self.args['build'], os.pardir, + 'sharedlibs', self.args['buildtype'], 'Resources')): + for libfile in ( + 'libllwebrtc.dylib', + ): + self.path(libfile) + + oldpath = os.path.join("@rpath", libfile) + self.run_command( + ['install_name_tool', '-change', oldpath, + '@executable_path/../Resources/%s' % libfile, + executable]) + # dylibs is a list of all the .dylib files we expect to need # in our bundled sub-apps. For each of these we'll create a # symlink from sub-app/Contents/Resources to the real .dylib. diff --git a/scripts/messages/message_template.msg.sha1 b/scripts/messages/message_template.msg.sha1 index 5ad85458e9..47291dcd66 100755 --- a/scripts/messages/message_template.msg.sha1 +++ b/scripts/messages/message_template.msg.sha1 @@ -1 +1 @@ -e3bd0529a647d938ab6d48f26d21dd52c07ebc6e
\ No newline at end of file +dd15c52581b3fe99e072b26872deba2560893fc4 |