diff options
53 files changed, 8384 insertions, 2169 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/autobuild.xml b/autobuild.xml index 13c9e7ec35..ac31336b26 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> @@ -455,18 +455,6 @@ </map> <key>emoji_shortcodes</key> <map> - <key>canonical_repo</key> - <string>https://github.com/secondlife/3p-emoji-shortcodes</string> - <key>copyright</key> - <string>Copyright 2017-2019 Miles Johnson.</string> - <key>description</key> - <string>Emoji shortcodes</string> - <key>license</key> - <string>MIT</string> - <key>license_file</key> - <string>LICENSES/emojibase-license.txt</string> - <key>name</key> - <string>emoji_shortcodes</string> <key>platforms</key> <map> <key>darwin64</key> @@ -494,8 +482,20 @@ <string>windows64</string> </map> </map> + <key>license</key> + <string>MIT</string> + <key>license_file</key> + <string>LICENSES/emojibase-license.txt</string> + <key>copyright</key> + <string>Copyright 2017-2019 Miles Johnson.</string> <key>version</key> <string>6.1.0.579438</string> + <key>name</key> + <string>emoji_shortcodes</string> + <key>canonical_repo</key> + <string>https://github.com/secondlife/3p-emoji-shortcodes</string> + <key>description</key> + <string>Emoji shortcodes</string> </map> <key>expat</key> <map> @@ -568,11 +568,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> @@ -584,11 +584,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> @@ -600,11 +600,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> @@ -617,7 +617,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> @@ -655,16 +655,6 @@ </map> <key>freetype</key> <map> - <key>copyright</key> - <string>Copyright 2006, 2007, 2008, 2009, 2010 by David Turner, Robert Wilhelm, and Werner Lemberg.</string> - <key>description</key> - <string>Font rendering library</string> - <key>license</key> - <string>FreeType</string> - <key>license_file</key> - <string>LICENSES/freetype.txt</string> - <key>name</key> - <string>freetype</string> <key>platforms</key> <map> <key>darwin64</key> @@ -724,8 +714,18 @@ <string>windows64</string> </map> </map> + <key>license</key> + <string>FreeType</string> + <key>license_file</key> + <string>LICENSES/freetype.txt</string> + <key>copyright</key> + <string>Copyright 2006, 2007, 2008, 2009, 2010 by David Turner, Robert Wilhelm, and Werner Lemberg.</string> <key>version</key> <string>2.12.1.557becd</string> + <key>name</key> + <string>freetype</string> + <key>description</key> + <string>Font rendering library</string> </map> <key>glext</key> <map> @@ -768,11 +768,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> @@ -785,7 +785,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> @@ -904,11 +904,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> @@ -932,11 +932,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> @@ -957,18 +957,6 @@ </map> <key>icu4c</key> <map> - <key>canonical_repo</key> - <string>https://bitbucket.org/lindenlab/3p-icu4c</string> - <key>copyright</key> - <string>Copyright (c) 1995-2011 International Business Machines Corporation and others <http://source.icu-project.org></string> - <key>description</key> - <string>ICU is a mature, widely used set of C/C++ and Java libraries providing Unicode and Globalization support for software applications. ICU is widely portable and gives applications the same results on all platforms and between C/C++ and Java software.</string> - <key>license</key> - <string>ICU, permissive non-copyleft free software license</string> - <key>license_file</key> - <string>LICENSES/icu.txt</string> - <key>name</key> - <string>icu4c</string> <key>platforms</key> <map> <key>darwin64</key> @@ -1000,8 +988,20 @@ <string>windows64</string> </map> </map> + <key>license</key> + <string>ICU, permissive non-copyleft free software license</string> + <key>license_file</key> + <string>LICENSES/icu.txt</string> + <key>copyright</key> + <string>Copyright (c) 1995-2011 International Business Machines Corporation and others <http://source.icu-project.org></string> <key>version</key> <string>4.8.1-7d08d82</string> + <key>name</key> + <string>icu4c</string> + <key>canonical_repo</key> + <string>https://bitbucket.org/lindenlab/3p-icu4c</string> + <key>description</key> + <string>ICU is a mature, widely used set of C/C++ and Java libraries providing Unicode and Globalization support for software applications. ICU is widely portable and gives applications the same results on all platforms and between C/C++ and Java software.</string> </map> <key>jpegencoderbasic</key> <map> @@ -1190,11 +1190,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> @@ -1206,11 +1206,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> @@ -1222,31 +1222,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> @@ -1255,7 +1239,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> @@ -1547,15 +1531,6 @@ </map> <key>llca</key> <map> - <key>copyright</key> - <string>Copyright (c) 2016, Linden Research, Inc.; data provided by the Mozilla NSS Project. - </string> - <key>license</key> - <string>mit</string> - <key>license_file</key> - <string>LICENSES/ca-license.txt</string> - <key>name</key> - <string>llca</string> <key>platforms</key> <map> <key>common</key> @@ -1563,18 +1538,27 @@ <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> </map> </map> + <key>license</key> + <string>mit</string> + <key>license_file</key> + <string>LICENSES/ca-license.txt</string> + <key>copyright</key> + <string>Copyright (c) 2016, Linden Research, Inc.; data provided by the Mozilla NSS Project. + </string> <key>version</key> - <string>202402012004.0</string> + <string>202310121530.0</string> + <key>name</key> + <string>llca</string> </map> <key>llphysicsextensions_source</key> <map> @@ -1587,11 +1571,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> @@ -1603,11 +1587,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> @@ -1619,11 +1603,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> @@ -1636,7 +1620,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> @@ -1668,6 +1652,18 @@ <key>name</key> <string>linux64</string> </map> + <key>windows</key> + <map> + <key>archive</key> + <map> + <key>hash</key> + <string>2e5f1f7046a49d8b0bc295aa878116bc</string> + <key>url</key> + <string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/60043/564063/llphysicsextensions_stub-1.0.542456-windows-542456.tar.bz2</string> + </map> + <key>name</key> + <string>windows</string> + </map> </map> <key>license</key> <string>internal</string> @@ -1815,42 +1811,36 @@ <key>archive</key> <map> <key>hash</key> - <string>6cc1585dba85b0226a2e7033a7e2a2ceaae7c983</string> - <key>hash_algorithm</key> - <string>sha1</string> + <string>b48b7ac0792d3ea8f087d99d9e4a29d8</string> <key>url</key> - <string>https://github.com/secondlife/3p-mikktspace/releases/download/v1-5cee1f4/mikktspace-1-darwin64-5cee1f4.tar.zst</string> + <string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/104415/914944/mikktspace-1-darwin64-574859.tar.bz2</string> </map> <key>name</key> <string>darwin64</string> </map> - <key>windows64</key> + <key>windows</key> <map> <key>archive</key> <map> <key>hash</key> - <string>6b7d01ad54e4a88a001f66840c32329cedb28202</string> - <key>hash_algorithm</key> - <string>sha1</string> + <string>0a016b9c0c1e2c0b557e0124094da6c5</string> <key>url</key> - <string>https://github.com/secondlife/3p-mikktspace/releases/download/v1-5cee1f4/mikktspace-1-windows64-5cee1f4.tar.zst</string> + <string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/104407/914918/mikktspace-1-windows-574859.tar.bz2</string> </map> <key>name</key> - <string>windows64</string> + <string>windows</string> </map> - <key>linux64</key> + <key>windows64</key> <map> <key>archive</key> <map> <key>hash</key> - <string>edc9782bf209e17ad1845498b42f16d733582082</string> - <key>hash_algorithm</key> - <string>sha1</string> + <string>02e9e5b6fe6788f4d2babb83ec544843</string> <key>url</key> - <string>https://github.com/secondlife/3p-mikktspace/releases/download/v1-5cee1f4/mikktspace-1-linux64-5cee1f4.tar.zst</string> + <string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/104406/914909/mikktspace-1-windows64-574859.tar.bz2</string> </map> <key>name</key> - <string>linux64</string> + <string>windows64</string> </map> </map> <key>license</key> @@ -1932,18 +1922,6 @@ </map> <key>nanosvg</key> <map> - <key>canonical_repo</key> - <string>https://bitbucket.org/lindenlab/3p-nanosvg</string> - <key>copyright</key> - <string>Copyright (c) 2013-14 Mikko Mononen</string> - <key>description</key> - <string>NanoSVG is a simple single-header-file SVG parser and rasterizer</string> - <key>license</key> - <string>Zlib</string> - <key>license_file</key> - <string>LICENSES/nanosvg.txt</string> - <key>name</key> - <string>nanosvg</string> <key>platforms</key> <map> <key>darwin64</key> @@ -1983,8 +1961,20 @@ <string>windows64</string> </map> </map> + <key>license</key> + <string>Zlib</string> + <key>license_file</key> + <string>LICENSES/nanosvg.txt</string> + <key>copyright</key> + <string>Copyright (c) 2013-14 Mikko Mononen</string> <key>version</key> <string>2022.09.27</string> + <key>name</key> + <string>nanosvg</string> + <key>canonical_repo</key> + <string>https://bitbucket.org/lindenlab/3p-nanosvg</string> + <key>description</key> + <string>NanoSVG is a simple single-header-file SVG parser and rasterizer</string> </map> <key>nghttp2</key> <map> @@ -2519,11 +2509,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> @@ -2557,11 +2545,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> @@ -2571,11 +2557,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> @@ -2588,7 +2574,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> @@ -2694,16 +2680,6 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> </map> <key>viewer-fonts</key> <map> - <key>copyright</key> - <string>Copyright 2016-2022 Brad Erickson CC-BY-4.0/MIT, Copyright 2016-2022 Twitter, Inc. CC-BY-4.0, Copyright 2013 Joe Loughry and Terence Eden MIT</string> - <key>description</key> - <string>Viewer fonts</string> - <key>license</key> - <string>Various open source</string> - <key>license_file</key> - <string>LICENSES/fonts.txt</string> - <key>name</key> - <string>viewer-fonts</string> <key>platforms</key> <map> <key>darwin64</key> @@ -2731,8 +2707,18 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <string>windows64</string> </map> </map> + <key>license</key> + <string>Various open source</string> + <key>license_file</key> + <string>LICENSES/fonts.txt</string> + <key>copyright</key> + <string>Copyright 2016-2022 Brad Erickson CC-BY-4.0/MIT, Copyright 2016-2022 Twitter, Inc. CC-BY-4.0, Copyright 2013 Joe Loughry and Terence Eden MIT</string> <key>version</key> <string>1.579464</string> + <key>name</key> + <string>viewer-fonts</string> + <key>description</key> + <string>Viewer fonts</string> </map> <key>viewer-manager</key> <map> @@ -2906,29 +2892,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> @@ -2936,6 +2944,76 @@ 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>creds</key> + <string>github</string> + <key>hash</key> + <string>e89de3c1352589be64fd838f9b4c06927a139db3</string> + <key>hash_algorithm</key> + <string>sha1</string> + <key>url</key> + <string>https://github.com/secondlife/3p-webrtc-build/releases/download/m114.5735.08.59-debug/webrtc-m114.5735.08.59-debug.8823062977-darwin64-8823062977.tar.zst</string> + </map> + <key>name</key> + <string>darwin64</string> + </map> + <key>linux64</key> + <map> + <key>archive</key> + <map> + <key>hash</key> + <string>b19afcf0dc6775c27f2d2e18f558f39180d04144</string> + <key>hash_algorithm</key> + <string>sha1</string> + <key>url</key> + <string>https://github.com/secondlife/3p-webrtc-build/releases/download/m114.5735.08.59-debug/webrtc-m114.5735.08.59-debug.8823062977-linux64-8823062977.tar.zst</string> + </map> + <key>name</key> + <string>linux64</string> + </map> + <key>windows64</key> + <map> + <key>archive</key> + <map> + <key>creds</key> + <string>github</string> + <key>hash</key> + <string>abbd27bdbeec9b8c01ab7c176bee7bacbf7252f5</string> + <key>hash_algorithm</key> + <string>sha1</string> + <key>url</key> + <string>https://github.com/secondlife/3p-webrtc-build/releases/download/m114.5735.08.59-debug/webrtc-m114.5735.08.59-debug.8823062977-windows64-8823062977.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.59-debug.8823062977</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> @@ -2945,14 +3023,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> @@ -2962,7 +3080,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> @@ -3030,6 +3148,62 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <key>description</key> <string>zlib data compression library for the next generation systems</string> </map> + <key>webrtc-shim</key> + <map> + <key>platforms</key> + <map> + <key>darwin64</key> + <map> + <key>archive</key> + <map> + <key>creds</key> + <string>github</string> + <key>hash</key> + <string>a23ffe29c49f8fabb8c5f2de9879bed9d7e0e0ca</string> + <key>hash_algorithm</key> + <string>sha1</string> + <key>url</key> + <string>https://api.github.com/repos/secondlife/3p-webrtc-shim/releases/assets/155507516</string> + </map> + <key>name</key> + <string>darwin64</string> + </map> + <key>windows64</key> + <map> + <key>archive</key> + <map> + <key>creds</key> + <string>github</string> + <key>hash</key> + <string>b264c6ed008bd45c4687c5dc4d7532727c74624a</string> + <key>hash_algorithm</key> + <string>sha1</string> + <key>url</key> + <string>https://api.github.com/repos/secondlife/3p-webrtc-shim/releases/assets/155507520</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.0.26.d4b8b92</string> + <key>name</key> + <string>webrtc-shim</string> + <key>vcs_branch</key> + <string>main</string> + <key>vcs_revision</key> + <string>d4b8b921825ae4344d87fdb0c9179c358c6e3698</string> + <key>vcs_url</key> + <string>https://github.com/secondlife/3p-webrtc-shim</string> + <key>canonical_repo</key> + <string>https://github.com/secondlife/3p-webrtc-shim</string> + </map> </map> <key>package_description</key> <map> 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 500ffa3e8b..422927704a 100644 --- a/indra/CMakeLists.txt +++ b/indra/CMakeLists.txt @@ -54,6 +54,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 24534c98d9..26a4162e42 100644 --- a/indra/cmake/00-Common.cmake +++ b/indra/cmake/00-Common.cmake @@ -182,6 +182,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 05c51c018d..cce1c042bd 100644 --- a/indra/cmake/CMakeLists.txt +++ b/indra/cmake/CMakeLists.txt @@ -64,6 +64,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..a9adc41831 --- /dev/null +++ b/indra/cmake/WebRTC.cmake @@ -0,0 +1,32 @@ +# -*- 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") +use_prebuilt_binary(webrtc) + +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 ) +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 51ce163b4e..aaa868352a 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..29dc1df8d6 --- /dev/null +++ b/indra/llwebrtc/CMakeLists.txt @@ -0,0 +1,60 @@ +# -*- 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) + +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++ -lc++abi") + + +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) diff --git a/indra/llwebrtc/llwebrtc.cpp b/indra/llwebrtc/llwebrtc.cpp new file mode 100644 index 0000000000..97c04ae446 --- /dev/null +++ b/indra/llwebrtc/llwebrtc.cpp @@ -0,0 +1,1324 @@ +/** + * @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" + +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) +{ + 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]; + 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 / (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 = true; + apm_config.gain_controller1.mode = webrtc::AudioProcessing::Config::GainController1::kAdaptiveAnalog; + apm_config.gain_controller2.enabled = true; + 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(); + } + mPeerConnections.clear(); + + mSignalingThread->BlockingCall([this]() { mPeerConnectionFactory = nullptr; }); + 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 = true; + apm_config.gain_controller1.mode = webrtc::AudioProcessing::Config::GainController1::kAdaptiveAnalog; + apm_config.gain_controller2.enabled = true; + 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()); } + + +// +// 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()) + { + (*it)->terminate(); + mPeerConnections.erase(it); + } + if (mPeerConnections.empty()) + { + setRecording(false); + } +} + + +// +// LLWebRTCPeerConnectionImpl implementation. +// +// Most peer connection (signaling) happens on +// the signaling thread. + +LLWebRTCPeerConnectionImpl::LLWebRTCPeerConnectionImpl() : + mWebRTCImpl(nullptr), + mClosing(false), + mPeerConnection(nullptr), + mMute(false), + mAnswerReceived(false) +{ +} + +LLWebRTCPeerConnectionImpl::~LLWebRTCPeerConnectionImpl() +{ + terminate(); + mSignalingObserverList.clear(); + mDataObserverList.clear(); +} + +// +// LLWebRTCPeerConnection interface +// + +void LLWebRTCPeerConnectionImpl::init(LLWebRTCImpl * webrtc_impl) +{ + mWebRTCImpl = webrtc_impl; + mPeerConnectionFactory = mWebRTCImpl->getPeerConnectionFactory(); +} +void LLWebRTCPeerConnectionImpl::terminate() +{ + rtc::scoped_refptr<webrtc::PeerConnectionInterface> connection; + mPeerConnection.swap(connection); + rtc::scoped_refptr<webrtc::DataChannelInterface> dataChannel; + mDataChannel.swap(dataChannel); + rtc::scoped_refptr<webrtc::MediaStreamInterface> localStream; + mLocalStream.swap(localStream); + + mWebRTCImpl->PostSignalingTask( + [=]() + { + if (connection) + { + connection->Close(); + } + if (dataChannel) + { + dataChannel->UnregisterObserver(); + dataChannel->Close(); + } + }); +} + +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() +{ + mClosing = true; + 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); + } + } + }); +} + +// +// 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..ac71e0c744 --- /dev/null +++ b/indra/llwebrtc/llwebrtc.h @@ -0,0 +1,274 @@ +/** + * @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 +}; + +// 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 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..984aaef734 --- /dev/null +++ b/indra/llwebrtc/llwebrtc_impl.h @@ -0,0 +1,379 @@ +/** + * @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; } + + 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; +}; + + +// 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; + + // + // 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; + + bool mClosing; + + 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 a6ae041935..4b6135c24c 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -693,6 +693,7 @@ set(viewer_SOURCE_FILES llvoiceclient.cpp llvoicevisualizer.cpp llvoicevivox.cpp + llvoicewebrtc.cpp llvoinventorylistener.cpp llvopartgroup.cpp llvosky.cpp @@ -1340,6 +1341,7 @@ set(viewer_HEADER_FILES llvoiceclient.h llvoicevisualizer.h llvoicevivox.h + llvoicewebrtc.h llvoinventorylistener.h llvopartgroup.h llvosky.h @@ -1440,6 +1442,7 @@ if (LINUX) endif (LINUX) if (WINDOWS) + list(APPEND viewer_SOURCE_FILES llappviewerwin32.cpp llwindebug.cpp @@ -1713,6 +1716,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 @@ -1779,13 +1783,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) @@ -1909,6 +1914,7 @@ target_link_libraries(${VIEWER_BINARY_NAME} llcorehttp llcommon llmeshoptimizer + llwebrtc ll::ndof lllogin llprimitive diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 2f7c256b49..56c16ed9de 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -15086,6 +15086,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> @@ -15100,13 +15133,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 13501833b2..0974950274 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 d6a4c41497..b88ab845fe 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -3360,15 +3360,15 @@ 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(), + std::ostringstream version_string; + 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(); } @@ -5148,7 +5148,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 9352dba06d..063fdd980c 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 313339f131..156b5b4047 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/llconversationview.cpp b/indra/newview/llconversationview.cpp index 48c7df40df..42194c9c16 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 ed2a2807b5..c6868ffeda 100644 --- a/indra/newview/llfloaterimsession.cpp +++ b/indra/newview/llfloaterimsession.cpp @@ -552,7 +552,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 28464fc14b..fc431f3ced 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( diff --git a/indra/newview/llgroupactions.cpp b/indra/newview/llgroupactions.cpp index 380e49c320..9d39da148c 100644 --- a/indra/newview/llgroupactions.cpp +++ b/indra/newview/llgroupactions.cpp @@ -254,7 +254,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; diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index ce4a032b27..c9b4454f3f 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -87,8 +87,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); @@ -108,7 +124,7 @@ BOOL LLSessionTimeoutTimer::tick() { gIMMgr->showSessionStartError("session_initialization_timed_out_error", mSessionId); } - return TRUE; + return TRUE; } @@ -137,7 +153,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) } @@ -396,7 +412,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); @@ -408,6 +424,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); @@ -437,7 +463,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 @@ -503,7 +568,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 @@ -643,7 +708,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), @@ -653,43 +724,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. @@ -699,8 +757,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 @@ -730,6 +787,68 @@ LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, const std::string& } } +void LLIMModel::LLIMSession::initVoiceChannel(const LLSD& voiceChannelInfo) +{ + mVoiceChannelStateChangeConnection.disconnect(); + + if (mVoiceChannel) + { + 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(); @@ -833,7 +952,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); } @@ -849,22 +968,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). @@ -881,7 +984,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); + } } } @@ -1439,7 +1545,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()) { @@ -1453,7 +1559,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 @@ -1465,11 +1571,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) @@ -1717,7 +1823,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 LLSD& voice_channel_info ) const { LLIMSession* session = findIMSession(session_id); if (!session) @@ -1725,6 +1831,14 @@ LLVoiceChannel* LLIMModel::getVoiceChannel( const LLUUID& session_id ) const LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL; return NULL; } + if (IM_NOTHING_SPECIAL == session->mType || IM_SESSION_P2P_INVITE == session->mType) + { + LLVoiceP2POutgoingCallInterface *outgoingInterface = LLVoiceClient::getInstance()->getOutgoingCallInterface(voice_channel_info); + if ((outgoingInterface != NULL) != (dynamic_cast<LLVoiceChannelP2P *>(session->mVoiceChannel) != NULL)) + { + session->initVoiceChannel(voice_channel_info); + } + } return session->mVoiceChannel; } @@ -2018,7 +2132,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 ) { @@ -2034,7 +2149,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++) @@ -2049,8 +2164,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 @@ -2065,7 +2180,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; } @@ -2309,6 +2433,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; @@ -2651,15 +2781,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(); @@ -2671,7 +2801,7 @@ void LLIncomingCallDialog::onLifetimeExpired() LLUUID session_id = mPayload["session_id"].asUUID(); gIMMgr->clearPendingAgentListUpdates(session_id); gIMMgr->clearPendingInvitation(session_id); - closeFloater(); + LLIncomingCallDialog::onReject(this); } } @@ -2836,6 +2966,10 @@ void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload LLUUID session_id = payload["session_id"].asUUID(); LLUUID caller_id = payload["caller_id"].asUUID(); std::string session_name = payload["session_name"].asString(); + if (session_name.empty()) + { + session_name = payload["caller_name"].asString(); + } EInstantMessage type = (EInstantMessage)payload["type"].asInteger(); LLIMMgr::EInvitationType inv_type = (LLIMMgr::EInvitationType)payload["inv_type"].asInteger(); bool voice = true; @@ -2852,14 +2986,11 @@ void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload { // 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 { @@ -2905,7 +3036,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"); @@ -2913,8 +3044,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") @@ -2935,114 +3065,48 @@ void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload { if (type == IM_SESSION_P2P_INVITE) { - if(LLVoiceClient::getInstance()) + // 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) { - 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) - { - // 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; - LLSD data; - data["method"] = "decline invitation"; - data["session-id"] = session_id; - LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, data, - "Invitation declined.", - "Invitation decline failed."); + LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, data, "Invitation declined", "Invitation decline failed."); + } } } gIMMgr->clearPendingAgentListUpdates(session_id); gIMMgr->clearPendingInvitation(session_id); - break; } - - return false; } // @@ -3116,7 +3180,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) @@ -3278,22 +3342,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 @@ -3303,11 +3356,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; } @@ -3317,7 +3371,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()) @@ -3350,7 +3405,9 @@ LLUUID LLIMMgr::addSession( bool new_session = (LLIMModel::getInstance()->findIMSession(session_id) == 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) @@ -3363,7 +3420,7 @@ LLUUID LLIMMgr::addSession( //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 @@ -3424,9 +3481,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"; @@ -3467,11 +3530,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) { @@ -3496,7 +3559,6 @@ void LLIMMgr::inviteToSession( LLIncomingCallDialog::processCallResponse(0, payload); return; } - if (voice_invite) { bool isRejectGroupCall = (gSavedSettings.getBOOL("VoiceCallsRejectGroup") && (notify_box_type == "VoiceInviteGroup")); @@ -3520,7 +3582,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; @@ -3535,6 +3597,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, @@ -3588,6 +3653,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 ) { @@ -3745,11 +3844,10 @@ 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); + LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(session_id, voice_channel_info); if (!voice_channel) return false; - voice_channel->setCallDirection(direction); voice_channel->activate(); return true; @@ -3757,7 +3855,7 @@ bool LLIMMgr::startCall(const LLUUID& session_id, LLVoiceChannel::EDirection dir bool LLIMMgr::endCall(const LLUUID& session_id) { - LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(session_id); + LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(session_id, LLSD()); if (!voice_channel) return false; voice_channel->deactivate(); @@ -4051,6 +4149,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(); + } + } } }; @@ -4135,7 +4242,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") ) @@ -4146,13 +4253,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 bace97d37a..16444a6755 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 @@ -284,7 +287,7 @@ public: * Get voice channel for the session specified by session_id * Returns NULL if the session does not exist */ - LLVoiceChannel* getVoiceChannel(const LLUUID& session_id) const; + LLVoiceChannel* getVoiceChannel(const LLUUID& session_id, const LLSD& voice_channel_info = LLSD()) const; /** * Get im speaker manager for the 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 @@ -415,9 +417,9 @@ public: const LLUUID& caller, const std::string& caller_name, EInstantMessage type, - EInvitationType inv_type, - const std::string& session_handle = LLStringUtil::null, - const std::string& session_uri = LLStringUtil::null); + EInvitationType inv_type, + 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 ab255d5215..a06b50390f 100644 --- a/indra/newview/llpanelgroup.cpp +++ b/indra/newview/llpanelgroup.cpp @@ -276,7 +276,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 be40b08a6d..3ca6426887 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 13b52e97c5..27019badd2 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 14205cebe2..e4484b66c6 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 8114e05a94..fd46c4ca7d 100644 --- a/indra/newview/llpanelprofile.cpp +++ b/indra/newview/llpanelprofile.cpp @@ -1446,7 +1446,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) { diff --git a/indra/newview/llpanelprofile.h b/indra/newview/llpanelprofile.h index 11632a10ae..ea2bb25ac2 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 af57169f3b..0f87bf197d 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 60bada8f58..00691d4382 100644 --- a/indra/newview/llspeakers.cpp +++ b/indra/newview/llspeakers.cpp @@ -618,7 +618,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) diff --git a/indra/newview/llspeakers.h b/indra/newview/llspeakers.h index 22c9481687..aa594d1e13 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/llstartup.cpp b/indra/newview/llstartup.cpp index a0324ca82a..82f16a1518 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -3365,7 +3365,7 @@ LLSD transform_cert_args(LLPointer<LLCertificate> cert) // are actually arrays, and we want to format them as comma separated // strings, so special case those. LLSDSerialize::toXML(cert_info[iter->first], std::cout); - if((iter->first == std::string(CERT_KEY_USAGE)) || + if((iter->first== std::string(CERT_KEY_USAGE)) || (iter->first == std::string(CERT_EXTENDED_KEY_USAGE))) { value = ""; diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp index 7738cb904e..44173a8043 100644 --- a/indra/newview/llviewercontrol.cpp +++ b/indra/newview/llviewercontrol.cpp @@ -803,6 +803,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 3181ae6283..6374d68988 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/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index eba7189a82..e7489fbc5f 100755 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -3144,6 +3144,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 622490c881..bff424b38a 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 @@ -543,7 +544,7 @@ public: U8 mCentralBakeVersion; LLVOCacheEntry* mLastVisitedEntry; - U32 mInvisibilityCheckHistory; + U32 mInvisibilityCheckHistory; // Information for Homestead / CR-53 S32 mClassID; diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index 8ecfa3eed1..b5ef3bf19b 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; diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h index 4bb0c8aa73..30135037e7 100644 --- a/indra/newview/llvoavatar.h +++ b/indra/newview/llvoavatar.h @@ -106,9 +106,10 @@ public: virtual void markDead(); static void initClass(); // Initialize data that's only init'd once per class. static void cleanupClass(); // Cleanup data that's only init'd once per class. - virtual void initInstance(); // Called after construction to initialize the class. + 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/llvoicechannel.cpp b/indra/newview/llvoicechannel.cpp index b0eb8d962c..f9d4a7e222 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()) { 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 = channelInfo; + } + if (!LLVoiceClient::getInstance()->compareChannels(mChannelInfo, channelInfo)) { return; } @@ -153,18 +147,21 @@ void LLVoiceChannel::handleStatusChange(EStatusType type) switch(type) { case STATUS_LOGIN_RETRY: - // no user notice + // no user notice break; 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,65 +420,64 @@ 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::addSpeakersToRecent(mSessionID); + } + else { - 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()) + 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) { std::string url = region->getCapability("ChatSessionRequest"); - LLCoros::instance().launch("LLVoiceChannelGroup::voiceCallCapCoro", - boost::bind(&LLVoiceChannelGroup::voiceCallCapCoro, this, url)); + LLCoros::instance().launch("LLVoiceChannelGroup::voiceCallCapCoro", + boost::bind(&LLVoiceChannelGroup::voiceCallCapCoro, this, url)); } } -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.isUndefined()) { 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 @@ -604,61 +588,69 @@ void LLVoiceChannelGroup::setState(EState state) void LLVoiceChannelGroup::voiceCallCapCoro(std::string url) { - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("voiceCallCapCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - LLSD postData; - postData["method"] = "call"; - postData["session-id"] = mSessionID; + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("voiceCallCapCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LL_INFOS("Voice", "voiceCallCapCoro") << "Generic POST for " << url << LL_ENDL; + 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; - LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData); + LL_INFOS("Voice", "voiceCallCapCoro") << "Generic POST for " << url << LL_ENDL; - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData); - LLVoiceChannel* channelp = LLVoiceChannel::getChannelByID(mSessionID); - if (!channelp) - { - LL_WARNS("Voice") << "Unable to retrieve channel with Id = " << mSessionID << LL_ENDL; - return; - } + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - if (!status) - { - if (status == LLCore::HttpStatus(HTTP_FORBIDDEN)) - { - //403 == no ability - LLNotificationsUtil::add( - "VoiceNotAllowed", - channelp->getNotifyArgs()); - } - else - { - LLNotificationsUtil::add( - "VoiceCallGenericError", - channelp->getNotifyArgs()); - } - channelp->deactivate(); - return; - } + LLVoiceChannel* channelp = LLVoiceChannel::getChannelByID(mSessionID); + if (!channelp) + { + LL_WARNS("Voice") << "Unable to retrieve channel with Id = " << mSessionID << LL_ENDL; + return; + } - result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); + if (!status) + { + if (status == LLCore::HttpStatus(HTTP_FORBIDDEN)) + { + //403 == no ability + LLNotificationsUtil::add( + "VoiceNotAllowed", + channelp->getNotifyArgs()); + } + else + { + LLNotificationsUtil::add( + "VoiceCallGenericError", + channelp->getNotifyArgs()); + } + channelp->deactivate(); + return; + } - LLSD::map_const_iterator iter; - for (iter = result.beginMap(); iter != result.endMap(); ++iter) - { - LL_DEBUGS("Voice") << "LLVoiceCallCapResponder::result got " - << iter->first << LL_ENDL; - } + result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); - channelp->setChannelInfo( - result["voice_credentials"]["channel_uri"].asString(), - result["voice_credentials"]["channel_credentials"].asString()); + LLSD::map_const_iterator iter; + for (iter = result.beginMap(); iter != result.endMap(); ++iter) + { + LL_DEBUGS("Voice") << "LLVoiceChannelGroup::voiceCallCapCoro got " + << iter->first << LL_ENDL; + } + LL_INFOS("Voice") << "LLVoiceChannelGroup::voiceCallCapCoro got " << result << LL_ENDL; + channelp->setChannelInfo(result["voice_credentials"]); } @@ -682,13 +674,14 @@ void LLVoiceChannelProximal::activate() if((LLVoiceChannel::sCurrentVoiceChannel != this) && (LLVoiceChannel::getState() == STATE_CONNECTED)) { // we're connected to a non-spatial channel, so disconnect. - LLVoiceClient::getInstance()->leaveNonSpatialChannel(); + LLVoiceClient::getInstance()->leaveNonSpatialChannel(); } + 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()->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.isUndefined()) { - 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 e68bfbe1ff..adc387e22d 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(); @@ -96,43 +94,37 @@ public: EDirection getCallDirection() {return mCallDirection;} static LLVoiceChannel* getChannelByID(const LLUUID& session_id); - static LLVoiceChannel* getChannelByURI(std::string uri); static LLVoiceChannel* getCurrentVoiceChannel(); - + static void initClass(); - + 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 +136,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); - /*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(); + 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; + + 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 +198,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 68d9f4ffab..6eadc3892e 100644 --- a/indra/newview/llvoiceclient.cpp +++ b/indra/newview/llvoiceclient.cpp @@ -1,36 +1,36 @@ - /** + /** * @file llvoiceclient.cpp * @brief Voice client delegation class implementation. * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, 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 "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" @@ -81,10 +81,10 @@ LLVoiceHandler gVoiceHandler; std::string LLVoiceClientStatusObserver::status2string(LLVoiceClientStatusObserver::EStatusType inStatus) { std::string result = "UNTRANSLATED"; - + // Prevent copy-paste errors when updating this list... #define CASE(x) case x: result = #x; break - + switch(inStatus) { CASE(STATUS_LOGIN_RETRY); @@ -107,19 +107,35 @@ std::string LLVoiceClientStatusObserver::status2string(LLVoiceClientStatusObserv } break; } - + #undef CASE - + 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)), mVoiceEffectDefault(LLCachedControl<std::string>(gSavedPerAccountSettings, "VoiceEffectDefault", "00000000-0000-0000-0000-000000000000")), @@ -133,7 +149,6 @@ LLVoiceClient::LLVoiceClient(LLPumpIO *pump) mMuteMic(false), mDisableMic(false) { - updateSettings(); init(pump); } @@ -142,46 +157,139 @@ LLVoiceClient::LLVoiceClient(LLPumpIO *pump) LLVoiceClient::~LLVoiceClient() { - llassert(!mVoiceModule); + llassert(!mSpatialVoiceModule); } 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) { - mVoiceModule = NULL; - return; + // 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); } - mVoiceModule->init(m_servicePump); - mVoiceModule->userAuthorized(user_id, agentID); +} + +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) + { + return; + } + 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,190 +409,164 @@ 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; - } + // we don't support PSTN calls anymore. (did we ever?) + return TRUE; } -/* obsolete -BOOL LLVoiceClient::sendTextMessage(const LLUUID& participant_id, const std::string& message) +//---------------------------------------------- +// channels + +bool LLVoiceClient::inProximalChannel() { - if (mVoiceModule) + if (mSpatialVoiceModule) { - return mVoiceModule->sendTextMessage(participant_id, message); + return mSpatialVoiceModule->inProximalChannel(); } else { - return FALSE; - } + return false; + } } -*/ -void LLVoiceClient::endUserIMSession(const LLUUID& participant_id) +void LLVoiceClient::setNonSpatialChannel( + 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->endUserIMSession(participant_id); // A SLim leftover + mNonSpatialVoiceModule->processChannels(true); + mNonSpatialVoiceModule->setNonSpatialChannel(channelInfo, notify_on_first_join, hangup_on_last_leave); } } -//---------------------------------------------- -// channels - -bool LLVoiceClient::inProximalChannel() +void LLVoiceClient::setSpatialChannel(const LLSD &channelInfo) { - if (mVoiceModule) + mSpatialCredentials = channelInfo; + LLViewerRegion *region = gAgent.getRegion(); + if (region && region->simulatorFeaturesReceived()) { - return mVoiceModule->inProximalChannel(); + LLSD simulatorFeatures; + region->getSimulatorFeatures(simulatorFeatures); + setSpatialVoiceModule(simulatorFeatures["VoiceServerType"].asString()); } else { - return false; + return; } -} - -void LLVoiceClient::setNonSpatialChannel( - const std::string &uri, - const std::string &credentials) -{ - if (mVoiceModule) - { - mVoiceModule->setNonSpatialChannel(uri, credentials); - } -} -void LLVoiceClient::setSpatialChannel( - const std::string &uri, - const std::string &credentials) -{ - if (mVoiceModule) - { - mVoiceModule->setSpatialChannel(uri, credentials); - } + if (mSpatialVoiceModule) + { + mSpatialVoiceModule->setSpatialChannel(channelInfo); + } } void LLVoiceClient::leaveNonSpatialChannel() { - if (mVoiceModule) + if (mNonSpatialVoiceModule) { - mVoiceModule->leaveNonSpatialChannel(); + mNonSpatialVoiceModule->leaveNonSpatialChannel(); + mNonSpatialVoiceModule->processChannels(false); + mNonSpatialVoiceModule = nullptr; } + if (mSpatialVoiceModule) + { + mSpatialVoiceModule->processChannels(true); + ; + } } -void LLVoiceClient::leaveChannel(void) +void LLVoiceClient::activateSpatialChannel(bool activate) { - if (mVoiceModule) + if (mSpatialVoiceModule) { - mVoiceModule->leaveChannel(); + mSpatialVoiceModule->processChannels(activate); } } -std::string LLVoiceClient::getCurrentChannel() +bool LLVoiceClient::isCurrentChannel(const LLSD& channelInfo) { - if (mVoiceModule) - { - return mVoiceModule->getCurrentChannel(); - } - else - { - return std::string(); - } + 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); + return module->getIncomingCallInterface(voice_call_info); } - else - { - return false; - } -} + return nullptr; -bool LLVoiceClient::answerInvite(std::string &channelHandle) -{ - if (mVoiceModule) - { - return mVoiceModule->answerInvite(channelHandle); - } - else - { - return false; - } } -void LLVoiceClient::declineInvite(std::string &channelHandle) +//--------------------------------------- +// outgoing calls +LLVoiceP2POutgoingCallInterface *LLVoiceClient::getOutgoingCallInterface(const LLSD& voiceChannelInfo) { - if (mVoiceModule) mVoiceModule->declineInvite(channelHandle); + 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; + } + if (voiceChannelInfo.has("voice_server_type")) + { + voice_server_type = voiceChannelInfo["voice_server_type"].asString(); + } + LLVoiceModuleInterface *module = getVoiceModule(voice_server_type); + return dynamic_cast<LLVoiceP2POutgoingCallInterface *>(module); } - //------------------------------------------ // 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); } @@ -537,29 +575,22 @@ void LLVoiceClient::setMicGain(F32 volume) bool LLVoiceClient::voiceEnabled() { - if (mVoiceModule) - { - return mVoiceModule->voiceEnabled(); - } - else - { - return false; - } + static LLCachedControl<bool> enable_voice_chat(gSavedSettings, "EnableVoiceChat"); + static LLCachedControl<bool> cmd_line_disable_voice(gSavedSettings, "CmdLineDisableVoice"); + return enable_voice_chat && !cmd_line_disable_voice && !gNonInteractive; } void LLVoiceClient::setVoiceEnabled(bool enabled) { - if (mVoiceModule) - { - mVoiceModule->setVoiceEnabled(enabled); - } + LLWebRTCVoiceClient::getInstance()->setVoiceEnabled(enabled); + LLVivoxVoiceClient::getInstance()->setVoiceEnabled(enabled); } void LLVoiceClient::updateMicMuteLogic() { // If not configured to use PTT, the mic should be open (otherwise the user will be unable to speak). bool new_mic_mute = false; - + if(mUsePTT) { // If configured to use PTT, track the user state. @@ -571,34 +602,20 @@ 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) - { - return mVoiceModule->lipSyncEnabled(); - } - else + if (mMuteMic != muted) { - return false; + mMuteMic = muted; + updateMicMuteLogic(); + mMicroChangedSignal(); } } -void LLVoiceClient::setMuteMic(bool muted) -{ - mMuteMic = muted; - updateMicMuteLogic(); - mMicroChangedSignal(); -} - // ---------------------------------------------- // PTT @@ -627,7 +644,7 @@ void LLVoiceClient::setUsePTT(bool usePTT) mUserPTTState = false; } mUsePTT = usePTT; - + updateMicMuteLogic(); } @@ -638,7 +655,7 @@ void LLVoiceClient::setPTTIsToggle(bool PTTIsToggle) // When the user turns off toggle, reset the current state. mUserPTTState = false; } - + mPTTIsToggle = PTTIsToggle; updateMicMuteLogic(); @@ -653,12 +670,12 @@ void LLVoiceClient::inputUserControlState(bool down) { if(mPTTIsToggle) { - if(down) // toggle open-mic state on 'down' + if(down) // toggle open-mic state on 'down' { toggleUserPTTState(); } } - else // set open-mic state as an absolute + else // set open-mic state as an absolute { setUserPTTState(down); } @@ -675,117 +692,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) + std::string result = LLWebRTCVoiceClient::getInstance()->getDisplayName(id); + if (result.empty()) { - return mVoiceModule->getDisplayName(id); - } - else - { - 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 +764,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 +816,7 @@ std::string LLVoiceClient::sipURIFromID(const LLUUID &id) LLVoiceEffectInterface* LLVoiceClient::getVoiceEffectInterface() const { - return getVoiceEffectEnabled() ? dynamic_cast<LLVoiceEffectInterface*>(mVoiceModule) : NULL; + return getVoiceEffectEnabled() ? dynamic_cast<LLVoiceEffectInterface*>(mSpatialVoiceModule) : NULL; } /////////////////// @@ -852,31 +824,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")) + { + voice_server_type = input["body"]["voice_server_type"].asString(); + } + + LLVoiceModuleInterface *voiceModule = NULL; + + if (voice_server_type == "vivox" || voice_server_type.empty()) { - int major_voice_version = - input["body"]["major_version"].asInteger(); - // int minor_voice_version = - // input["body"]["minor_version"].asInteger(); - LLVoiceVersionInfo versionInfo = LLVoiceClient::getInstance()->getVersion(); - - if (major_voice_version > 1) + 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; } } }; @@ -890,41 +883,27 @@ class LLViewerParcelVoiceInfo : public LLHTTPNode { //the parcel you are in has changed something about its //voice information - + //this is a misnomer, as it can also be when you are not in //a parcel at all. Should really be something like //LLViewerVoiceInfoChanged..... if ( input.has("body") ) { LLSD body = input["body"]; - + //body has "region_name" (str), "parcel_local_id"(int), //"voice_credentials" (map). - + //body["voice_credentials"] has "channel_uri" (str), //body["voice_credentials"] has "channel_credentials" (str) - + //if we really wanted to be extra careful, //we'd check the supplied //local parcel id to make sure it's for the same parcel //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"]); } } } @@ -966,7 +945,7 @@ void LLSpeakerVolumeStorage::storeSpeakerVolume(const LLUUID& speaker_id, F32 vo bool LLSpeakerVolumeStorage::getSpeakerVolume(const LLUUID& speaker_id, F32& volume) { speaker_data_map_t::const_iterator it = mSpeakersData.find(speaker_id); - + if (it != mSpeakersData.end()) { volume = it->second; @@ -1045,9 +1024,9 @@ void LLSpeakerVolumeStorage::load() if (LLSDParser::PARSE_FAILURE == LLSDSerialize::fromXML(settings_llsd, file)) { LL_WARNS("Voice") << "failed to parse " << filename << LL_ENDL; - + } - + } for (LLSD::map_const_iterator iter = settings_llsd.beginMap(); @@ -1087,7 +1066,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 aa67502908..6b6cb452ee 100644 --- a/indra/newview/llvoiceclient.h +++ b/indra/newview/llvoiceclient.h @@ -1,25 +1,25 @@ -/** +/** * @file llvoiceclient.h * @brief Declaration of LLVoiceClient class which is the interface to the voice client process. * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, 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$ */ @@ -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 /// @@ -110,30 +147,32 @@ class LLVoiceModuleInterface public: LLVoiceModuleInterface() {} virtual ~LLVoiceModuleInterface() {} - + virtual void init(LLPumpIO *pump)=0; // Call this once at application startup (creates connector) virtual void terminate()=0; // Call this to clean up during shutdown - + virtual void updateSettings()=0; // call after loading settings and whenever they change - + virtual bool isVoiceWorking() const = 0; // connected to a voice server and voice channel - + virtual void setHidden(bool hidden)=0; // Hides the user from voice. virtual const LLVoiceVersionInfo& getVersion()=0; - + + + ///////////////////// /// @name Tuning //@{ virtual void tuningStart()=0; virtual void tuningStop()=0; virtual bool inTuningMode()=0; - + virtual void tuningSetMicVolume(float volume)=0; virtual void tuningSetSpeakerVolume(float volume)=0; virtual float tuningGetEnergy(void)=0; //@} - + ///////////////////// /// @name Devices //@{ @@ -141,114 +180,110 @@ public: // i.e. when the daemon is running and connected, and the device lists are populated. virtual bool deviceSettingsAvailable()=0; virtual bool deviceSettingsUpdated() = 0; - + // 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)=0; - + virtual void setCaptureDevice(const std::string& name)=0; virtual void setRenderDevice(const std::string& name)=0; - + virtual LLVoiceDeviceList& getCaptureDevices()=0; virtual LLVoiceDeviceList& getRenderDevices()=0; - + virtual void getParticipantList(std::set<LLUUID> &participants)=0; virtual bool isParticipant(const LLUUID& speaker_id)=0; //@} - + //////////////////////////// /// @ 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()=0; - - virtual void setNonSpatialChannel(const std::string &uri, - const std::string &credentials)=0; - - virtual bool setSpatialChannel(const std::string &uri, - const std::string &credentials)=0; - - virtual void leaveNonSpatialChannel()=0; - - virtual void leaveChannel(void)=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; + + virtual void setNonSpatialChannel(const LLSD& channelInfo, + bool notify_on_first_join, + bool hangup_on_last_leave)=0; + + virtual bool setSpatialChannel(const LLSD& channelInfo)=0; + + virtual void leaveNonSpatialChannel() = 0; + virtual void processChannels(bool process) = 0; + + virtual bool isCurrentChannel(const LLSD &channelInfo) = 0; + virtual bool compareChannels(const LLSD &channelInfo1, const LLSD &channelInfo2) = 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; //@} - + ///////////////////////// /// @name Volume/gain //@{ virtual void setVoiceVolume(F32 volume)=0; virtual void setMicGain(F32 volume)=0; //@} - + ///////////////////////// /// @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) + virtual void setUserVolume(const LLUUID& id, F32 volume)=0; // set's volume for specified agent, from 0-1 (where .5 is nominal) //@} - + ////////////////////////// /// @name text chat //@{ 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 virtual void userAuthorized(const std::string& user_id, const LLUUID &agentID)=0; - + ////////////////////////////// /// @name Status notification //@{ virtual void addObserver(LLVoiceClientStatusObserver* observer)=0; virtual void removeObserver(LLVoiceClientStatusObserver* observer)=0; virtual void addObserver(LLFriendObserver* observer)=0; - virtual void removeObserver(LLFriendObserver* observer)=0; + virtual void removeObserver(LLFriendObserver* observer)=0; virtual void addObserver(LLVoiceClientParticipantObserver* observer)=0; - virtual void removeObserver(LLVoiceClientParticipantObserver* observer)=0; + virtual void removeObserver(LLVoiceClientParticipantObserver* observer)=0; //@} - + virtual std::string sipURIFromID(const LLUUID &id)=0; //@} - + }; @@ -320,9 +355,9 @@ public: micro_changed_signal_t mMicroChangedSignal; void terminate(); // Call this to clean up during shutdown - + const LLVoiceVersionInfo getVersion(); - + static const F32 OVERDRIVEN_POWER_LEVEL; static const F32 VOLUME_MIN; @@ -337,18 +372,18 @@ public: void tuningStart(); void tuningStop(); bool inTuningMode(); - + void tuningSetMicVolume(float volume); void tuningSetSpeakerVolume(float volume); float tuningGetEnergy(void); - + // devices - + // 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. bool deviceSettingsAvailable(); bool deviceSettingsUpdated(); // returns true when the device list has been updated recently. - + // 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). @@ -365,60 +400,59 @@ public: //////////////////////////// // 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(); - 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 - + void setVoiceVolume(F32 volume); void setMicGain(F32 volume); - void setUserVolume(const LLUUID& id, F32 volume); // set's volume for specified agent, from 0-1 (where .5 is nominal) + 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(); void toggleUserPTTState(void); - void inputUserControlState(bool down); // interpret any sort of up-down mic-open control input according to ptt-toggle prefs + void inputUserControlState(bool down); // interpret any sort of up-down mic-open control input according to ptt-toggle prefs void setVoiceEnabled(bool enabled); void setUsePTT(bool usePTT); void setPTTIsToggle(bool PTTIsToggle); - bool getPTTIsToggle(); + bool getPTTIsToggle(); void updateMicMuteLogic(); - BOOL lipSyncEnabled(); - boost::signals2::connection MicroChangedCallback(const micro_changed_signal_t::slot_type& cb ) { return mMicroChangedSignal.connect(cb); } - + ///////////////////////////// // Accessors for data related to nearby speakers BOOL getVoiceEnabled(const LLUUID& id); // true if we've received data for this avatar - std::string getDisplayName(const LLUUID& id); + std::string getDisplayName(const LLUUID& id); BOOL isOnlineSIP(const LLUUID &id); BOOL isParticipantAvatar(const LLUUID &id); BOOL getIsSpeaking(const LLUUID& id); @@ -428,32 +462,33 @@ 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); - + ////////////////////////// /// @name text chat //@{ 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); void addObserver(LLFriendObserver* observer); void removeObserver(LLFriendObserver* observer); void addObserver(LLVoiceClientParticipantObserver* observer); void removeObserver(LLVoiceClientParticipantObserver* observer); - - std::string sipURIFromID(const LLUUID &id); + + std::string sipURIFromID(const LLUUID &id); ////////////////////////// /// @name Voice effects @@ -464,20 +499,30 @@ 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; + + 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 mPTTDirty; bool mPTT; - + bool mUsePTT; S32 mPTTMouseButton; KEY mPTTKey; diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp index 310c9ee297..6bbb2f24a6 100644 --- a/indra/newview/llvoicevivox.cpp +++ b/indra/newview/llvoicevivox.cpp @@ -1,25 +1,25 @@ - /** + /** * @file LLVivoxVoiceClient.cpp * @brief Implementation of LLVivoxVoiceClient class which is the interface to the voice client process. * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, 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$ */ @@ -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; @@ -94,7 +96,7 @@ namespace { // Don't send positional updates more frequently than this: const F32 UPDATE_THROTTLE_SECONDS = 0.5f; - // Timeout for connection to Vivox + // Timeout for connection to Vivox const F32 CONNECT_ATTEMPT_TIMEOUT = 300.0f; const F32 CONNECT_DNS_TIMEOUT = 5.0f; const int CONNECT_RETRY_MAX = 3; @@ -113,8 +115,8 @@ namespace { const F32 SESSION_JOIN_TIMEOUT = 30.0f; // Defines the maximum number of times(in a row) "stateJoiningSession" case for spatial channel is reached in stateMachine() - // which is treated as normal. The is the number of frames to wait for a channel join before giving up. This was changed - // from the original count of 50 for two reason. Modern PCs have higher frame rates and sometimes the SLVoice process + // which is treated as normal. The is the number of frames to wait for a channel join before giving up. This was changed + // from the original count of 50 for two reason. Modern PCs have higher frame rates and sometimes the SLVoice process // backs up processing join requests. There is a log statement that records when channel joins take longer than 100 frames. const int MAX_NORMAL_JOINING_SPATIAL_NUM = 1500; @@ -133,17 +135,17 @@ namespace { static int scale_mic_volume(float volume) { - // incoming volume has the range [0.0 ... 2.0], with 1.0 as the default. - // Map it to Vivox levels as follows: 0.0 -> 30, 1.0 -> 50, 2.0 -> 70 + // incoming volume has the range [0.0 ... 2.0], with 1.0 as the default. + // Map it to Vivox levels as follows: 0.0 -> 30, 1.0 -> 50, 2.0 -> 70 return 30 + (int)(volume * 20.0f); } static int scale_speaker_volume(float volume) { - // incoming volume has the range [0.0 ... 1.0], with 0.5 as the default. - // Map it to Vivox levels as follows: 0.0 -> 30, 0.5 -> 50, 1.0 -> 70 + // incoming volume has the range [0.0 ... 1.0], with 0.5 as the default. + // Map it to Vivox levels as follows: 0.0 -> 30, 0.5 -> 50, 1.0 -> 70 return 30 + (int)(volume * 40.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,19 +358,24 @@ 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); - + #if LL_DARWIN || LL_LINUX // HACK: THIS DOES NOT BELONG HERE // When the vivox daemon dies, the next write attempt on our socket generates a SIGPIPE, which kills us. // This should cause us to ignore SIGPIPE and handle the error through proper channels. // This should really be set up elsewhere. Where should it go? signal(SIGPIPE, SIG_IGN); - + // Since we're now launching the gateway with fork/exec instead of system(), we need to deal with zombie processes. // Ignoring SIGCHLD should prevent zombies from being created. Alternately, we could use wait(), but I'd rather not do that. signal(SIGCHLD, SIG_IGN); @@ -410,13 +415,13 @@ void LLVivoxVoiceClient::terminate() return; } - // needs to be done manually here since we will not get another pass in + // needs to be done manually here since we will not get another pass in // coroutines... that mechanism is long since gone. if (mIsLoggedIn) { logoutOfVivox(false); } - + if(sConnected) { breakVoiceConnection(false); @@ -437,7 +442,7 @@ void LLVivoxVoiceClient::terminate() void LLVivoxVoiceClient::cleanUp() { LL_DEBUGS("Voice") << LL_ENDL; - + deleteAllSessions(); deleteAllVoiceFonts(); deleteVoiceFontTemplates(); @@ -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")); } ///////////////////////////// @@ -480,7 +484,7 @@ bool LLVivoxVoiceClient::writeString(const std::string &str) apr_status_t err; apr_size_t size = (apr_size_t)str.size(); apr_size_t written = size; - + //MARK: Turn this on to log outgoing XML // LL_DEBUGS("Voice") << "sending: " << str << LL_ENDL; @@ -489,7 +493,7 @@ bool LLVivoxVoiceClient::writeString(const std::string &str) mSocket->getSocket(), (const char*)str.data(), &written); - + if(err == 0 && written == size) { // Success. @@ -512,7 +516,7 @@ bool LLVivoxVoiceClient::writeString(const std::string &str) daemonDied(); } } - + return result; } @@ -523,7 +527,7 @@ void LLVivoxVoiceClient::connectorCreate() { std::ostringstream stream; std::string logdir = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ""); - + // Transition to stateConnectorStarted when the connector handle comes back. std::string vivoxLogLevel = gSavedSettings.getString("VivoxDebugLevel"); if ( vivoxLogLevel.empty() ) @@ -531,8 +535,8 @@ void LLVivoxVoiceClient::connectorCreate() vivoxLogLevel = "0"; } LL_DEBUGS("Voice") << "creating connector with log level " << vivoxLogLevel << LL_ENDL; - - stream + + stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.Create.1\">" << "<ClientName>V2 SDK</ClientName>" << "<AccountManagementServer>" << mVoiceAccountServerURI << "</AccountManagementServer>" @@ -548,7 +552,7 @@ void LLVivoxVoiceClient::connectorCreate() //<< "<Application></Application>" //Name can cause problems per vivox. << "<MaxCalls>12</MaxCalls>" << "</Request>\n\n\n"; - + writeString(stream.str()); } @@ -562,10 +566,10 @@ void LLVivoxVoiceClient::connectorShutdown() << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>" << "</Request>" << "\n\n\n"; - + mShutdownComplete = false; mConnectorEstablished = false; - + writeString(stream.str()); } else @@ -597,7 +601,7 @@ void LLVivoxVoiceClient::setLoginInfo( { // Already logged in. LL_WARNS("Voice") << "Called while already logged in." << LL_ENDL; - + // Don't process another login. return; } @@ -612,14 +616,14 @@ void LLVivoxVoiceClient::setLoginInfo( } std::string debugSIPURIHostName = gSavedSettings.getString("VivoxDebugSIPURIHostName"); - + if( !debugSIPURIHostName.empty() ) { LL_INFOS("Voice") << "Overriding account server based on VivoxDebugSIPURIHostName: " << debugSIPURIHostName << LL_ENDL; mVoiceSIPURIHostName = debugSIPURIHostName; } - + if( mVoiceSIPURIHostName.empty() ) { // we have an empty account server name @@ -639,7 +643,7 @@ void LLVivoxVoiceClient::setLoginInfo( << mVoiceSIPURIHostName << LL_ENDL; } - + std::string debugAccountServerURI = gSavedSettings.getString("VivoxDebugVoiceAccountServerURI"); if( !debugAccountServerURI.empty() ) @@ -648,11 +652,11 @@ void LLVivoxVoiceClient::setLoginInfo( << debugAccountServerURI << LL_ENDL; mVoiceAccountServerURI = debugAccountServerURI; } - + if( mVoiceAccountServerURI.empty() ) { // If the account server URI isn't specified, construct it from the SIP URI hostname - mVoiceAccountServerURI = "https://www." + mVoiceSIPURIHostName + "/api2/"; + mVoiceAccountServerURI = "https://www." + mVoiceSIPURIHostName + "/api2/"; LL_INFOS("Voice") << "Inferring account server based on SIP URI Host name: " << mVoiceAccountServerURI << LL_ENDL; } @@ -663,11 +667,11 @@ void LLVivoxVoiceClient::idle(void* user_data) } //========================================================================= -// the following are methods to support the coroutine implementation of the -// voice connection and processing. They should only be called in the context +// 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. -// -// +// +// typedef enum e_voice_control_coro_state { @@ -816,7 +820,7 @@ void LLVivoxVoiceClient::voiceControlStateMachine(S32 &coro_state) case VOICE_STATE_SESSION_ESTABLISHED: { - // enable/disable the automatic VAD and explicitly set the initial values of + // enable/disable the automatic VAD and explicitly set the initial values of // the VAD variables ourselves when it is off - see SL-15072 for more details // note: we set the other parameters too even if the auto VAD is on which is ok unsigned int vad_auto = gSavedSettings.getU32("VivoxVadAuto"); @@ -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; @@ -1011,7 +1015,7 @@ bool LLVivoxVoiceClient::startAndLaunchDaemon() { LLFile::rename(new_log, old_log); } - + if (!shutdown_timeout().empty()) { params.args.add("-st"); @@ -1091,7 +1095,7 @@ bool LLVivoxVoiceClient::startAndLaunchDaemon() llcoro::suspendUntilTimeout(DAEMON_CONNECT_THROTTLE_SECONDS); } } - + //--------------------------------------------------------------------- if (sShuttingDown && !sConnected) { @@ -1109,7 +1113,7 @@ bool LLVivoxVoiceClient::startAndLaunchDaemon() { return false; } - + // MBW -- Note to self: pumps and pipes examples in // indra/test/io.cpp // indra/test/llpipeutil.{cpp|h} @@ -1147,7 +1151,7 @@ bool LLVivoxVoiceClient::provisionVoiceAccount() // *TODO* Pump a message for wake up. llcoro::suspend(); } - + if (sShuttingDown) { return false; @@ -1169,7 +1173,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) { @@ -1199,7 +1205,7 @@ bool LLVivoxVoiceClient::provisionVoiceAccount() else { provisioned = true; - } + } } while (!provisioned && ++retryCount <= PROVISION_RETRY_MAX && !sShuttingDown); if (sShuttingDown && !provisioned) @@ -1213,7 +1219,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(); @@ -1223,7 +1229,7 @@ bool LLVivoxVoiceClient::provisionVoiceAccount() { voiceSipUriHostname = result["voice_sip_uri_hostname"].asString(); } - + // this key is actually misnamed -- it will be an entire URI, not just a hostname. if (result.has("voice_account_server_name")) { @@ -1254,7 +1260,7 @@ bool LLVivoxVoiceClient::establishVoiceConnection() { return false; } - + LLSD result; bool connected(false); bool giving_up(false); @@ -1373,7 +1379,7 @@ bool LLVivoxVoiceClient::loginToVivox() bool account_login(false); bool send_login(true); - do + do { mIsLoggingIn = true; if (send_login) @@ -1381,7 +1387,7 @@ bool LLVivoxVoiceClient::loginToVivox() loginSendMessage(); send_login = false; } - + LLSD result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGIN_ATTEMPT_TIMEOUT, timeoutResult); if (sShuttingDown) @@ -1421,7 +1427,7 @@ bool LLVivoxVoiceClient::loginToVivox() // tell the user there is a problem LL_WARNS("Voice") << "login " << loginresp << " will retry login in " << timeout << " seconds." << LL_ENDL; - + if (!sShuttingDown) { // Todo: this is way to long, viewer can get stuck waiting during shutdown @@ -1526,7 +1532,7 @@ bool LLVivoxVoiceClient::retrieveVoiceFonts() mIsWaitingForFonts = true; LLSD result; - do + do { result = llcoro::suspendUntilEventOn(mVivoxPump); @@ -1561,7 +1567,7 @@ bool LLVivoxVoiceClient::requestParcelVoiceInfo() LL_DEBUGS("Voice") << "ParcelVoiceInfoRequest capability not available in this region" << LL_ENDL; return false; } - + // update the parcel checkParcelChanged(true); @@ -1609,48 +1615,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()) { @@ -1662,9 +1627,9 @@ bool LLVivoxVoiceClient::requestParcelVoiceInfo() } } - // set the spatial channel. If no voice credentials or uri are + // 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) @@ -1717,7 +1682,7 @@ bool LLVivoxVoiceClient::addAndJoinSession(const sessionStatePtr_t &nextSession) notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED); LL_WARNS() << "There seems to be problem with connection to voice server. Disabling voice chat abilities." << LL_ENDL; } - + // Increase mSpatialJoiningNum only for spatial sessions- it's normal to reach this case for // example for p2p many times while waiting for response, so it can't be used to detect errors if (mAudioSession && mAudioSession->mIsSpatial) @@ -1759,8 +1724,8 @@ bool LLVivoxVoiceClient::addAndJoinSession(const sessionStatePtr_t &nextSession) LLSD timeoutResult(LLSDMap("session", "timeout")); - // We are about to start a whole new session. Anything that MIGHT still be in our - // maildrop is going to be stale and cause us much wailing and gnashing of teeth. + // We are about to start a whole new session. Anything that MIGHT still be in our + // maildrop is going to be stale and cause us much wailing and gnashing of teeth. // Just flush it all out and start new. mVivoxPump.discard(); @@ -1803,25 +1768,25 @@ 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. - - if (message == "failed") + LL_INFOS("Voice") << "Result:" << result << LL_ENDL; + if (message == "failed") { int reason = result["reason"].asInteger(); LL_WARNS("Voice") << "Add and join failed for reason " << reason << LL_ENDL; - + if ( (reason == ERROR_VIVOX_NOT_LOGGED_IN) || (reason == ERROR_VIVOX_OBJECT_NOT_FOUND)) { LL_DEBUGS("Voice") << "Requesting reprovision and login." << LL_ENDL; requestRelog(); - } + } } else { LL_WARNS("Voice") << "session '" << message << "' " << LL_ENDL; } - + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); mIsJoiningSession = false; return false; @@ -1840,8 +1805,8 @@ bool LLVivoxVoiceClient::addAndJoinSession(const sessionStatePtr_t &nextSession) // Events that need to happen when a session is joined could go here. // send an initial positional information immediately upon joining. - // - // do an initial update for position and the camera position, then send a + // + // do an initial update for position and the camera position, then send a // positional update. updatePosition(); enforceTether(); @@ -1948,7 +1913,7 @@ bool LLVivoxVoiceClient::terminateAudioSession(bool wait) notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); // Always reset the terminate request flag when we get here. - // Some slower PCs have a race condition where they can switch to an incoming P2P call faster than the state machine leaves + // Some slower PCs have a race condition where they can switch to an incoming P2P call faster than the state machine leaves // the region chat. mSessionTerminateRequested = false; @@ -1983,7 +1948,7 @@ bool LLVivoxVoiceClient::waitForChannel() EVoiceWaitForChannelState state = VOICE_CHANNEL_STATE_LOGIN; - do + do { if (sShuttingDown) { @@ -2027,7 +1992,6 @@ bool LLVivoxVoiceClient::waitForChannel() break; case VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING: - mIsProcessingChannels = true; llcoro::suspend(); state = VOICE_CHANNEL_STATE_PROCESS_CHANNEL; break; @@ -2041,7 +2005,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 @@ -2056,15 +2020,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 @@ -2150,13 +2122,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); @@ -2166,10 +2138,10 @@ bool LLVivoxVoiceClient::runSession(const sessionStatePtr_t &session) LL_DEBUGS("Voice") << "runSession terminate requested " << LL_ENDL; terminateAudioSession(true); } - // if a relog has been requested then addAndJoineSession + // if a relog has been requested then addAndJoineSession // failed in a spectacular way and we need to back out. // If this is not the case then we were simply trying to - // make a call and the other party rejected it. + // make a call and the other party rejected it. return !mRelogRequested; } @@ -2180,12 +2152,14 @@ bool LLVivoxVoiceClient::runSession(const sessionStatePtr_t &session) mIsInChannel = true; mMuteMicDirty = true; + mSessionTerminateRequested = false; while (!sShuttingDown && mVoiceEnabled && isGatewayRunning() && !mSessionTerminateRequested - && !mTuningMode) + && !mTuningMode + && mProcessChannels) { sendCaptureAndRenderDevices(); // suspends @@ -2204,7 +2178,7 @@ bool LLVivoxVoiceClient::runSession(const sessionStatePtr_t &session) mAudioSession->mParticipantsChanged = false; notifyParticipantObservers(); } - + if (!inSpatialChannel()) { // When in a non-spatial channel, never send positional updates. @@ -2216,9 +2190,9 @@ bool LLVivoxVoiceClient::runSession(const sessionStatePtr_t &session) if (checkParcelChanged()) { - // *RIDER: I think I can just return here if the parcel has changed + // *RIDER: I think I can just return here if the parcel has changed // and grab the new voice channel from the outside loop. - // + // // if the parcel has changed, attempted to request the // cap for the parcel voice info. If we can't request it // then we don't have the cap URL so we do nothing and will @@ -2261,7 +2235,7 @@ bool LLVivoxVoiceClient::runSession(const sessionStatePtr_t &session) LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; } if (result.has("session")) - { + { if (result.has("handle")) { if (!mAudioSession) @@ -2366,7 +2340,7 @@ void LLVivoxVoiceClient::recordingAndPlaybackMode() int LLVivoxVoiceClient::voiceRecordBuffer() { - LLSD timeoutResult(LLSDMap("recplay", "stop")); + LLSD timeoutResult(LLSDMap("recplay", "stop")); LL_INFOS("Voice") << "Recording voice buffer" << LL_ENDL; @@ -2437,95 +2411,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; @@ -2573,7 +2463,7 @@ void LLVivoxVoiceClient::logout() // Ensure that we'll re-request provisioning before logging in again mAccountPassword.clear(); mVoiceAccountServerURI.clear(); - + logoutSendMessage(); } @@ -2598,7 +2488,7 @@ void LLVivoxVoiceClient::logoutSendMessage() void LLVivoxVoiceClient::sessionGroupCreateSendMessage() { if(mAccountLoggedIn) - { + { std::ostringstream stream; LL_DEBUGS("Voice") << "creating session group" << LL_ENDL; @@ -2651,6 +2541,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()); } @@ -2666,7 +2557,7 @@ void LLVivoxVoiceClient::sessionGroupAddSessionSendMessage(const sessionStatePtr { session->mMediaConnectInProgress = true; } - + std::string password; if(!session->mHash.empty()) { @@ -2691,7 +2582,7 @@ void LLVivoxVoiceClient::sessionGroupAddSessionSendMessage(const sessionStatePtr << "<PasswordHashAlgorithm>SHA1UserName</PasswordHashAlgorithm>" << "</Request>\n\n\n" ; - + writeString(stream.str()); } @@ -2703,7 +2594,7 @@ void LLVivoxVoiceClient::sessionMediaConnectSendMessage(const sessionStatePtr_t << LL_ENDL; session->mMediaConnectInProgress = true; - + std::ostringstream stream; stream @@ -2720,7 +2611,7 @@ void LLVivoxVoiceClient::sessionMediaConnectSendMessage(const sessionStatePtr_t void LLVivoxVoiceClient::sessionTextConnectSendMessage(const sessionStatePtr_t &session) { LL_DEBUGS("Voice") << "connecting text to session handle: " << session->mHandle << LL_ENDL; - + std::ostringstream stream; stream @@ -2761,7 +2652,7 @@ void LLVivoxVoiceClient::leaveAudioSession() time_t now = time(NULL); const size_t BUF_SIZE = 64; char time_str[BUF_SIZE]; /* Flawfinder: ignore */ - + strftime(time_str, BUF_SIZE, "%Y-%m-%dT%H:%M:%SZ", gmtime(&now)); savepath += time_str; } @@ -2772,7 +2663,7 @@ void LLVivoxVoiceClient::leaveAudioSession() } else { - LL_WARNS("Voice") << "called with no session handle" << LL_ENDL; + LL_WARNS("Voice") << "called with no session handle" << LL_ENDL; } } else @@ -2789,12 +2680,12 @@ void LLVivoxVoiceClient::sessionTerminateSendMessage(const sessionStatePtr_t &se sessionGroupTerminateSendMessage(session); return; /* - LL_DEBUGS("Voice") << "Sending Session.Terminate with handle " << session->mHandle << LL_ENDL; + LL_DEBUGS("Voice") << "Sending Session.Terminate with handle " << session->mHandle << LL_ENDL; stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Terminate.1\">" << "<SessionHandle>" << session->mHandle << "</SessionHandle>" << "</Request>\n\n\n"; - + writeString(stream.str()); */ } @@ -2802,13 +2693,13 @@ void LLVivoxVoiceClient::sessionTerminateSendMessage(const sessionStatePtr_t &se void LLVivoxVoiceClient::sessionGroupTerminateSendMessage(const sessionStatePtr_t &session) { std::ostringstream stream; - - LL_DEBUGS("Voice") << "Sending SessionGroup.Terminate with handle " << session->mGroupHandle << LL_ENDL; + + LL_DEBUGS("Voice") << "Sending SessionGroup.Terminate with handle " << session->mGroupHandle << LL_ENDL; stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.Terminate.1\">" << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>" << "</Request>\n\n\n"; - + writeString(stream.str()); } @@ -2818,17 +2709,17 @@ void LLVivoxVoiceClient::sessionMediaDisconnectSendMessage(const sessionStatePtr sessionGroupTerminateSendMessage(session); return; /* - LL_DEBUGS("Voice") << "Sending Session.MediaDisconnect with handle " << session->mHandle << LL_ENDL; + LL_DEBUGS("Voice") << "Sending Session.MediaDisconnect with handle " << session->mHandle << LL_ENDL; stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.MediaDisconnect.1\">" << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>" << "<SessionHandle>" << session->mHandle << "</SessionHandle>" << "<Media>Audio</Media>" << "</Request>\n\n\n"; - + writeString(stream.str()); */ - + } @@ -2838,7 +2729,7 @@ void LLVivoxVoiceClient::getCaptureDevicesSendMessage() stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.GetCaptureDevices.1\">" << "</Request>\n\n\n"; - + writeString(stream.str()); } @@ -2848,7 +2739,7 @@ void LLVivoxVoiceClient::getRenderDevicesSendMessage() stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.GetRenderDevices.1\">" << "</Request>\n\n\n"; - + writeString(stream.str()); } @@ -2876,7 +2767,7 @@ void LLVivoxVoiceClient::setCaptureDevice(const std::string& name) if(!mCaptureDevice.empty()) { mCaptureDevice.clear(); - mCaptureDeviceDirty = true; + mCaptureDeviceDirty = true; } } else @@ -2884,7 +2775,7 @@ void LLVivoxVoiceClient::setCaptureDevice(const std::string& name) if(mCaptureDevice != name) { mCaptureDevice = name; - mCaptureDeviceDirty = true; + mCaptureDeviceDirty = true; } } } @@ -2894,7 +2785,7 @@ void LLVivoxVoiceClient::setDevicesListUpdated(bool state) } void LLVivoxVoiceClient::clearRenderDevices() -{ +{ LL_DEBUGS("Voice") << "called" << LL_ENDL; mRenderDevices.clear(); } @@ -2917,7 +2808,7 @@ void LLVivoxVoiceClient::setRenderDevice(const std::string& name) if(!mRenderDevice.empty()) { mRenderDevice.clear(); - mRenderDeviceDirty = true; + mRenderDeviceDirty = true; } } else @@ -2925,10 +2816,10 @@ void LLVivoxVoiceClient::setRenderDevice(const std::string& name) if(mRenderDevice != name) { mRenderDevice = name; - mRenderDeviceDirty = true; + mRenderDeviceDirty = true; } } - + } void LLVivoxVoiceClient::tuningStart() @@ -2950,6 +2841,9 @@ void LLVivoxVoiceClient::tuningStart() void LLVivoxVoiceClient::tuningStop() { mTuningMode = false; + // force a renegotiation. + mCurrentParcelLocalID = 0; + mCurrentRegionName = ""; } bool LLVivoxVoiceClient::inTuningMode() @@ -2958,7 +2852,7 @@ bool LLVivoxVoiceClient::inTuningMode() } void LLVivoxVoiceClient::tuningRenderStartSendMessage(const std::string& name, bool loop) -{ +{ mTuningAudioFile = name; std::ostringstream stream; stream @@ -2966,7 +2860,7 @@ void LLVivoxVoiceClient::tuningRenderStartSendMessage(const std::string& name, b << "<SoundFilePath>" << mTuningAudioFile << "</SoundFilePath>" << "<Loop>" << (loop?"1":"0") << "</Loop>" << "</Request>\n\n\n"; - + writeString(stream.str()); } @@ -2977,33 +2871,33 @@ void LLVivoxVoiceClient::tuningRenderStopSendMessage() << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.RenderAudioStop.1\">" << "<SoundFilePath>" << mTuningAudioFile << "</SoundFilePath>" << "</Request>\n\n\n"; - + writeString(stream.str()); } void LLVivoxVoiceClient::tuningCaptureStartSendMessage(int loop) { LL_DEBUGS("Voice") << "sending CaptureAudioStart" << LL_ENDL; - + std::ostringstream stream; stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.CaptureAudioStart.1\">" << "<Duration>-1</Duration>" << "<LoopToRenderDevice>" << loop << "</LoopToRenderDevice>" << "</Request>\n\n\n"; - + writeString(stream.str()); } void LLVivoxVoiceClient::tuningCaptureStopSendMessage() { LL_DEBUGS("Voice") << "sending CaptureAudioStop" << LL_ENDL; - + std::ostringstream stream; stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.CaptureAudioStop.1\">" << "</Request>\n\n\n"; - + writeString(stream.str()); mTuningEnergy = 0.0f; @@ -3022,7 +2916,7 @@ void LLVivoxVoiceClient::tuningSetMicVolume(float volume) void LLVivoxVoiceClient::tuningSetSpeakerVolume(float volume) { - int scaled_volume = scale_speaker_volume(volume); + int scaled_volume = scale_speaker_volume(volume); if(scaled_volume != mTuningSpeakerVolume) { @@ -3030,7 +2924,7 @@ void LLVivoxVoiceClient::tuningSetSpeakerVolume(float volume) mTuningSpeakerVolumeDirty = true; } } - + float LLVivoxVoiceClient::tuningGetEnergy(void) { return mTuningEnergy; @@ -3039,13 +2933,13 @@ float LLVivoxVoiceClient::tuningGetEnergy(void) bool LLVivoxVoiceClient::deviceSettingsAvailable() { bool result = true; - + if(!sConnected) result = false; - + if(mRenderDevices.empty()) result = false; - + return result; } bool LLVivoxVoiceClient::deviceSettingsUpdated() @@ -3056,7 +2950,7 @@ bool LLVivoxVoiceClient::deviceSettingsUpdated() // a hot swap event or a polling of the audio devices has been parsed since the last redraw of the input and output device panel. mDevicesListUpdated = false; // toggle the setting } - return updated; + return updated; } void LLVivoxVoiceClient::refreshDeviceLists(bool clearCurrentList) @@ -3089,9 +2983,9 @@ void LLVivoxVoiceClient::giveUp() static void oldSDKTransform (LLVector3 &left, LLVector3 &up, LLVector3 &at, LLVector3d &pos, LLVector3 &vel) { F32 nat[3], nup[3], nl[3]; // the new at, up, left vectors and the new position and velocity -// F32 nvel[3]; +// F32 nvel[3]; F64 npos[3]; - + // The original XML command was sent like this: /* << "<Position>" @@ -3149,7 +3043,7 @@ static void oldSDKTransform (LLVector3 &left, LLVector3 &up, LLVector3 &at, LLVe left.mV[i] = nl[i]; pos.mdV[i] = npos[i]; } - + // This was the original transform done in the SDK nat[0] = at.mV[2]; nat[1] = 0; // y component of at vector is always 0, this was up[2] @@ -3203,7 +3097,7 @@ static void oldSDKTransform (LLVector3 &left, LLVector3 &up, LLVector3 &at, LLVe left.mV[i] = nl[i]; pos.mdV[i] = npos[i]; } - + #endif } @@ -3213,7 +3107,7 @@ void LLVivoxVoiceClient::setHidden(bool hidden) if (mHidden && inSpatialChannel()) { - // get out of the channel entirely + // get out of the channel entirely leaveAudioSession(); } else @@ -3223,20 +3117,20 @@ void LLVivoxVoiceClient::setHidden(bool hidden) } void LLVivoxVoiceClient::sendPositionAndVolumeUpdate(void) -{ +{ std::ostringstream stream; - + if (mSpatialCoordsDirty && inSpatialChannel()) { LLVector3 l, u, a, vel; LLVector3d pos; mSpatialCoordsDirty = false; - + // Always send both speaker and listener positions together. - stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Set3DPosition.1\">" + stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Set3DPosition.1\">" << "<SessionHandle>" << getAudioSessionHandle() << "</SessionHandle>"; - + stream << "<SpeakerPosition>"; LLMatrix3 avatarRot = mAvatarRot.getMatrix3(); @@ -3252,7 +3146,7 @@ void LLVivoxVoiceClient::sendPositionAndVolumeUpdate(void) // SLIM SDK: the old SDK was doing a transform on the passed coordinates that the new one doesn't do anymore. // The old transform is replicated by this function. oldSDKTransform(l, u, a, pos, vel); - + if (mHidden) { for (int i=0;i<3;++i) @@ -3260,7 +3154,7 @@ void LLVivoxVoiceClient::sendPositionAndVolumeUpdate(void) pos.mdV[i] = VX_NULL_POSITION; } } - + stream << "<Position>" << "<X>" << pos.mdV[VX] << "</X>" @@ -3288,7 +3182,7 @@ void LLVivoxVoiceClient::sendPositionAndVolumeUpdate(void) << "<Z>" << l.mV [VZ] << "</Z>" << "</LeftOrientation>" ; - + stream << "</SpeakerPosition>"; stream << "<ListenerPosition>"; @@ -3296,7 +3190,7 @@ void LLVivoxVoiceClient::sendPositionAndVolumeUpdate(void) LLVector3d earPosition; LLVector3 earVelocity; LLMatrix3 earRot; - + switch(mEarLocation) { case earLocCamera: @@ -3305,13 +3199,13 @@ void LLVivoxVoiceClient::sendPositionAndVolumeUpdate(void) earVelocity = mCameraVelocity; earRot = mCameraRot; break; - + case earLocAvatar: earPosition = mAvatarPosition; earVelocity = mAvatarVelocity; earRot = avatarRot; break; - + case earLocMixed: earPosition = mAvatarPosition; earVelocity = mAvatarVelocity; @@ -3326,9 +3220,9 @@ void LLVivoxVoiceClient::sendPositionAndVolumeUpdate(void) pos = earPosition; vel = earVelocity; - + oldSDKTransform(l, u, a, pos, vel); - + if (mHidden) { for (int i=0;i<3;++i) @@ -3336,7 +3230,7 @@ void LLVivoxVoiceClient::sendPositionAndVolumeUpdate(void) pos.mdV[i] = VX_NULL_POSITION; } } - + stream << "<Position>" << "<X>" << pos.mdV[VX] << "</X>" @@ -3369,19 +3263,19 @@ void LLVivoxVoiceClient::sendPositionAndVolumeUpdate(void) stream << "<ReqDispositionType>1</ReqDispositionType>"; //do not generate responses for update requests stream << "</Request>\n\n\n"; - } - + } + if(mAudioSession && (mAudioSession->mVolumeDirty || mAudioSession->mMuteDirty)) { participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin(); mAudioSession->mVolumeDirty = false; mAudioSession->mMuteDirty = false; - + for(; iter != mAudioSession->mParticipantsByURI.end(); iter++) { participantStatePtr_t p(iter->second); - + if(p->mVolumeDirty) { // Can't set volume/mute for yourself @@ -3390,7 +3284,7 @@ void LLVivoxVoiceClient::sendPositionAndVolumeUpdate(void) // scale from the range 0.0-1.0 to vivox volume in the range 0-100 S32 volume = ll_round(p->mVolume / VOLUME_SCALE_VIVOX); bool mute = p->mOnMuteList; - + if(mute) { // SetParticipantMuteForMe doesn't work in p2p sessions. @@ -3401,16 +3295,16 @@ void LLVivoxVoiceClient::sendPositionAndVolumeUpdate(void) // changing it to 0, so that we can return to it when unmuting. p->mVolumeSet = true; } - + if(volume == 0) { mute = true; } LL_DEBUGS("Voice") << "Setting volume/mute for avatar " << p->mAvatarID << " to " << volume << (mute?"/true":"/false") << LL_ENDL; - + // SLIM SDK: Send both volume and mute commands. - + // Send a "volume for me" command for the user. stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.SetParticipantVolumeForMe.1\">" << "<SessionHandle>" << getAudioSessionHandle() << "</SessionHandle>" @@ -3430,7 +3324,7 @@ void LLVivoxVoiceClient::sendPositionAndVolumeUpdate(void) << "</Request>\n\n\n"; } } - + p->mVolumeDirty = false; } } @@ -3450,13 +3344,13 @@ void LLVivoxVoiceClient::buildSetCaptureDevice(std::ostringstream &stream) if(mCaptureDeviceDirty) { LL_DEBUGS("Voice") << "Setting input device = \"" << mCaptureDevice << "\"" << LL_ENDL; - - stream + + stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetCaptureDevice.1\">" << "<CaptureDeviceSpecifier>" << mCaptureDevice << "</CaptureDeviceSpecifier>" << "</Request>" << "\n\n\n"; - + mCaptureDeviceDirty = false; } } @@ -3548,7 +3442,7 @@ void LLVivoxVoiceClient::sendLocalAudioUpdates() * Because of the recurring voice cutout issues (SL-15072) we are going to try * to disable the automatic VAD (Voice Activity Detection) and set the associated * parameters directly. We will expose them via Debug Settings and that should - * let us iterate on a collection of values that work for us. Hopefully! + * let us iterate on a collection of values that work for us. Hopefully! * * From the VIVOX Docs: * @@ -3558,16 +3452,16 @@ void LLVivoxVoiceClient::sendLocalAudioUpdates() * for the VAD to switch back to silence from speech mode after the last speech * frame has been detected. * - * VadNoiseFloor: A dimensionless value between 0 and + * VadNoiseFloor: A dimensionless value between 0 and * 20000 (default 576) that controls the maximum level at which the noise floor * may be set at by the VAD's noise tracking. Too low of a value will make noise - * tracking ineffective (A value of 0 disables noise tracking and the VAD then - * relies purely on the sensitivity property). Too high of a value will make + * tracking ineffective (A value of 0 disables noise tracking and the VAD then + * relies purely on the sensitivity property). Too high of a value will make * long speech classifiable as noise. * - * VadSensitivity: A dimensionless value between 0 and + * VadSensitivity: A dimensionless value between 0 and * 100, indicating the 'sensitivity of the VAD'. Increasing this value corresponds - * to decreasing the sensitivity of the VAD (i.e. '0' is most sensitive, + * to decreasing the sensitivity of the VAD (i.e. '0' is most sensitive, * while 100 is 'least sensitive') */ void LLVivoxVoiceClient::setupVADParams(unsigned int vad_auto, @@ -3615,7 +3509,7 @@ void LLVivoxVoiceClient::onVADSettingsChange() // Response/Event handlers void LLVivoxVoiceClient::connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle, std::string &versionID) -{ +{ LLSD result = LLSD::emptyMap(); if(statusCode == 0) @@ -3649,7 +3543,7 @@ void LLVivoxVoiceClient::connectorCreateResponse(int statusCode, std::string &st // this is usually fatal, but a long timeout might work result["connector"] = LLSD::Boolean(false); result["retry"] = LLSD::Real(CONNECT_ATTEMPT_TIMEOUT); - + LL_WARNS("Voice") << "Voice connection failed" << LL_ENDL; } else if (statusCode == 10006) // name resolution failure - a shorter retry may work @@ -3657,7 +3551,7 @@ void LLVivoxVoiceClient::connectorCreateResponse(int statusCode, std::string &st // some networks have slower DNS, but a short timeout might let it catch up result["connector"] = LLSD::Boolean(false); result["retry"] = LLSD::Real(CONNECT_DNS_TIMEOUT); - + LL_WARNS("Voice") << "Voice connection DNS lookup failed" << LL_ENDL; } else // unknown failure - give up @@ -3671,13 +3565,13 @@ void LLVivoxVoiceClient::connectorCreateResponse(int statusCode, std::string &st } void LLVivoxVoiceClient::loginResponse(int statusCode, std::string &statusString, std::string &accountHandle, int numberOfAliases) -{ +{ LLSD result = LLSD::emptyMap(); LL_DEBUGS("Voice") << "Account.Login response (" << statusCode << "): " << statusString << LL_ENDL; - + // Status code of 20200 means "bad password". We may want to special-case that at some point. - + if ( statusCode == HTTP_UNAUTHORIZED ) { // Login failure which is probably caused by the delay after a user's password being updated. @@ -3702,20 +3596,20 @@ void LLVivoxVoiceClient::loginResponse(int statusCode, std::string &statusString } void LLVivoxVoiceClient::sessionCreateResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle) -{ +{ sessionStatePtr_t session(findSessionBeingCreatedByURI(requestId)); - + if(session) { session->mCreateInProgress = false; } - + if(statusCode != 0) { LL_WARNS("Voice") << "Session.Create response failure (" << statusCode << "): " << statusString << LL_ENDL; if(session) { - session->mErrorStatusCode = statusCode; + session->mErrorStatusCode = statusCode; session->mErrorStatusString = statusString; if(session == mAudioSession) { @@ -3746,20 +3640,20 @@ void LLVivoxVoiceClient::sessionCreateResponse(std::string &requestId, int statu } void LLVivoxVoiceClient::sessionGroupAddSessionResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle) -{ +{ sessionStatePtr_t session(findSessionBeingCreatedByURI(requestId)); - + if(session) { session->mCreateInProgress = false; } - + if(statusCode != 0) { LL_WARNS("Voice") << "SessionGroup.AddSession response failure (" << statusCode << "): " << statusString << LL_ENDL; if(session) { - session->mErrorStatusCode = statusCode; + session->mErrorStatusCode = statusCode; session->mErrorStatusString = statusString; if(session == mAudioSession) { @@ -3820,7 +3714,7 @@ void LLVivoxVoiceClient::sessionConnectResponse(std::string &requestId, int stat } void LLVivoxVoiceClient::logoutResponse(int statusCode, std::string &statusString) -{ +{ if(statusCode != 0) { LL_WARNS("Voice") << "Account.Logout response failure: " << statusString << LL_ENDL; @@ -3838,21 +3732,21 @@ void LLVivoxVoiceClient::connectorShutdownResponse(int statusCode, std::string & LL_WARNS("Voice") << "Connector.InitiateShutdown response failure: " << statusString << LL_ENDL; // Should this ever fail? do we care if it does? } - + sConnected = false; mShutdownComplete = true; - + LLSD vivoxevent(LLSDMap("connector", LLSD::Boolean(false))); mVivoxPump.post(vivoxevent); } void LLVivoxVoiceClient::sessionAddedEvent( - std::string &uriString, - std::string &alias, - std::string &sessionHandle, - std::string &sessionGroupHandle, - bool isChannel, + std::string &uriString, + std::string &alias, + std::string &sessionHandle, + std::string &sessionGroupHandle, + bool isChannel, bool incoming, std::string &nameString, std::string &applicationString) @@ -3860,7 +3754,7 @@ void LLVivoxVoiceClient::sessionAddedEvent( sessionStatePtr_t session; LL_INFOS("Voice") << "session " << uriString << ", alias " << alias << ", name " << nameString << " handle " << sessionHandle << LL_ENDL; - + session = addSession(uriString, sessionHandle); if(session) { @@ -3868,19 +3762,19 @@ void LLVivoxVoiceClient::sessionAddedEvent( session->mIsChannel = isChannel; session->mIncoming = incoming; session->mAlias = alias; - + // Generate a caller UUID -- don't need to do this for channels if(!session->mIsChannel) { if(IDFromName(session->mSIPURI, session->mCallerID)) { - // Normal URI(base64-encoded UUID) + // Normal URI(base64-encoded UUID) } else if(!session->mAlias.empty() && IDFromName(session->mAlias, session->mCallerID)) { // Wrong URI, but an alias is available. Stash the incoming URI as an alternate session->mAlternateSIPURI = session->mSIPURI; - + // and generate a proper URI from the ID. setSessionURI(session, sipURIFromID(session->mCallerID)); } @@ -3889,7 +3783,7 @@ void LLVivoxVoiceClient::sessionAddedEvent( LL_INFOS("Voice") << "Could not generate caller id from uri, using hash of uri " << session->mSIPURI << LL_ENDL; session->mCallerID.generate(session->mSIPURI); session->mSynthesizedCallerID = true; - + // Can't look up the name in this case -- we have to extract it from the URI. std::string namePortion = nameFromsipURI(session->mSIPURI); if(namePortion.empty()) @@ -3897,14 +3791,14 @@ void LLVivoxVoiceClient::sessionAddedEvent( // Didn't seem to be a SIP URI, just use the whole provided name. namePortion = nameString; } - + // Some incoming names may be separated with an underscore instead of a space. Fix this. LLStringUtil::replaceChar(namePortion, '_', ' '); - + // Act like we just finished resolving the name (this stores it in all the right places) avatarNameResolved(session->mCallerID, namePortion); } - + LL_INFOS("Voice") << "caller ID: " << session->mCallerID << LL_ENDL; if(!session->mSynthesizedCallerID) @@ -3919,7 +3813,7 @@ void LLVivoxVoiceClient::sessionAddedEvent( void LLVivoxVoiceClient::sessionGroupAddedEvent(std::string &sessionGroupHandle) { LL_DEBUGS("Voice") << "handle " << sessionGroupHandle << LL_ENDL; - + #if USE_SESSION_GROUPS if(mMainSessionGroupHandle.empty()) { @@ -3946,7 +3840,7 @@ void LLVivoxVoiceClient::joinedAudioSession(const sessionStatePtr_t &session) // The old session may now need to be deleted. reapSession(oldSession); } - + // This is the session we're joining. if(mIsJoiningSession) { @@ -3962,10 +3856,10 @@ void LLVivoxVoiceClient::joinedAudioSession(const sessionStatePtr_t &session) participant->mIsSelf = true; lookupName(participant->mAvatarID); - LL_INFOS("Voice") << "added self as participant \"" << participant->mAccountName + LL_INFOS("Voice") << "added self as participant \"" << participant->mAccountName << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; } - + if(!session->mIsChannel) { // this is a p2p session. Make sure the other end is added as a participant. @@ -3981,9 +3875,9 @@ void LLVivoxVoiceClient::joinedAudioSession(const sessionStatePtr_t &session) participant->mDisplayName = session->mName; avatarNameResolved(participant->mAvatarID, session->mName); } - + // TODO: Question: Do we need to set up mAvatarID/mAvatarIDValid here? - LL_INFOS("Voice") << "added caller as participant \"" << participant->mAccountName + LL_INFOS("Voice") << "added caller as participant \"" << participant->mAccountName << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; } } @@ -3991,11 +3885,11 @@ void LLVivoxVoiceClient::joinedAudioSession(const sessionStatePtr_t &session) } void LLVivoxVoiceClient::sessionRemovedEvent( - std::string &sessionHandle, + std::string &sessionHandle, std::string &sessionGroupHandle) { LL_INFOS("Voice") << "handle " << sessionHandle << LL_ENDL; - + sessionStatePtr_t session(findSession(sessionHandle)); if(session) { @@ -4003,15 +3897,15 @@ void LLVivoxVoiceClient::sessionRemovedEvent( // This message invalidates the session's handle. Set it to empty. clearSessionHandle(session); - + // This also means that the session's session group is now empty. // Terminate the session group so it doesn't leak. sessionGroupTerminateSendMessage(session); - + // Reset the media state (we now have no info) session->mMediaStreamState = streamStateUnknown; //session->mTextStreamState = streamStateUnknown; - + // Conditionally delete the session reapSession(session); } @@ -4027,7 +3921,7 @@ void LLVivoxVoiceClient::reapSession(const sessionStatePtr_t &session) { if(session) { - + if(session->mCreateInProgress) { LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (create in progress)" << LL_ENDL; @@ -4049,7 +3943,7 @@ void LLVivoxVoiceClient::reapSession(const sessionStatePtr_t &session) // We don't have a reason to keep tracking this session, so just delete it. LL_DEBUGS("Voice") << "deleting session " << session->mSIPURI << LL_ENDL; deleteSession(session); - } + } } else { @@ -4061,32 +3955,32 @@ void LLVivoxVoiceClient::reapSession(const sessionStatePtr_t &session) bool LLVivoxVoiceClient::sessionNeedsRelog(const sessionStatePtr_t &session) { bool result = false; - + if(session) { // Only make this check for spatial channels (so it won't happen for group or p2p calls) if(session->mIsSpatial) - { + { std::string::size_type atsign; - + atsign = session->mSIPURI.find("@"); - + if(atsign != std::string::npos) { std::string urihost = session->mSIPURI.substr(atsign + 1); if(stricmp(urihost.c_str(), mVoiceSIPURIHostName.c_str())) { // The hostname in this URI is different from what we expect. This probably means we need to relog. - + // We could make a ProvisionVoiceAccountRequest and compare the result with the current values of // mVoiceSIPURIHostName and mVoiceAccountServerURI to be really sure, but this is a pretty good indicator. - + result = true; } } } } - + return result; } @@ -4102,9 +3996,9 @@ void LLVivoxVoiceClient::leftAudioSession(const sessionStatePtr_t &session) } void LLVivoxVoiceClient::accountLoginStateChangeEvent( - std::string &accountHandle, - int statusCode, - std::string &statusString, + std::string &accountHandle, + int statusCode, + std::string &statusString, int state) { LLSD levent = LLSD::emptyMap(); @@ -4116,9 +4010,9 @@ void LLVivoxVoiceClient::accountLoginStateChangeEvent( login_state_logging_in = 2, login_state_logging_out = 3, login_state_resetting = 4, - login_state_error=100 + login_state_error=100 */ - + LL_DEBUGS("Voice") << "state change event: " << state << LL_ENDL; switch(state) { @@ -4148,7 +4042,7 @@ void LLVivoxVoiceClient::accountLoginStateChangeEvent( mVivoxPump.post(levent); break; - + default: //Used to be a commented out warning LL_WARNS("Voice") << "unknown account state event: " << state << LL_ENDL; @@ -4185,24 +4079,24 @@ void LLVivoxVoiceClient::mediaCompletionEvent(std::string &sessionGroupHandle, s } void LLVivoxVoiceClient::mediaStreamUpdatedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle, - int statusCode, - std::string &statusString, - int state, + std::string &sessionHandle, + std::string &sessionGroupHandle, + int statusCode, + std::string &statusString, + int state, bool incoming) { sessionStatePtr_t session(findSession(sessionHandle)); - + LL_DEBUGS("Voice") << "session " << sessionHandle << ", status code " << statusCode << ", string \"" << statusString << "\"" << LL_ENDL; - + if(session) { // We know about this session - + // Save the state for later use session->mMediaStreamState = state; - + switch(statusCode) { case 0: @@ -4230,9 +4124,9 @@ void LLVivoxVoiceClient::mediaStreamUpdatedEvent( session->mVoiceActive = true; session->mMediaConnectInProgress = false; joinedAudioSession(session); - case streamStateConnecting: // do nothing, but prevents a warning getting into the logs. + case streamStateConnecting: // do nothing, but prevents a warning getting into the logs. break; - + case streamStateRinging: if(incoming) { @@ -4251,13 +4145,13 @@ void LLVivoxVoiceClient::mediaStreamUpdatedEvent( } } break; - + default: LL_WARNS("Voice") << "unknown state " << state << LL_ENDL; break; - + } - + } else { @@ -4267,12 +4161,12 @@ void LLVivoxVoiceClient::mediaStreamUpdatedEvent( } void LLVivoxVoiceClient::participantAddedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle, - std::string &uriString, - std::string &alias, - std::string &nameString, - std::string &displayNameString, + std::string &sessionHandle, + std::string &sessionGroupHandle, + std::string &uriString, + std::string &alias, + std::string &nameString, + std::string &displayNameString, int participantType) { sessionStatePtr_t session(findSession(sessionHandle)); @@ -4283,7 +4177,7 @@ void LLVivoxVoiceClient::participantAddedEvent( { participant->mAccountName = nameString; - LL_DEBUGS("Voice") << "added participant \"" << participant->mAccountName + LL_DEBUGS("Voice") << "added participant \"" << participant->mAccountName << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; if(participant->mAvatarIDValid) @@ -4305,7 +4199,7 @@ void LLVivoxVoiceClient::participantAddedEvent( // Problems with both of the above, fall back to the account name namePortion = nameString; } - + // Set the display name (which is a hint to the active speakers window not to do its own lookup) participant->mDisplayName = namePortion; avatarNameResolved(participant->mAvatarID, namePortion); @@ -4315,10 +4209,10 @@ void LLVivoxVoiceClient::participantAddedEvent( } void LLVivoxVoiceClient::participantRemovedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle, - std::string &uriString, - std::string &alias, + std::string &sessionHandle, + std::string &sessionGroupHandle, + std::string &uriString, + std::string &alias, std::string &nameString) { sessionStatePtr_t session(findSession(sessionHandle)); @@ -4343,20 +4237,20 @@ void LLVivoxVoiceClient::participantRemovedEvent( void LLVivoxVoiceClient::participantUpdatedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle, - std::string &uriString, - std::string &alias, - bool isModeratorMuted, - bool isSpeaking, - int volume, + std::string &sessionHandle, + std::string &sessionGroupHandle, + std::string &uriString, + std::string &alias, + bool isModeratorMuted, + bool isSpeaking, + int volume, F32 energy) { sessionStatePtr_t session(findSession(sessionHandle)); if(session) { participantStatePtr_t participant(session->findParticipant(uriString)); - + if(participant) { //LL_INFOS("Voice") << "Participant Update for " << participant->mDisplayName << LL_ENDL; @@ -4381,25 +4275,25 @@ void LLVivoxVoiceClient::participantUpdatedEvent( { participant->mVolume = (F32)volume * VOLUME_SCALE_VIVOX; } - - // *HACK: mantipov: added while working on EXT-3544 - /* - Sometimes LLVoiceClient::participantUpdatedEvent callback is called BEFORE - LLViewerChatterBoxSessionAgentListUpdates::post() sometimes AFTER. - - participantUpdatedEvent updates voice participant state in particular participantState::mIsModeratorMuted - Originally we wanted to update session Speaker Manager to fire LLSpeakerVoiceModerationEvent to fix the EXT-3544 bug. - Calling of the LLSpeakerMgr::update() method was added into LLIMMgr::processAgentListUpdates. - - But in case participantUpdatedEvent() is called after LLViewerChatterBoxSessionAgentListUpdates::post() - voice participant mIsModeratorMuted is changed after speakers are updated in Speaker Manager - and event is not fired. - - So, we have to call LLSpeakerMgr::update() here. + + // *HACK: mantipov: added while working on EXT-3544 + /* + Sometimes LLVoiceClient::participantUpdatedEvent callback is called BEFORE + LLViewerChatterBoxSessionAgentListUpdates::post() sometimes AFTER. + + participantUpdatedEvent updates voice participant state in particular participantState::mIsModeratorMuted + Originally we wanted to update session Speaker Manager to fire LLSpeakerVoiceModerationEvent to fix the EXT-3544 bug. + Calling of the LLSpeakerMgr::update() method was added into LLIMMgr::processAgentListUpdates. + + But in case participantUpdatedEvent() is called after LLViewerChatterBoxSessionAgentListUpdates::post() + voice participant mIsModeratorMuted is changed after speakers are updated in Speaker Manager + and event is not fired. + + So, we have to call LLSpeakerMgr::update() here. */ LLVoiceChannel* voice_cnl = LLVoiceChannel::getCurrentVoiceChannel(); - - // ignore session ID of local chat + + // ignore session ID of local chat if (voice_cnl && voice_cnl->getSessionID().notNull()) { LLSpeakerMgr* speaker_manager = LLIMModel::getInstance()->getSpeakerManager(voice_cnl->getSessionID()); @@ -4428,10 +4322,10 @@ void LLVivoxVoiceClient::participantUpdatedEvent( } void LLVivoxVoiceClient::messageEvent( - std::string &sessionHandle, - std::string &uriString, - std::string &alias, - std::string &messageHeader, + std::string &sessionHandle, + std::string &uriString, + std::string &alias, + std::string &messageHeader, std::string &messageBody, std::string &applicationString) { @@ -4452,7 +4346,7 @@ void LLVivoxVoiceClient::messageEvent( const std::string endSpan = "</span>"; std::string::size_type start; std::string::size_type end; - + // Default to displaying the raw string, so the message gets through. message = messageBody; @@ -4464,38 +4358,38 @@ void LLVivoxVoiceClient::messageEvent( if(start != std::string::npos) { start += startMarker2.size(); - + if(end != std::string::npos) end -= start; - + message.assign(messageBody, start, end); } - else + else { // Didn't find a <body>, try looking for a <span> instead. start = messageBody.find(startSpan); start = messageBody.find(startMarker2, start); end = messageBody.find(endSpan); - + if(start != std::string::npos) { start += startMarker2.size(); - + if(end != std::string::npos) end -= start; - + message.assign(messageBody, start, end); - } + } } - } - + } + // LL_DEBUGS("Voice") << " raw message = \n" << message << LL_ENDL; // strip formatting tags { std::string::size_type start; std::string::size_type end; - + while((start = message.find('<')) != std::string::npos) { if((end = message.find('>', start + 1)) != std::string::npos) @@ -4510,7 +4404,7 @@ void LLVivoxVoiceClient::messageEvent( } } } - + // Decode ampersand-escaped chars { std::string::size_type mark = 0; @@ -4522,14 +4416,14 @@ void LLVivoxVoiceClient::messageEvent( message.replace(mark, 4, "<"); mark += 1; } - + mark = 0; while((mark = message.find(">", mark)) != std::string::npos) { message.replace(mark, 4, ">"); mark += 1; } - + mark = 0; while((mark = message.find("&", mark)) != std::string::npos) { @@ -4537,12 +4431,12 @@ void LLVivoxVoiceClient::messageEvent( mark += 1; } } - + // strip leading/trailing whitespace (since we always seem to get a couple newlines) LLStringUtil::trim(message); - + // LL_DEBUGS("Voice") << " stripped message = \n" << message << LL_ENDL; - + sessionStatePtr_t session(findSession(sessionHandle)); if(session) { @@ -4552,7 +4446,7 @@ void LLVivoxVoiceClient::messageEvent( LLChat chat; chat.mMuted = is_muted && !is_linden; - + if(!chat.mMuted) { chat.mFromID = session->mCallerID; @@ -4563,7 +4457,7 @@ void LLVivoxVoiceClient::messageEvent( { // TODO: Question: Return do not disturb mode response here? Or maybe when session is started instead? } - + LL_DEBUGS("Voice") << "adding message, name " << session->mName << " session " << session->mIMSessionID << ", target " << session->mCallerID << LL_ENDL; LLIMMgr::getInstance()->addMessage(session->mIMSessionID, session->mCallerID, @@ -4576,14 +4470,14 @@ void LLVivoxVoiceClient::messageEvent( LLUUID::null, // default arg LLVector3::zero); // default arg } - } + } } } void LLVivoxVoiceClient::sessionNotificationEvent(std::string &sessionHandle, std::string &uriString, std::string ¬ificationType) { sessionStatePtr_t session(findSession(sessionHandle)); - + if(session) { participantStatePtr_t participant(session->findParticipant(uriString)); @@ -4646,11 +4540,11 @@ void LLVivoxVoiceClient::muteListChanged() if(mAudioSession) { participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin(); - + for(; iter != mAudioSession->mParticipantsByURI.end(); iter++) { participantStatePtr_t p(iter->second); - + // Check to see if this participant is on the mute list already if(p->updateMuteState()) mAudioSession->mVolumeDirty = true; @@ -4660,18 +4554,18 @@ void LLVivoxVoiceClient::muteListChanged() ///////////////////////////// // Managing list of participants -LLVivoxVoiceClient::participantState::participantState(const std::string &uri) : - mURI(uri), - mPTT(false), - mIsSpeaking(false), - mIsModeratorMuted(false), - mLastSpokeTimestamp(0.f), - mPower(0.f), - mVolume(LLVoiceClient::VOLUME_DEFAULT), +LLVivoxVoiceClient::participantState::participantState(const std::string &uri) : + mURI(uri), + mPTT(false), + mIsSpeaking(false), + mIsModeratorMuted(false), + mLastSpokeTimestamp(0.f), + mPower(0.f), + mVolume(LLVoiceClient::VOLUME_DEFAULT), mUserVolume(0), - mOnMuteList(false), + mOnMuteList(false), mVolumeSet(false), - mVolumeDirty(false), + mVolumeDirty(false), mAvatarIDValid(false), mIsSelf(false) { @@ -4681,7 +4575,7 @@ LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::addP { participantStatePtr_t result; bool useAlternateURI = false; - + // Note: this is mostly the body of LLVivoxVoiceClient::sessionState::findParticipant(), but since we need to know if it // matched the alternate SIP URI (so we can add it properly), we need to reproduce it here. { @@ -4703,14 +4597,14 @@ LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::addP result = iter->second; } } - + if(!result) { // participant isn't already in one list or the other. result.reset(new participantState(useAlternateURI?mSIPURI:uri)); mParticipantsByURI.insert(participantMap::value_type(result->mURI, result)); mParticipantsChanged = true; - + // Try to do a reverse transform on the URI to get the GUID back. { LLUUID id; @@ -4726,12 +4620,12 @@ LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::addP result->mAvatarID.generate(uri); } } - + if(result->updateMuteState()) { mMuteDirty = true; } - + mParticipantsByUUID.insert(participantUUIDMap::value_type(result->mAvatarID, result)); if (LLSpeakerVolumeStorage::getInstance()->getSpeakerVolume(result->mAvatarID, result->mVolume)) @@ -4739,10 +4633,10 @@ LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::addP result->mVolumeDirty = true; mVolumeDirty = true; } - + LL_DEBUGS("Voice") << "participant \"" << result->mURI << "\" added." << LL_ENDL; } - + return result; } @@ -4771,9 +4665,9 @@ void LLVivoxVoiceClient::sessionState::removeParticipant(const LLVivoxVoiceClien { participantMap::iterator iter = mParticipantsByURI.find(participant->mURI); participantUUIDMap::iterator iter2 = mParticipantsByUUID.find(participant->mAvatarID); - + LL_DEBUGS("Voice") << "participant \"" << participant->mURI << "\" (" << participant->mAvatarID << ") removed." << LL_ENDL; - + if(iter == mParticipantsByURI.end()) { LL_WARNS("Voice") << "Internal error: participant " << participant->mURI << " not in URI map" << LL_ENDL; @@ -4804,7 +4698,7 @@ void LLVivoxVoiceClient::sessionState::removeAllParticipants() { removeParticipant(mParticipantsByURI.begin()->second); } - + if(!mParticipantsByUUID.empty()) { LL_WARNS("Voice") << "Internal error: empty URI map, non-empty UUID map" << LL_ENDL; @@ -4830,10 +4724,10 @@ 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(); + iter != mAudioSession->mParticipantsByUUID.end(); iter++) { participants.insert(iter->first); @@ -4843,18 +4737,18 @@ 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; } LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::findParticipant(const std::string &uri) { participantStatePtr_t result; - + participantMap::iterator iter = mParticipantsByURI.find(uri); if(iter == mParticipantsByURI.end()) @@ -4871,7 +4765,7 @@ LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::find { result = iter->second; } - + return result; } @@ -4891,12 +4785,12 @@ LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::find LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::findParticipantByID(const LLUUID& id) { participantStatePtr_t result; - + if(mAudioSession) { result = mAudioSession->findParticipantByID(id); } - + return result; } @@ -4907,15 +4801,15 @@ bool LLVivoxVoiceClient::checkParcelChanged(bool update) { LLViewerRegion *region = gAgent.getRegion(); LLParcel *parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - + if(region && parcel) { S32 parcelLocalID = parcel->getLocalID(); std::string regionName = region->getName(); - + // LL_DEBUGS("Voice") << "Region name = \"" << regionName << "\", parcel local ID = " << parcelLocalID << ", cap URI = \"" << capURI << "\"" << LL_ENDL; - - // The region name starts out empty and gets filled in later. + + // The region name starts out empty and gets filled in later. // Also, the cap gets filled in a short time after the region cross, but a little too late for our purposes. // If either is empty, wait for the next time around. if(!regionName.empty()) @@ -4943,7 +4837,7 @@ bool LLVivoxVoiceClient::switchChannel( std::string hash) { bool needsSwitch = !mIsInChannel; - + if (mIsInChannel) { if (mSessionTerminateRequested) @@ -4951,8 +4845,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 { @@ -5018,7 +4914,7 @@ bool LLVivoxVoiceClient::switchChannel( mNextAudioSession->mReconnect = !no_reconnect; mNextAudioSession->mIsP2P = is_p2p; } - + if (mIsInChannel) { // If we're already in a channel, or if we're joining one, terminate @@ -5042,23 +4938,24 @@ 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: \"" << mSpatialSessionURI << "\"" << LL_ENDL; - LL_DEBUGS("Voice") << "got spatial channel uri: \"" << uri << "\"" << LL_ENDL; - if((mIsInChannel && mAudioSession && !(mAudioSession->mIsSpatial)) || (mNextAudioSession && !(mNextAudioSession->mIsSpatial))) { // User is in a non-spatial chat or joining a non-spatial chat. Don't switch channels. @@ -5074,89 +4971,44 @@ 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) + +bool LLVivoxVoiceClient::answerInvite(const std::string &sessionHandle) { - return(findSession(sessionHandle) != NULL); - + // this is only ever used to answer incoming p2p call invites. + + sessionStatePtr_t session(findSession(sessionHandle)); + if (session) + { + session->mIsSpatial = false; + session->mReconnect = false; + session->mIsP2P = true; + mProcessChannels = true; + joinSession(session); + return true; + } + + return false; } -bool LLVivoxVoiceClient::answerInvite(std::string &sessionHandle) + +void LLVivoxVoiceClient::declineInvite(const std::string &sessionHandle) { - // this is only ever used to answer incoming p2p call invites. - sessionStatePtr_t session(findSession(sessionHandle)); - if(session) - { - session->mIsSpatial = false; - session->mReconnect = false; - session->mIsP2P = true; - - joinSession(session); - return true; - } - - return false; + if (session) + { + sessionMediaDisconnectSendMessage(session); + } } bool LLVivoxVoiceClient::isVoiceWorking() const @@ -5173,9 +5025,9 @@ bool LLVivoxVoiceClient::isVoiceWorking() const // Currently this will be false only for PSTN callers into group chats, and PSTN p2p calls. BOOL LLVivoxVoiceClient::isParticipantAvatar(const LLUUID &id) { - BOOL result = TRUE; + BOOL result = TRUE; sessionStatePtr_t session(findSession(id)); - + if(session) { // this is a p2p session with the indicated caller, or the session with the specified UUID. @@ -5194,22 +5046,22 @@ BOOL LLVivoxVoiceClient::isParticipantAvatar(const LLUUID &id) } } } - + return result; } // 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. +// Currently this will be false only for PSTN P2P calls. BOOL LLVivoxVoiceClient::isSessionCallBackPossible(const LLUUID &session_id) { - BOOL result = TRUE; + BOOL result = TRUE; sessionStatePtr_t session(findSession(session_id)); - + if(session != NULL) { result = session->isCallBackPossible(); } - + return result; } @@ -5219,62 +5071,71 @@ BOOL LLVivoxVoiceClient::isSessionTextIMPossible(const LLUUID &session_id) { bool result = TRUE; sessionStatePtr_t session(findSession(session_id)); - + if(session != NULL) { result = session->isTextIMPossible(); } - - return result; -} - -void LLVivoxVoiceClient::declineInvite(std::string &sessionHandle) -{ - sessionStatePtr_t session(findSession(sessionHandle)); - if(session) - { - sessionMediaDisconnectSendMessage(session); - } + return result; } void LLVivoxVoiceClient::leaveNonSpatialChannel() { LL_DEBUGS("Voice") << "Request to leave spacial channel." << LL_ENDL; - - // Make sure we don't rejoin the current session. + + // Make sure we don't rejoin the current session. sessionStatePtr_t oldNextSession(mNextAudioSession); mNextAudioSession.reset(); - + // Most likely this will still be the current session at this point, but check it anyway. reapSession(oldNextSession); - + verifySessionState(); - + sessionTerminate(); } -std::string LLVivoxVoiceClient::getCurrentChannel() +void LLVivoxVoiceClient::processChannels(bool process) { - std::string result; - - if (mIsInChannel && !mSessionTerminateRequested) - { - result = getAudioSessionURI(); - } - - return result; + mCurrentParcelLocalID = -1; + mCurrentRegionName.clear(); + mProcessChannels = process; +} + +bool LLVivoxVoiceClient::isCurrentChannel(const LLSD &channelInfo) +{ + if (!mProcessChannels || (channelInfo["voice_server_type"].asString() != VIVOX_VOICE_SERVER_TYPE)) + { + return false; + } + if (mAudioSession) + { + if (!channelInfo["session_handle"].asString().empty()) + { + return mAudioSession->mHandle == channelInfo["session_handle"].asString(); + } + return channelInfo["channel_uri"].asString() == mAudioSession->mSIPURI; + } + return false; +} + +bool LLVivoxVoiceClient::compareChannels(const LLSD& channelInfo1, const LLSD& channelInfo2) +{ + return (channelInfo1["voice_server_type"] == VIVOX_VOICE_SERVER_TYPE) && + (channelInfo1["voice_server_type"] == channelInfo2["voice_server_type"]) && + (channelInfo1["channel_uri"] == channelInfo2["channel_uri"]); } bool LLVivoxVoiceClient::inProximalChannel() { bool result = false; - + if (mIsInChannel && !mSessionTerminateRequested) { result = inSpatialChannel(); } - + return result; } @@ -5285,7 +5146,7 @@ std::string LLVivoxVoiceClient::sipURIFromID(const LLUUID &id) result += nameFromID(id); result += "@"; result += mVoiceSIPURIHostName; - + return result; } @@ -5299,24 +5160,14 @@ std::string LLVivoxVoiceClient::sipURIFromAvatar(LLVOAvatar *avatar) result += "@"; result += mVoiceSIPURIHostName; } - - 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; - + if (uuid.isNull()) { //VIVOX, the uuid emtpy look for the mURIString and return that instead. //result.assign(uuid.mURIStringName); @@ -5325,31 +5176,31 @@ std::string LLVivoxVoiceClient::nameFromID(const LLUUID &uuid) } // Prepending this apparently prevents conflicts with reserved names inside the vivox code. result = "x"; - - // Base64 encode and replace the pieces of base64 that are less compatible + + // Base64 encode and replace the pieces of base64 that are less compatible // with e-mail local-parts. // See RFC-4648 "Base 64 Encoding with URL and Filename Safe Alphabet" result += LLBase64::encode(uuid.mData, UUID_BYTES); LLStringUtil::replaceChar(result, '+', '-'); LLStringUtil::replaceChar(result, '/', '_'); - + // If you need to transform a GUID to this form on the Mac OS X command line, this will do so: // echo -n x && (echo e669132a-6c43-4ee1-a78d-6c82fff59f32 |xxd -r -p |openssl base64|tr '/+' '_-') - + // The reverse transform can be done with: // echo 'x5mkTKmxDTuGnjWyC__WfMg==' |cut -b 2- -|tr '_-' '/+' |openssl base64 -d|xxd -p - + return result; } bool LLVivoxVoiceClient::IDFromName(const std::string inName, LLUUID &uuid) { bool result = false; - + // SLIM SDK: The "name" may actually be a SIP URI such as: "sip:xFnPP04IpREWNkuw1cOXlhw==@bhr.vivox.com" // If it is, convert to a bare name before doing the transform. std::string name = nameFromsipURI(inName); - + // Doesn't look like a SIP URI, assume it's an actual name. if(name.empty()) name = inName; @@ -5357,7 +5208,7 @@ bool LLVivoxVoiceClient::IDFromName(const std::string inName, LLUUID &uuid) // This will only work if the name is of the proper form. // As an example, the account name for Monroe Linden (UUID 1673cfd3-8229-4445-8d92-ec3570e5e587) is: // "xFnPP04IpREWNkuw1cOXlhw==" - + if((name.size() == 25) && (name[0] == 'x') && (name[23] == '=') && (name[24] == '=')) { // The name appears to have the right form. @@ -5367,7 +5218,7 @@ bool LLVivoxVoiceClient::IDFromName(const std::string inName, LLUUID &uuid) LLStringUtil::replaceChar(temp, '-', '+'); LLStringUtil::replaceChar(temp, '_', '/'); - U8 rawuuid[UUID_BYTES + 1]; + U8 rawuuid[UUID_BYTES + 1]; int len = apr_base64_decode_binary(rawuuid, temp.c_str() + 1); if(len == UUID_BYTES) { @@ -5375,21 +5226,16 @@ bool LLVivoxVoiceClient::IDFromName(const std::string inName, LLUUID &uuid) memcpy(uuid.mData, rawuuid, UUID_BYTES); result = true; } - } - + } + if(!result) { // VIVOX: not a standard account name, just copy the URI name mURIString field // and hope for the best. bpj uuid.setNull(); // VIVOX, set the uuid field to nulls } - - return result; -} -std::string LLVivoxVoiceClient::displayNameFromAvatar(LLVOAvatar *avatar) -{ - return avatar->getFullname(); + return result; } std::string LLVivoxVoiceClient::sipURIFromName(std::string &name) @@ -5416,39 +5262,42 @@ std::string LLVivoxVoiceClient::nameFromsipURI(const std::string &uri) { result = uri.substr(sipOffset + 4, atOffset - (sipOffset + 4)); } - + return result; } bool LLVivoxVoiceClient::inSpatialChannel(void) { bool result = false; - + if(mAudioSession) { result = mAudioSession->mIsSpatial; } - + return result; } -std::string LLVivoxVoiceClient::getAudioSessionURI() + +LLSD LLVivoxVoiceClient::getAudioSessionChannelInfo() { - std::string result; - - if(mAudioSession) - result = mAudioSession->mSIPURI; - + LLSD result; + + if (mAudioSession) + { + result = mAudioSession->getVoiceChannelInfo(); + } + return result; } std::string LLVivoxVoiceClient::getAudioSessionHandle() { std::string result; - + if(mAudioSession) result = mAudioSession->mHandle; - + return result; } @@ -5467,11 +5316,11 @@ void LLVivoxVoiceClient::enforceTether(void) F32 camera_distance = (F32)camera_offset.magVec(); if(camera_distance > max_dist) { - tethered = mAvatarPosition + + tethered = mAvatarPosition + (max_dist / camera_distance) * camera_offset; } } - + if(dist_vec_squared(mCameraPosition, tethered) > 0.01) { mCameraPosition = tethered; @@ -5488,19 +5337,19 @@ void LLVivoxVoiceClient::updatePosition(void) LLMatrix3 rot; LLVector3d pos; LLQuaternion qrot; - + // TODO: If camera and avatar velocity are actually used by the voice system, we could compute them here... // They're currently always set to zero. - + // Send the current camera position to the voice code - rot.setRows(LLViewerCamera::getInstance()->getAtAxis(), LLViewerCamera::getInstance()->getLeftAxis (), LLViewerCamera::getInstance()->getUpAxis()); + rot.setRows(LLViewerCamera::getInstance()->getAtAxis(), LLViewerCamera::getInstance()->getLeftAxis (), LLViewerCamera::getInstance()->getUpAxis()); pos = gAgent.getRegion()->getPosGlobalFromRegion(LLViewerCamera::getInstance()->getOrigin()); - + LLVivoxVoiceClient::getInstance()->setCameraPosition( pos, // position LLVector3::zero, // velocity rot); // rotation matrix - + // Send the current avatar position to the voice code qrot = gAgentAvatarp->getRootJoint()->getWorldRotation(); pos = gAgentAvatarp->getPositionGlobal(); @@ -5508,7 +5357,7 @@ void LLVivoxVoiceClient::updatePosition(void) // TODO: Can we get the head offset from outside the LLVOAvatar? // pos += LLVector3d(mHeadOffset); pos += LLVector3d(0.f, 0.f, 1.f); - + LLVivoxVoiceClient::getInstance()->setAvatarPosition( pos, // position LLVector3::zero, // velocity @@ -5519,13 +5368,13 @@ void LLVivoxVoiceClient::updatePosition(void) void LLVivoxVoiceClient::setCameraPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot) { mCameraRequestedPosition = position; - + if(mCameraVelocity != velocity) { mCameraVelocity = velocity; mSpatialCoordsDirty = true; } - + if(mCameraRot != rot) { mCameraRot = rot; @@ -5540,19 +5389,19 @@ void LLVivoxVoiceClient::setAvatarPosition(const LLVector3d &position, const LLV 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; } @@ -5561,15 +5410,15 @@ void LLVivoxVoiceClient::setAvatarPosition(const LLVector3d &position, const LLV bool LLVivoxVoiceClient::channelFromRegion(LLViewerRegion *region, std::string &name) { bool result = false; - + if(region) { name = region->getName(); } - + if(!name.empty()) result = true; - + return result; } @@ -5599,14 +5448,14 @@ void LLVivoxVoiceClient::setVoiceEnabled(bool enabled) << " 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; @@ -5629,6 +5478,8 @@ void LLVivoxVoiceClient::setVoiceEnabled(bool enabled) LLVoiceChannel::getCurrentVoiceChannel()->deactivate(); gAgent.setVoiceConnected(false); status = LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED; + mCurrentParcelLocalID = -1; + mCurrentRegionName.clear(); } notifyStatusObservers(status); @@ -5639,38 +5490,13 @@ 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) { if(mEarLocation != loc) { LL_DEBUGS("Voice") << "Setting mEarLocation to " << loc << LL_ENDL; - + mEarLocation = loc; mSpatialCoordsDirty = true; } @@ -5678,7 +5504,7 @@ void LLVivoxVoiceClient::setEarLocation(S32 loc) void LLVivoxVoiceClient::setVoiceVolume(F32 volume) { - int scaled_volume = scale_speaker_volume(volume); + int scaled_volume = scale_speaker_volume(volume); if(scaled_volume != mSpeakerVolume) { @@ -5696,7 +5522,7 @@ void LLVivoxVoiceClient::setVoiceVolume(F32 volume) void LLVivoxVoiceClient::setMicGain(F32 volume) { int scaled_volume = scale_mic_volume(volume); - + if(scaled_volume != mMicVolume) { mMicVolume = scaled_volume; @@ -5706,29 +5532,19 @@ 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) - { - result = participant->mDisplayName; - } - + if (mProcessChannels) + { + participantStatePtr_t participant(findParticipantByID(id)); + if (participant) + { + result = participant->mDisplayName; + } + } + return result; } @@ -5737,42 +5553,47 @@ std::string LLVivoxVoiceClient::getDisplayName(const LLUUID& id) BOOL LLVivoxVoiceClient::getIsSpeaking(const LLUUID& id) { BOOL result = FALSE; + if (mProcessChannels) + { + participantStatePtr_t participant(findParticipantByID(id)); + if (participant) + { + if (participant->mSpeakingTimeout.getElapsedTimeF32() > SPEAKING_TIMEOUT) + { + participant->mIsSpeaking = FALSE; + } + result = participant->mIsSpeaking; + } + } - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - if (participant->mSpeakingTimeout.getElapsedTimeF32() > SPEAKING_TIMEOUT) - { - participant->mIsSpeaking = FALSE; - } - result = participant->mIsSpeaking; - } - return result; } BOOL LLVivoxVoiceClient::getIsModeratorMuted(const LLUUID& id) { BOOL result = FALSE; - + if (!mProcessChannels) + { + return FALSE; + } participantStatePtr_t participant(findParticipantByID(id)); if(participant) { result = participant->mIsModeratorMuted; } - + return result; } F32 LLVivoxVoiceClient::getCurrentPower(const LLUUID& id) -{ +{ F32 result = 0; participantStatePtr_t participant(findParticipantByID(id)); if(participant) { result = participant->mPower; } - + return result; } @@ -5789,19 +5610,6 @@ BOOL LLVivoxVoiceClient::getUsingPTT(const LLUUID& id) // Does "using PTT" mean they're configured with a push-to-talk button? // For now, we know there's no PTT mechanism in place, so nobody is using it. } - - return result; -} - -BOOL LLVivoxVoiceClient::getOnMuteList(const LLUUID& id) -{ - BOOL result = FALSE; - - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - result = participant->mOnMuteList; - } return result; } @@ -5811,7 +5619,7 @@ F32 LLVivoxVoiceClient::getUserVolume(const LLUUID& id) { // Minimum volume will be returned for users with voice disabled F32 result = LLVoiceClient::VOLUME_MIN; - + participantStatePtr_t participant(findParticipantByID(id)); if(participant) { @@ -5859,26 +5667,21 @@ std::string LLVivoxVoiceClient::getGroupID(const LLUUID& id) { result = participant->mGroupID; } - - return result; -} -BOOL LLVivoxVoiceClient::getAreaVoiceDisabled() -{ - return mAreaVoiceDisabled; + return result; } void LLVivoxVoiceClient::recordingLoopStart(int seconds, int deltaFramesPerControlFrame) { // LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Start)" << LL_ENDL; - + if(!mMainSessionGroupHandle.empty()) { std::ostringstream stream; stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlRecording.1\">" << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>" - << "<RecordingControlType>Start</RecordingControlType>" + << "<RecordingControlType>Start</RecordingControlType>" << "<DeltaFramesPerControlFrame>" << deltaFramesPerControlFrame << "</DeltaFramesPerControlFrame>" << "<Filename>" << "" << "</Filename>" << "<EnableAudioRecordingEvents>false</EnableAudioRecordingEvents>" @@ -5900,7 +5703,7 @@ void LLVivoxVoiceClient::recordingLoopSave(const std::string& filename) stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlRecording.1\">" << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>" - << "<RecordingControlType>Flush</RecordingControlType>" + << "<RecordingControlType>Flush</RecordingControlType>" << "<Filename>" << filename << "</Filename>" << "</Request>\n\n\n"; @@ -5918,7 +5721,7 @@ void LLVivoxVoiceClient::recordingStop() stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlRecording.1\">" << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>" - << "<RecordingControlType>Stop</RecordingControlType>" + << "<RecordingControlType>Stop</RecordingControlType>" << "</Request>\n\n\n"; writeString(stream.str()); @@ -5935,7 +5738,7 @@ void LLVivoxVoiceClient::filePlaybackStart(const std::string& filename) stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlPlayback.1\">" << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>" - << "<RecordingControlType>Start</RecordingControlType>" + << "<RecordingControlType>Start</RecordingControlType>" << "<Filename>" << filename << "</Filename>" << "</Request>\n\n\n"; @@ -5953,7 +5756,7 @@ void LLVivoxVoiceClient::filePlaybackStop() stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlPlayback.1\">" << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>" - << "<RecordingControlType>Stop</RecordingControlType>" + << "<RecordingControlType>Stop</RecordingControlType>" << "</Request>\n\n\n"; writeString(stream.str()); @@ -5994,6 +5797,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() { @@ -6031,7 +5846,7 @@ bool LLVivoxVoiceClient::sessionState::isTextIMPossible() } -/*static*/ +/*static*/ LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::matchSessionByHandle(const std::string &handle) { sessionStatePtr_t result; @@ -6045,7 +5860,7 @@ LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::matchS return result; } -/*static*/ +/*static*/ LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::matchCreatingSessionByURI(const std::string &uri) { sessionStatePtr_t result; @@ -6092,7 +5907,7 @@ void LLVivoxVoiceClient::sessionState::for_each(sessionFunc_t func) std::for_each(mSession.begin(), mSession.end(), boost::bind(for_eachPredicate, _1, func)); } -// simple test predicates. +// simple test predicates. // *TODO: These should be made into lambdas when we can pull the trigger on newer C++ features. bool LLVivoxVoiceClient::sessionState::testByHandle(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string handle) { @@ -6146,28 +5961,28 @@ LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::findSession(const std: { result = iter->second; } - + return result; } LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::findSessionBeingCreatedByURI(const std::string &uri) -{ +{ sessionStatePtr_t result = sessionState::matchCreatingSessionByURI(uri); - + return result; } LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::findSession(const LLUUID &participant_id) { sessionStatePtr_t result = sessionState::matchSessionByParticipant(participant_id); - + return result; } LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::addSession(const std::string &uri, const std::string &handle) { sessionStatePtr_t result; - + if(handle.empty()) { // No handle supplied. @@ -6178,7 +5993,7 @@ LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::addSession(const std:: { // Check for an existing session with this handle sessionMap::iterator iter = mSessionsByHandle.find(handle); - + if(iter != mSessionsByHandle.end()) { result = iter->second; @@ -6188,7 +6003,7 @@ LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::addSession(const std:: if(!result) { // No existing session found. - + LL_DEBUGS("Voice") << "adding new session: handle \"" << handle << "\" URI " << uri << LL_ENDL; result = sessionState::createSession(); result->mSIPURI = uri; @@ -6201,8 +6016,8 @@ LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::addSession(const std:: if(!result->mHandle.empty()) { - // *TODO: Rider: This concerns me. There is a path (via switchChannel) where - // we do not track the session. In theory this means that we could end up with + // *TODO: Rider: This concerns me. There is a path (via switchChannel) where + // we do not track the session. In theory this means that we could end up with // a mAuidoSession that does not match the session tracked in mSessionsByHandle mSessionsByHandle.insert(sessionMap::value_type(result->mHandle, result)); } @@ -6210,7 +6025,7 @@ LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::addSession(const std:: else { // Found an existing session - + if(uri != result->mSIPURI) { // TODO: Should this be an internal error? @@ -6232,12 +6047,12 @@ LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::addSession(const std:: setSessionHandle(result, handle); } } - + LL_DEBUGS("Voice") << "returning existing session: handle " << handle << " URI " << uri << LL_ENDL; } verifySessionState(); - + return result; } @@ -6268,7 +6083,7 @@ void LLVivoxVoiceClient::clearSessionHandle(const sessionStatePtr_t &session) void LLVivoxVoiceClient::setSessionHandle(const sessionStatePtr_t &session, const std::string &handle) { // Have to remove the session from the handle-indexed map before changing the handle, or things will break badly. - + if(!session->mHandle.empty()) { // Remove session from the map if it should have been there. @@ -6287,7 +6102,7 @@ void LLVivoxVoiceClient::setSessionHandle(const sessionStatePtr_t &session, cons LL_WARNS("Voice") << "Attempt to remove session with handle " << session->mHandle << " not found in map!" << LL_ENDL; } } - + session->mHandle = handle; if(!handle.empty()) @@ -6349,7 +6164,7 @@ void LLVivoxVoiceClient::deleteAllSessions() const sessionStatePtr_t session = mSessionsByHandle.begin()->second; deleteSession(session); } - + } void LLVivoxVoiceClient::verifySessionState(void) @@ -6437,19 +6252,25 @@ void LLVivoxVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::ESta } } } - - LL_DEBUGS("Voice") - << " " << LLVoiceClientStatusObserver::status2string(status) - << ", session URI " << getAudioSessionURI() + + LL_DEBUGS("Voice") + << " " << LLVoiceClientStatusObserver::status2string(status) + << ", 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); } @@ -6461,6 +6282,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) @@ -6530,23 +6352,19 @@ void LLVivoxVoiceClient::predAvatarNameResolution(const LLVivoxVoiceClient::sess { session->mTextInvitePending = false; - // We don't need to call LLIMMgr::getInstance()->addP2PSession() here. The first incoming message will create the panel. + // We don't need to call LLIMMgr::getInstance()->addP2PSession() here. The first incoming message will create the panel. } if (session->mVoiceInvitePending) { session->mVoiceInvitePending = false; - LLIMMgr::getInstance()->inviteToSession( session->mIMSessionID, session->mName, session->mCallerID, session->mName, IM_SESSION_P2P_INVITE, - LLIMMgr::INVITATION_TYPE_VOICE, - session->mHandle, - session->mSIPURI); + LLIMMgr::INVITATION_TYPE_VOICE); } - } } @@ -7087,7 +6905,7 @@ void LLVivoxVoiceClient::onClickVoiceEffect(const std::string& voice_effect_name } } -// it updates VoiceMorphing menu items in accordance with purchased properties +// it updates VoiceMorphing menu items in accordance with purchased properties void LLVivoxVoiceClient::updateVoiceMorphingMenu() { if (mVoiceFontListDirty) @@ -7328,7 +7146,7 @@ void LLVivoxVoiceClient::captureBufferPlayStopSendMessage() LLVivoxProtocolParser::LLVivoxProtocolParser() { parser = XML_ParserCreate(NULL); - + reset(); } @@ -7361,7 +7179,7 @@ void LLVivoxProtocolParser::reset() applicationString.clear(); } -//virtual +//virtual LLVivoxProtocolParser::~LLVivoxProtocolParser() { if (parser) @@ -7387,38 +7205,38 @@ LLIOPipe::EStatus LLVivoxProtocolParser::process_impl( istr.read(buf, sizeof(buf)); mInput.append(buf, istr.gcount()); } - + // Look for input delimiter(s) in the input buffer. If one is found, send the message to the xml parser. int start = 0; int delim; while((delim = mInput.find("\n\n\n", start)) != std::string::npos) - { - + { + // Reset internal state of the LLVivoxProtocolParser (no effect on the expat parser) reset(); - + XML_ParserReset(parser, NULL); XML_SetElementHandler(parser, ExpatStartTag, ExpatEndTag); XML_SetCharacterDataHandler(parser, ExpatCharHandler); - XML_SetUserData(parser, this); + XML_SetUserData(parser, this); XML_Parse(parser, mInput.data() + start, delim - start, false); - + LL_DEBUGS("VivoxProtocolParser") << "parsing: " << mInput.substr(start, delim - start) << LL_ENDL; start = delim + 3; } - + if(start != 0) mInput = mInput.substr(start); - + LL_DEBUGS("VivoxProtocolParser") << "at end, mInput is: " << mInput << LL_ENDL; - + if(!LLVivoxVoiceClient::sConnected) { // If voice has been disabled, we just want to close the socket. This does so. LL_INFOS("Voice") << "returning STATUS_STOP" << LL_ENDL; return STATUS_STOP; } - + return STATUS_OK; } @@ -7462,11 +7280,11 @@ void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr) textBuffer.clear(); // only accumulate text if we're not ignoring tags. accumulateText = !ignoringTags; - + if (responseDepth == 0) - { + { isEvent = !stricmp("Event", tag); - + if (!stricmp("Response", tag) || isEvent) { // Grab the attributes @@ -7474,7 +7292,7 @@ void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr) { const char *key = *attr++; const char *value = *attr++; - + if (!stricmp("requestId", key)) { requestId = value; @@ -7500,20 +7318,20 @@ void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr) else { LL_DEBUGS("VivoxProtocolParser") << tag << " (" << responseDepth << ")" << LL_ENDL; - + // Ignore the InputXml stuff so we don't get confused if (!stricmp("InputXml", tag)) { ignoringTags = true; ignoreDepth = responseDepth; accumulateText = false; - + LL_DEBUGS("VivoxProtocolParser") << "starting ignore, ignoreDepth is " << ignoreDepth << LL_ENDL; } else if (!stricmp("CaptureDevices", tag)) { LLVivoxVoiceClient::getInstance()->clearCaptureDevices(); - } + } else if (!stricmp("RenderDevices", tag)) { LLVivoxVoiceClient::getInstance()->clearRenderDevices(); @@ -7525,7 +7343,7 @@ void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr) else if (!stricmp("RenderDevice", tag)) { deviceString.clear(); - } + } else if (!stricmp("SessionFont", tag)) { id = 0; @@ -7560,9 +7378,9 @@ void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr) void LLVivoxProtocolParser::EndTag(const char *tag) { const std::string& string = textBuffer; - + responseDepth--; - + if (ignoringTags) { if (ignoreDepth == responseDepth) @@ -7575,11 +7393,11 @@ void LLVivoxProtocolParser::EndTag(const char *tag) LL_DEBUGS("VivoxProtocolParser") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; } } - + if (!ignoringTags) { LL_DEBUGS("VivoxProtocolParser") << "processing tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; - + // Closing a tag. Finalize the text we've accumulated and reset if (!stricmp("ReturnCode", tag)) returnCode = strtol(string.c_str(), NULL, 10); @@ -7634,7 +7452,7 @@ void LLVivoxProtocolParser::EndTag(const char *tag) else if (!stricmp("DisplayName", tag)) displayNameString = string; else if (!stricmp("Device", tag)) - deviceString = string; + deviceString = string; else if (!stricmp("AccountName", tag)) nameString = string; else if (!stricmp("ParticipantType", tag)) @@ -7732,7 +7550,7 @@ void LLVivoxProtocolParser::EndTag(const char *tag) textBuffer.clear(); accumulateText= false; - + if (responseDepth == 0) { // We finished all of the XML, process the data @@ -7749,7 +7567,7 @@ void LLVivoxProtocolParser::CharData(const char *buffer, int length) This method is called for anything that isn't a tag, which can be text you want that lies between tags, and a lot of stuff you don't want like file formatting (tabs, spaces, CR/LF, etc). - + Only copy text if we are in accumulate mode... */ if (accumulateText) @@ -7776,14 +7594,14 @@ LLDate LLVivoxProtocolParser::expiryTimeStampToLLDate(const std::string& vivox_t void LLVivoxProtocolParser::processResponse(std::string tag) { LL_DEBUGS("VivoxProtocolParser") << tag << LL_ENDL; - + // SLIM SDK: the SDK now returns a statusCode of "200" (OK) for success. This is a change vs. previous SDKs. // According to Mike S., "The actual API convention is that responses with return codes of 0 are successful, regardless of the status code returned", // so I believe this will give correct behavior. - + if(returnCode == 0) statusCode = 0; - + if (isEvent) { const char *eventTypeCstr = eventTypeString.c_str(); @@ -7851,7 +7669,7 @@ void LLVivoxProtocolParser::processResponse(std::string tag) } else if (!stricmp(eventTypeCstr, "ParticipantAddedEvent")) { - /* + /* <Event type="ParticipantAddedEvent"> <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg4</SessionGroupHandle> <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==4</SessionHandle> @@ -7883,12 +7701,12 @@ void LLVivoxProtocolParser::processResponse(std::string tag) // These are really spammy in tuning mode LLVivoxVoiceClient::getInstance()->auxAudioPropertiesEvent(energy); } - else if (!stricmp(eventTypeCstr, "MessageEvent")) + else if (!stricmp(eventTypeCstr, "MessageEvent")) { //TODO: This probably is not received any more, it was used to support SLim clients LLVivoxVoiceClient::getInstance()->messageEvent(sessionHandle, uriString, alias, messageHeader, messageBody, applicationString); } - else if (!stricmp(eventTypeCstr, "SessionNotificationEvent")) + else if (!stricmp(eventTypeCstr, "SessionNotificationEvent")) { //TODO: This probably is not received any more, it was used to support SLim clients LLVivoxVoiceClient::getInstance()->sessionNotificationEvent(sessionHandle, uriString, notificationType); @@ -7958,23 +7776,23 @@ void LLVivoxProtocolParser::processResponse(std::string tag) } else if (!stricmp(actionCstr, "Session.Create.1")) { - LLVivoxVoiceClient::getInstance()->sessionCreateResponse(requestId, statusCode, statusString, sessionHandle); + LLVivoxVoiceClient::getInstance()->sessionCreateResponse(requestId, statusCode, statusString, sessionHandle); } else if (!stricmp(actionCstr, "SessionGroup.AddSession.1")) { - LLVivoxVoiceClient::getInstance()->sessionGroupAddSessionResponse(requestId, statusCode, statusString, sessionHandle); + LLVivoxVoiceClient::getInstance()->sessionGroupAddSessionResponse(requestId, statusCode, statusString, sessionHandle); } else if (!stricmp(actionCstr, "Session.Connect.1")) { - LLVivoxVoiceClient::getInstance()->sessionConnectResponse(requestId, statusCode, statusString); + LLVivoxVoiceClient::getInstance()->sessionConnectResponse(requestId, statusCode, statusString); } else if (!stricmp(actionCstr, "Account.Logout.1")) { - LLVivoxVoiceClient::getInstance()->logoutResponse(statusCode, statusString); + LLVivoxVoiceClient::getInstance()->logoutResponse(statusCode, statusString); } else if (!stricmp(actionCstr, "Connector.InitiateShutdown.1")) { - LLVivoxVoiceClient::getInstance()->connectorShutdownResponse(statusCode, statusString); + LLVivoxVoiceClient::getInstance()->connectorShutdownResponse(statusCode, statusString); } else if (!stricmp(actionCstr, "Account.GetSessionFonts.1")) { @@ -8003,75 +7821,75 @@ void LLVivoxProtocolParser::processResponse(std::string tag) } else if (!stricmp(actionCstr, "Connector.AccountCreate.1")) { - + } else if (!stricmp(actionCstr, "Connector.MuteLocalMic.1")) { - + } else if (!stricmp(actionCstr, "Connector.MuteLocalSpeaker.1")) { - + } else if (!stricmp(actionCstr, "Connector.SetLocalMicVolume.1")) { - + } else if (!stricmp(actionCstr, "Connector.SetLocalSpeakerVolume.1")) { - + } else if (!stricmp(actionCstr, "Session.ListenerSetPosition.1")) { - + } else if (!stricmp(actionCstr, "Session.SpeakerSetPosition.1")) { - + } else if (!stricmp(actionCstr, "Session.AudioSourceSetPosition.1")) { - + } else if (!stricmp(actionCstr, "Session.GetChannelParticipants.1")) { - + } else if (!stricmp(actionCstr, "Account.ChannelCreate.1")) { - + } else if (!stricmp(actionCstr, "Account.ChannelUpdate.1")) { - + } else if (!stricmp(actionCstr, "Account.ChannelDelete.1")) { - + } else if (!stricmp(actionCstr, "Account.ChannelCreateAndInvite.1")) { - + } else if (!stricmp(actionCstr, "Account.ChannelFolderCreate.1")) { - + } else if (!stricmp(actionCstr, "Account.ChannelFolderUpdate.1")) { - + } else if (!stricmp(actionCstr, "Account.ChannelFolderDelete.1")) { - + } else if (!stricmp(actionCstr, "Account.ChannelAddModerator.1")) { - + } else if (!stricmp(actionCstr, "Account.ChannelDeleteModerator.1")) { - + } */ } @@ -8089,7 +7907,7 @@ LLVivoxSecurity::LLVivoxSecurity() random_value[b] = ll_rand() & 0xff; } mConnectorHandle = LLBase64::encode(random_value, VIVOX_TOKEN_BYTES); - + for (int b = 0; b < VIVOX_TOKEN_BYTES; b++) { random_value[b] = ll_rand() & 0xff; @@ -8100,3 +7918,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 7e1835d876..899fd149c8 100644 --- a/indra/newview/llvoicevivox.h +++ b/indra/newview/llvoicevivox.h @@ -1,25 +1,25 @@ -/** +/** * @file llvoicevivox.h * @brief Declaration of LLDiamondwareVoiceClient class which is the interface to the voice client process. * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, 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$ */ @@ -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,149 +79,149 @@ 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 - - virtual const LLVoiceVersionInfo& getVersion() override; - - virtual void updateSettings() override; // call after loading settings and whenever they change + void init(LLPumpIO *pump) override; // Call this once at application startup (creates connector) + void terminate() override; // Call this to clean up during shutdown + + const LLVoiceVersionInfo& getVersion() override; + + 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; + // Returns true if vivox has successfully logged in and is not in error state + bool isVoiceWorking() const override; ///////////////////// /// @name Tuning //@{ - virtual void tuningStart() override; - virtual void tuningStop() override; - virtual bool inTuningMode() override; - - virtual void tuningSetMicVolume(float volume) override; - virtual void tuningSetSpeakerVolume(float volume) override; - virtual float tuningGetEnergy(void) override; + 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. // 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; - - virtual void setCaptureDevice(const std::string& name) override; - virtual void setRenderDevice(const std::string& name) override; - - virtual LLVoiceDeviceList& getCaptureDevices() override; - virtual LLVoiceDeviceList& getRenderDevices() override; - //@} - - virtual void getParticipantList(std::set<LLUUID> &participants) override; - virtual bool isParticipant(const LLUUID& speaker_id) override; + 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;}; - - // 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; - + // 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; + // 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; - - + // NOTE: this will return true if the session can't be found. + 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; - - virtual void setNonSpatialChannel(const std::string &uri, - const std::string &credentials) override; - - virtual bool setSpatialChannel(const std::string &uri, - const std::string &credentials) override; - - virtual void leaveNonSpatialChannel() override; - - virtual void leaveChannel(void) 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; + bool inProximalChannel() override; + + void setNonSpatialChannel(const LLSD& channelInfo, + bool notify_on_first_join, + bool hangup_on_last_leave) override; + + bool setSpatialChannel(const LLSD& channelInfo) override; + + void leaveNonSpatialChannel() override; + + void processChannels(bool process) override; + + void leaveChannel(void); + + bool isCurrentChannel(const LLSD &channelInfo) override; + bool compareChannels(const LLSD &channelInfo1, const LLSD &channelInfo2) 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; //@} //@} @@ -251,12 +266,12 @@ public: protected: ////////////////////// - // Vivox Specific definitions - + // Vivox Specific definitions + friend class LLVivoxVoiceClientMuteListObserver; - friend class LLVivoxVoiceClientFriendsObserver; + friend class LLVivoxVoiceClientFriendsObserver; + - enum streamState { streamStateUnknown = 0, @@ -265,16 +280,16 @@ protected: streamStateRinging = 3, streamStateConnecting = 6, // same as Vivox session_media_connecting enum streamStateDisconnecting = 7, //Same as Vivox session_media_disconnecting enum - }; + }; struct participantState { public: participantState(const std::string &uri); - + bool updateMuteState(); // true if mute state has changed bool isAvatar(); - + std::string mURI; LLUUID mAvatarID; std::string mAccountName; @@ -299,7 +314,7 @@ protected: typedef std::map<const std::string, participantStatePtr_t> participantMap; typedef std::map<const LLUUID, participantStatePtr_t> participantUUIDMap; - + struct sessionState { public: @@ -310,7 +325,9 @@ protected: static ptr_t createSession(); ~sessionState(); - + + LLSD getVoiceChannelInfo(); + participantStatePtr_t addParticipant(const std::string &uri); void removeParticipant(const participantStatePtr_t &participant); void removeAllParticipants(); @@ -325,7 +342,8 @@ protected: bool isCallBackPossible(); bool isTextIMPossible(); - + bool isSpatial() { return mIsSpatial; } + static void for_each(sessionFunc_t func); std::string mHandle; @@ -337,7 +355,7 @@ protected: std::string mHash; // Channel password std::string mErrorStatusString; std::queue<std::string> mTextMsgQueue; - + LLUUID mIMSessionID; LLUUID mCallerID; int mErrorStatusCode; @@ -384,7 +402,7 @@ protected: typedef std::shared_ptr<sessionState> sessionStatePtr_t; typedef std::map<std::string, sessionStatePtr_t> sessionMap; - + /////////////////////////////////////////////////////// // Private Member Functions ////////////////////////////////////////////////////// @@ -396,17 +414,17 @@ protected: //@{ // Call this if the connection to the daemon terminates unexpectedly. It will attempt to reset everything and relaunch. void daemonDied(); - + // Call this if we're just giving up on voice (can't provision an account, etc.). It will clean up and go away. - void giveUp(); - + void giveUp(); + // write to the tvc bool writeString(const std::string &str); - + void connectorCreate(); - void connectorShutdown(); - void closeSocket(void); - + void connectorShutdown(); + void closeSocket(void); + // void requestVoiceAccountProvision(S32 retries = 3); void setLoginInfo( const std::string& account_name, @@ -415,14 +433,14 @@ protected: const std::string& voice_account_server_uri); void loginSendMessage(); void logout(); - void logoutSendMessage(); - - + void logoutSendMessage(); + + //@} - + //------------------------------------ // tuning - + void tuningRenderStartSendMessage(const std::string& name, bool loop); void tuningRenderStopSendMessage(); @@ -435,12 +453,12 @@ protected: void addCaptureDevice(const LLVoiceDevice& device); void clearRenderDevices(); void setDevicesListUpdated(bool state); - void addRenderDevice(const LLVoiceDevice& device); + void addRenderDevice(const LLVoiceDevice& device); void buildSetAudioDevices(std::ostringstream &stream); - + void getCaptureDevicesSendMessage(); void getRenderDevicesSendMessage(); - + // local audio updates, mic mute, speaker mute, mic volume and speaker volumes void sendLocalAudioUpdates(); @@ -467,9 +485,9 @@ protected: void auxAudioPropertiesEvent(F32 energy); void messageEvent(std::string &sessionHandle, std::string &uriString, std::string &alias, std::string &messageHeader, std::string &messageBody, std::string &applicationString); void sessionNotificationEvent(std::string &sessionHandle, std::string &uriString, std::string ¬ificationType); - + void muteListChanged(); - + ///////////////////////////// // VAD changes // disable auto-VAD and configure VAD parameters explicitly @@ -485,7 +503,7 @@ protected: void setEarLocation(S32 loc); - + ///////////////////////////// // Accessors for data related to nearby speakers @@ -494,30 +512,25 @@ 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); void recordingStop(); - + // Playback controls void filePlaybackStart(const std::string& filename); void filePlaybackStop(); void filePlaybackSetPaused(bool paused); void filePlaybackSetMode(bool vox = false, float speed = 1.0f); - + participantStatePtr_t findParticipantByID(const LLUUID& id); - + #if 0 //////////////////////////////////////// // voice sessions. typedef std::set<sessionStatePtr_t> sessionSet; - + typedef sessionSet::iterator sessionIterator; sessionIterator sessionsBegin(void); sessionIterator sessionsEnd(void); @@ -526,7 +539,7 @@ protected: sessionStatePtr_t findSession(const std::string &handle); sessionStatePtr_t findSessionBeingCreatedByURI(const std::string &uri); sessionStatePtr_t findSession(const LLUUID &participant_id); - + sessionStatePtr_t addSession(const std::string &uri, const std::string &handle = std::string()); void clearSessionHandle(const sessionStatePtr_t &session); void setSessionHandle(const sessionStatePtr_t &session, const std::string &handle); @@ -542,11 +555,11 @@ protected: // This is called in several places where the session _may_ need to be deleted. // It contains logic for whether to delete the session or keep it around. void reapSession(const sessionStatePtr_t &session); - + // Returns true if the session seems to indicate we've moved to a region on a different voice server bool sessionNeedsRelog(const sessionStatePtr_t &session); - - + + ////////////////////////////////////// // buddy list stuff, needed for SLIM later struct buddyListEntry @@ -566,13 +579,13 @@ protected: }; typedef std::map<std::string, buddyListEntry*> buddyListMap; - + ///////////////////////////// // session control messages void accountListBlockRulesSendMessage(); void accountListAutoAcceptRulesSendMessage(); - + void sessionGroupCreateSendMessage(); void sessionCreateSendMessage(const sessionStatePtr_t &session, bool startAudio = true, bool startText = false); void sessionGroupAddSessionSendMessage(const sessionStatePtr_t &session, bool startAudio = true, bool startText = false); @@ -583,20 +596,20 @@ protected: void sessionMediaDisconnectSendMessage(const sessionStatePtr_t &session); // void sessionTextDisconnectSendMessage(sessionState *session); - - + + // Pokes the state machine to leave the audio session next time around. - void sessionTerminate(); - + void sessionTerminate(); + // Pokes the state machine to shut down the connector and restart it. void requestRelog(); - + // Does the actual work to get out of the audio session void leaveAudioSession(); - + friend class LLVivoxVoiceClientCapResponder; - - + + void lookupName(const LLUUID &id); void onAvatarNameCache(const LLUUID& id, const LLAvatarName& av_name); void avatarNameResolved(const LLUUID &id, const std::string &name); @@ -616,10 +629,10 @@ protected: const S32 font_status, const bool template_font = false); void accountGetSessionFontsResponse(int statusCode, const std::string &statusString); - void accountGetTemplateFontsResponse(int statusCode, const std::string &statusString); + void accountGetTemplateFontsResponse(int statusCode, const std::string &statusString); private: - + LLVoiceVersionInfo mVoiceVersion; // Coroutine support methods @@ -661,21 +674,21 @@ private: // The larger it is the greater is possibility there is a problem with connection to voice server. // Introduced while fixing EXT-4313. int mSpatialJoiningNum; - + static void idle(void *user_data); - + LLHost mDaemonHost; LLSocket::ptr_t mSocket; - - // We should kill the voice daemon in case of connection alert + + // We should kill the voice daemon in case of connection alert bool mTerminateDaemon; - + friend class LLVivoxProtocolParser; - + std::string mAccountName; std::string mAccountPassword; std::string mAccountDisplayName; - + bool mTuningMode; float mTuningEnergy; std::string mTuningAudioFile; @@ -685,14 +698,13 @@ private: bool mTuningSpeakerVolumeDirty; 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 mSpatialSessionURI; std::string mSpatialSessionCredentials; std::string mMainSessionGroupHandle; // handle of the "main" session group. - - std::string mChannelName; // Name of the channel to be looked up - bool mAreaVoiceDisabled; + + std::string mChannelName; // Name of the channel to be looked up 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. @@ -700,27 +712,27 @@ private: S32 mCurrentParcelLocalID; // Used to detect parcel boundary crossings std::string mCurrentRegionName; // Used to detect parcel boundary crossings - + bool mConnectorEstablished; // set by "Create Connector" response - bool mAccountLoggedIn; // set by login message + bool mAccountLoggedIn; // set by login message int mNumberOfAliases; U32 mCommandCookie; std::string mVoiceAccountServerURI; std::string mVoiceSIPURIHostName; - + int mLoginRetryCount; - + sessionMap mSessionsByHandle; // Active sessions, indexed by session handle. Sessions which are being initiated may not be in this map. #if 0 sessionSet mSessions; // All sessions, not indexed. This is the canonical session list. #endif - + bool mBuddyListMapPopulated; bool mBlockRulesListReceived; bool mAutoAcceptRulesListReceived; buddyListMap mBuddyListMap; - + LLVoiceDeviceList mCaptureDevices; LLVoiceDeviceList mRenderDevices; @@ -731,32 +743,30 @@ private: bool mIsInitialized; bool mShutdownComplete; - + bool checkParcelChanged(bool update = false); 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); - + // Returns the name portion of the SIP URI if the string looks vaguely like a SIP URI, or an empty string if not. - std::string nameFromsipURI(const std::string &uri); + std::string nameFromsipURI(const std::string &uri); bool inSpatialChannel(void); - std::string getAudioSessionURI(); + LLSD getAudioSessionChannelInfo(); std::string getAudioSessionHandle(); - + void setHidden(bool hidden) override; //virtual void sendPositionAndVolumeUpdate(void); - + void sendCaptureAndRenderDevices(); void buildSetCaptureDevice(std::ostringstream &stream); void buildSetRenderDevice(std::ostringstream &stream); - + void sendFriendsListUpdates(); @@ -767,9 +777,9 @@ private: #endif void enforceTether(void); - + bool mSpatialCoordsDirty; - + LLVector3d mCameraPosition; LLVector3d mCameraRequestedPosition; LLVector3 mCameraVelocity; @@ -778,36 +788,35 @@ private: LLVector3d mAvatarPosition; LLVector3 mAvatarVelocity; LLQuaternion mAvatarRot; - + bool mMuteMic; bool mMuteMicDirty; bool mHidden; //Set to true during teleport to hide the agent's position. - + // Set to true when the friends list is known to have changed. bool mFriendsListDirty; - + enum { earLocCamera = 0, // ear at camera earLocAvatar, // ear at avatar earLocMixed // ear at avatar location/camera direction }; - - S32 mEarLocation; - + + S32 mEarLocation; + bool mSpeakerVolumeDirty; bool mSpeakerMuteDirty; int mSpeakerVolume; int mMicVolume; 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; @@ -816,7 +825,7 @@ private: typedef std::set<LLVoiceClientStatusObserver*> status_observer_set_t; status_observer_set_t mStatusObservers; - + void notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status); typedef std::set<LLFriendObserver*> friend_observer_set_t; @@ -923,7 +932,7 @@ private: }; -/** +/** * @class LLVivoxProtocolParser * @brief This class helps construct new LLIOPipe specializations * @see LLIOPipe @@ -936,12 +945,12 @@ class LLVivoxProtocolParser : public LLIOPipe public: LLVivoxProtocolParser(); virtual ~LLVivoxProtocolParser(); - + protected: /* @name LLIOPipe virtual implementations */ //@{ - /** + /** * @brief Process the data in buffer */ virtual EStatus process_impl( @@ -951,16 +960,16 @@ protected: LLSD& context, LLPumpIO* pump); //@} - + std::string mInput; - + // Expat control members XML_Parser parser; int responseDepth; bool ignoringTags; bool isEvent; int ignoreDepth; - + // Members for processing responses. The values are transient and only valid within a call to processResponse(). int returnCode; int statusCode; @@ -975,7 +984,7 @@ protected: std::string sessionGroupHandle; std::string alias; std::string applicationString; - + // Members for processing events. The values are transient and only valid within a call to processResponse(). std::string eventTypeString; int state; @@ -1014,19 +1023,19 @@ protected: S32 fontType; S32 fontStatus; std::string mediaCompletionType; - + // Members for processing text between tags std::string textBuffer; bool accumulateText; - + void reset(); - + void processResponse(std::string tag); - + static void XMLCALL ExpatStartTag(void *data, const char *el, const char **attr); static void XMLCALL ExpatEndTag(void *data, const char *el); static void XMLCALL ExpatCharHandler(void *data, const XML_Char *s, int len); - + void StartTag(const char *tag, const char **attr); void EndTag(const char *tag); void CharData(const char *buffer, int length); @@ -1053,7 +1062,7 @@ class LLVoiceVivoxStats : public LLSingleton<LLVoiceVivoxStats> LLSINGLETON(LLVoiceVivoxStats); LOG_CLASS(LLVoiceVivoxStats); virtual ~LLVoiceVivoxStats(); - + private: F64SecondsImplicit mStartTime; @@ -1061,7 +1070,7 @@ class LLVoiceVivoxStats : public LLSingleton<LLVoiceVivoxStats> F64 mConnectTime; U32 mConnectAttempts; - + F64 mProvisionTime; U32 mProvisionAttempts; diff --git a/indra/newview/llvoicewebrtc.cpp b/indra/newview/llvoicewebrtc.cpp new file mode 100644 index 0000000000..ddbe2eb552 --- /dev/null +++ b/indra/newview/llvoicewebrtc.cpp @@ -0,0 +1,3100 @@ + /** + * @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> +#include <format> +#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.35; + + 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; + } + + if (mSession) + { + if (!channelInfo["session_handle"].asString().empty()) + { + return mSession->mHandle == channelInfo["session_handle"].asString(); + } + return channelInfo["channel_uri"].asString() == mSession->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; + sessionState::for_each(boost::bind(predSetMicGain, _1, gain)); + } +} + +void LLWebRTCVoiceClient::predSetMicGain(const LLWebRTCVoiceClient::sessionStatePtr_t &session, F32 gain) +{ + session->setMicGain(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), + mMicGain(1.0), + 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::setMicGain(F32 gain) +{ + mMicGain = gain; + for (auto &connection : mWebRTCConnections) + { + connection->setMicGain(gain); + } +} + +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->setMicGain(mMicGain); + 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->setMicGain(mMicGain); + 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), + mMicGain(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; + } + if (mWebRTCPeerConnectionInterface) + { + llwebrtc::freePeerConnection(mWebRTCPeerConnectionInterface); + mWebRTCPeerConnectionInterface = nullptr; + } +} + + +// 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; + }); +} + + +void LLVoiceWebRTCConnection::setMuteMic(bool muted) +{ + mMuted = muted; + if (mWebRTCAudioInterface) + { + mWebRTCAudioInterface->setMute(muted); + } +} + +void LLVoiceWebRTCConnection::setMicGain(F32 gain) +{ + mMicGain = gain; + if (mWebRTCAudioInterface) + { + mWebRTCAudioInterface->setSendVolume(gain); + } +} + +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++; + setVoiceConnectionState(VOICE_STATE_WAIT_FOR_EXIT); + + // 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); + + if (LLWebRTCVoiceClient::isShuttingDown()) + { + mOutstandingRequests--; + return; + } + + if (mWebRTCPeerConnectionInterface) + { + mWebRTCPeerConnectionInterface->shutdownConnection(); + } + setVoiceConnectionState(VOICE_STATE_SESSION_EXIT); + + mOutstandingRequests--; +} + +// 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_RETRY); + } + 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_RETRY); + 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_DISCONNECT); + 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_DISCONNECT); + } + break; + } + + case VOICE_STATE_REQUEST_CONNECTION: + if (mShutDown) + { + setVoiceConnectionState(VOICE_STATE_DISCONNECT); + 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); + mWebRTCAudioInterface->setSendVolume(mMicGain); + 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: + LLCoros::instance().launch("LLVoiceWebRTCConnection::breakVoiceConnectionCoro", + boost::bind(&LLVoiceWebRTCConnection::breakVoiceConnectionCoro, this)); + break; + + case VOICE_STATE_WAIT_FOR_EXIT: + break; + + case VOICE_STATE_SESSION_EXIT: + { + { + 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() +{ + if (LLWebRTCVoiceClient::isShuttingDown()) + { + // peer connection and observers will be cleaned up + // by llwebrtc::terminate() on shutdown. + return; + } + assert(mOutstandingRequests == 0); + mWebRTCPeerConnectionInterface->unsetSignalingObserver(this); +} + +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() +{ + if (LLWebRTCVoiceClient::isShuttingDown()) + { + // peer connection and observers will be cleaned up + // by llwebrtc::terminate() on shutdown. + return; + } + assert(mOutstandingRequests == 0); + mWebRTCPeerConnectionInterface->unsetSignalingObserver(this); +} + +// 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) + { + setVoiceConnectionState(VOICE_STATE_SESSION_RETRY); + } + else + { + OnVoiceConnectionRequestSuccess(result); + } + mOutstandingRequests--; +} diff --git a/indra/newview/llvoicewebrtc.h b/indra/newview/llvoicewebrtc.h new file mode 100644 index 0000000000..c417dfe329 --- /dev/null +++ b/indra/newview/llvoicewebrtc.h @@ -0,0 +1,745 @@ +/** + * @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 setMicGain(F32 volume); + 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 mMicGain; // gain for this session. + 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 predSetMicGain(const LLWebRTCVoiceClient::sessionStatePtr_t &session, F32 volume); + 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 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 setMicGain(F32 volume); + 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_SESSION_STOPPING = 0x780 + } 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 mMicGain; + 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/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/panel_preferences_sound.xml b/indra/newview/skins/default/xui/en/panel_preferences_sound.xml index 7eaaaee536..569f2318ae 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,45 @@ 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" + enabled_control="AudioStreamingMedia" + 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 +544,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/viewer_manifest.py b/indra/newview/viewer_manifest.py index 4de4dc8fc5..c6f5335ac3 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'): @@ -994,6 +1000,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 |