diff --git a/Samples/Common/Sample.Common/Sample.Common.csproj b/Samples/Common/Sample.Common/Sample.Common.csproj
index 09061e22..71d8c31f 100644
--- a/Samples/Common/Sample.Common/Sample.Common.csproj
+++ b/Samples/Common/Sample.Common/Sample.Common.csproj
@@ -10,8 +10,8 @@
-
-
+
+
diff --git a/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/FrontEnd/AVPFrontEnd.csproj b/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/FrontEnd/AVPFrontEnd.csproj
index a8776d59..083eae49 100644
--- a/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/FrontEnd/AVPFrontEnd.csproj
+++ b/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/FrontEnd/AVPFrontEnd.csproj
@@ -55,14 +55,14 @@
False
- ..\..\..\..\packages\Microsoft.Skype.Bots.Media\1.14.1.234-alpha\src\skype_media_lib\Microsoft.Skype.Internal.Media.H264.dll
+ ..\..\..\..\packages\Microsoft.Skype.Bots.Media\1.17.0.39-alpha\src\skype_media_lib\Microsoft.Skype.Internal.Media.H264.dll
-
+
-
+
diff --git a/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/WorkerRole/AVPWorkerRole.csproj b/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/WorkerRole/AVPWorkerRole.csproj
index 1b9f603a..b02e7de1 100644
--- a/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/WorkerRole/AVPWorkerRole.csproj
+++ b/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/WorkerRole/AVPWorkerRole.csproj
@@ -34,7 +34,7 @@
-
+
@@ -48,6 +48,9 @@
+
+ Always
+
Designer
PreserveNewest
diff --git a/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/WorkerRole/VC_redist.x64.exe b/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/WorkerRole/VC_redist.x64.exe
new file mode 100644
index 00000000..7476d75a
Binary files /dev/null and b/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/WorkerRole/VC_redist.x64.exe differ
diff --git a/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/WorkerRole/app.config b/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/WorkerRole/app.config
index 3488b1ab..cdf3a12c 100644
--- a/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/WorkerRole/app.config
+++ b/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/WorkerRole/app.config
@@ -56,7 +56,7 @@
-
+
@@ -64,7 +64,7 @@
-
+
diff --git a/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/WorkerRole/startup.cmd b/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/WorkerRole/startup.cmd
index 6aea8c43..fa65f8b7 100644
--- a/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/WorkerRole/startup.cmd
+++ b/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/WorkerRole/startup.cmd
@@ -1,6 +1,9 @@
REM --- Move to this scripts location ---
pushd "%~dp0"
+REM --- Ensure the VC_redist is installed for the Microsoft.Skype.Bots.Media Library ---
+.\VC_redist.x64.exe /quiet
+
REM --- Print out environment variables for debugging ---
set
diff --git a/Samples/V1.0Samples/LocalMediaSamples/HueBot/HueBot/HueBot.csproj b/Samples/V1.0Samples/LocalMediaSamples/HueBot/HueBot/HueBot.csproj
index de609922..c4b46c58 100644
--- a/Samples/V1.0Samples/LocalMediaSamples/HueBot/HueBot/HueBot.csproj
+++ b/Samples/V1.0Samples/LocalMediaSamples/HueBot/HueBot/HueBot.csproj
@@ -19,8 +19,8 @@
-
-
+
+
@@ -35,7 +35,10 @@
- PreserveNewest
+ Always
+
+
+ Always
diff --git a/Samples/V1.0Samples/LocalMediaSamples/HueBot/HueBot/Startup.cmd b/Samples/V1.0Samples/LocalMediaSamples/HueBot/HueBot/Startup.cmd
index f698e077..95c2312b 100644
--- a/Samples/V1.0Samples/LocalMediaSamples/HueBot/HueBot/Startup.cmd
+++ b/Samples/V1.0Samples/LocalMediaSamples/HueBot/HueBot/Startup.cmd
@@ -8,6 +8,12 @@ pushd "%~dp0"
REM --- Print out environment variables for debugging ---
REM set Fabric
+REM --- Ensure the VC_redist is installed for the Microsoft.Skype.Bots.Media Library ---
+@echo off
+set logfile=.\InstallCppRuntime-HueBot.log
+.\VC_redist.x64.exe /quiet
+echo %date% %time% ErrorLevel=%errorlevel% >> %logfile%
+
REM --- Register media perf dlls ---
powershell .\MediaPlatformStartupScript.bat
diff --git a/Samples/V1.0Samples/LocalMediaSamples/HueBot/HueBot/VC_redist.x64.exe b/Samples/V1.0Samples/LocalMediaSamples/HueBot/HueBot/VC_redist.x64.exe
new file mode 100644
index 00000000..7476d75a
Binary files /dev/null and b/Samples/V1.0Samples/LocalMediaSamples/HueBot/HueBot/VC_redist.x64.exe differ
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot.sln b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot.sln
similarity index 96%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot.sln
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot.sln
index c5573f99..69d88b88 100644
--- a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot.sln
+++ b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot.sln
@@ -10,7 +10,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
..\..\nuget.config = ..\..\nuget.config
EndProjectSection
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Common.Beta", "..\..\Common\Sample.Common.Beta\Sample.Common.Beta.csproj", "{3268E59C-90DC-4D7B-97EA-A1DBB2716DF3}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Common", "..\..\Common\Sample.Common\Sample.Common.csproj", "{3268E59C-90DC-4D7B-97EA-A1DBB2716DF3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CRFrontEnd", "PolicyRecordingBot\FrontEnd\CRFrontEnd.csproj", "{739D09C4-47E8-42B7-9B89-94DCB890AC0F}"
EndProject
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/Bot.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/Bot.cs
similarity index 100%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/Bot.cs
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/Bot.cs
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/BotMediaStream.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/BotMediaStream.cs
similarity index 100%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/BotMediaStream.cs
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/BotMediaStream.cs
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/CallHandler.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/CallHandler.cs
similarity index 100%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/CallHandler.cs
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/CallHandler.cs
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/ExceptionExtensions.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/ExceptionExtensions.cs
similarity index 100%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/ExceptionExtensions.cs
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/ExceptionExtensions.cs
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/CRFrontEnd.csproj b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/CRFrontEnd.csproj
similarity index 92%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/CRFrontEnd.csproj
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/CRFrontEnd.csproj
index 0df9e7a4..3bc4e2dd 100644
--- a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/CRFrontEnd.csproj
+++ b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/CRFrontEnd.csproj
@@ -53,21 +53,21 @@
False
- ..\..\..\..\packages\Microsoft.Skype.Bots.Media\1.14.1.234-alpha\src\skype_media_lib\Microsoft.Skype.Internal.Media.H264.dll
+ ..\..\..\..\packages\Microsoft.Skype.Bots.Media\1.17.0.39-alpha\src\skype_media_lib\Microsoft.Skype.Internal.Media.H264.dll
-
+
-
+
-
+
{88b67fbe-6de8-4dc3-83cf-9f6b4ea10138}
- Sample.Common.Beta
+ Sample.Common
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/Controllers/DemoController.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/Controllers/DemoController.cs
similarity index 100%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/Controllers/DemoController.cs
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/Controllers/DemoController.cs
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/Controllers/HttpRouteConstants.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/Controllers/HttpRouteConstants.cs
similarity index 100%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/Controllers/HttpRouteConstants.cs
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/Controllers/HttpRouteConstants.cs
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/Controllers/PlatformCallController.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/Controllers/PlatformCallController.cs
similarity index 100%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/Controllers/PlatformCallController.cs
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/Controllers/PlatformCallController.cs
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/ExceptionLogger.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/ExceptionLogger.cs
similarity index 100%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/ExceptionLogger.cs
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/ExceptionLogger.cs
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/HttpConfigurationInitializer.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/HttpConfigurationInitializer.cs
similarity index 100%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/HttpConfigurationInitializer.cs
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/HttpConfigurationInitializer.cs
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/LoggingMessageHandler.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/LoggingMessageHandler.cs
similarity index 100%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/LoggingMessageHandler.cs
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/LoggingMessageHandler.cs
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/IConfiguration.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/IConfiguration.cs
similarity index 100%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/IConfiguration.cs
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/IConfiguration.cs
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/LRUCache.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/LRUCache.cs
similarity index 100%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/LRUCache.cs
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/LRUCache.cs
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Properties/AssemblyInfo.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Properties/AssemblyInfo.cs
similarity index 100%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Properties/AssemblyInfo.cs
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Properties/AssemblyInfo.cs
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/SampleConstants.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/SampleConstants.cs
similarity index 100%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/SampleConstants.cs
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/SampleConstants.cs
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Service.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Service.cs
similarity index 100%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Service.cs
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Service.cs
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Utilities.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Utilities.cs
similarity index 100%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Utilities.cs
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Utilities.cs
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/Images/TestMeeting1.png b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/Images/TestMeeting1.png
similarity index 100%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/Images/TestMeeting1.png
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/Images/TestMeeting1.png
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/Images/TestMeeting2.png b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/Images/TestMeeting2.png
similarity index 100%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/Images/TestMeeting2.png
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/Images/TestMeeting2.png
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/PolicyRecordingBot.ccproj b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/PolicyRecordingBot.ccproj
similarity index 100%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/PolicyRecordingBot.ccproj
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/PolicyRecordingBot.ccproj
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/README.md b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/README.md
similarity index 100%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/README.md
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/README.md
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/ServiceConfiguration.Cloud.cscfg b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/ServiceConfiguration.Cloud.cscfg
similarity index 100%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/ServiceConfiguration.Cloud.cscfg
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/ServiceConfiguration.Cloud.cscfg
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/ServiceConfiguration.Local.cscfg b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/ServiceConfiguration.Local.cscfg
similarity index 100%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/ServiceConfiguration.Local.cscfg
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/ServiceConfiguration.Local.cscfg
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/ServiceDefinition.csdef b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/ServiceDefinition.csdef
similarity index 100%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/ServiceDefinition.csdef
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/ServiceDefinition.csdef
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/AzureConfiguration.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/AzureConfiguration.cs
similarity index 100%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/AzureConfiguration.cs
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/AzureConfiguration.cs
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/CRWorkerRole.csproj b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/CRWorkerRole.csproj
similarity index 92%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/CRWorkerRole.csproj
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/CRWorkerRole.csproj
index 8e3ad32e..b262bce8 100644
--- a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/CRWorkerRole.csproj
+++ b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/CRWorkerRole.csproj
@@ -35,7 +35,7 @@
-
+
@@ -60,14 +60,19 @@
-
+
{88b67fbe-6de8-4dc3-83cf-9f6b4ea10138}
- Sample.Common.Beta
+ Sample.Common
{739d09c4-47e8-42b7-9b89-94dcb890ac0f}
CRFrontEnd
+
+
+ Always
+
+
\ No newline at end of file
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/ConfigurationException.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/ConfigurationException.cs
similarity index 100%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/ConfigurationException.cs
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/ConfigurationException.cs
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/Properties/AssemblyInfo.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/Properties/AssemblyInfo.cs
similarity index 100%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/Properties/AssemblyInfo.cs
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/Properties/AssemblyInfo.cs
diff --git a/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/VC_redist.x64.exe b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/VC_redist.x64.exe
new file mode 100644
index 00000000..7476d75a
Binary files /dev/null and b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/VC_redist.x64.exe differ
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/WorkerRole.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/WorkerRole.cs
similarity index 100%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/WorkerRole.cs
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/WorkerRole.cs
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/app.config b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/app.config
similarity index 95%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/app.config
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/app.config
index 44a9bae6..4721f1ce 100644
--- a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/app.config
+++ b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/app.config
@@ -56,7 +56,7 @@
-
+
@@ -64,7 +64,7 @@
-
+
@@ -78,6 +78,6 @@
-
+
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/startup.cmd b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/startup.cmd
similarity index 90%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/startup.cmd
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/startup.cmd
index 6aea8c43..d536f07e 100644
--- a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/startup.cmd
+++ b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/startup.cmd
@@ -4,6 +4,9 @@ pushd "%~dp0"
REM --- Print out environment variables for debugging ---
set
+REM --- Ensure the VC_redist is installed for the Microsoft.Skype.Bots.Media Library ---
+.\VC_redist.x64.exe /quiet /norestart
+
REM --- Delete existing certificate bindings and URL ACL registrations ---
netsh http delete sslcert ipport=%InstanceIpAddress%:%PrivateDefaultCallControlPort%
netsh http delete sslcert ipport=%InstanceIpAddress%:%PrivateInstanceCallControlPort%
diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRoleContent/diagnostics.wadcfgx b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRoleContent/diagnostics.wadcfgx
similarity index 100%
rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRoleContent/diagnostics.wadcfgx
rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRoleContent/diagnostics.wadcfgx
diff --git a/Samples/V1.0Samples/RemoteMediaSamples/IncidentBot.sln b/Samples/V1.0Samples/RemoteMediaSamples/IncidentBot.sln
index 5af3ebb2..2cad675c 100644
--- a/Samples/V1.0Samples/RemoteMediaSamples/IncidentBot.sln
+++ b/Samples/V1.0Samples/RemoteMediaSamples/IncidentBot.sln
@@ -12,7 +12,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IncidentBot", "IncidentBot\IncidentBot.csproj", "{FA70E97D-7018-4B08-A9D0-ABA9967AD7DC}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Common.Beta", "..\..\Common\Sample.Common.Beta\Sample.Common.Beta.csproj", "{3268E59C-90DC-4D7B-97EA-A1DBB2716DF3}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Common", "..\..\Common\Sample.Common\Sample.Common.csproj", "{3268E59C-90DC-4D7B-97EA-A1DBB2716DF3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
diff --git a/Samples/V1.0Samples/RemoteMediaSamples/IncidentBot/Bot/Bot.cs b/Samples/V1.0Samples/RemoteMediaSamples/IncidentBot/Bot/Bot.cs
index 800dbd80..7efcaf36 100644
--- a/Samples/V1.0Samples/RemoteMediaSamples/IncidentBot/Bot/Bot.cs
+++ b/Samples/V1.0Samples/RemoteMediaSamples/IncidentBot/Bot/Bot.cs
@@ -480,7 +480,7 @@ private void AddCallToHandlers(ICall call, IncidentCallContext incidentCallConte
var statusData = this.IncidentStatusManager.GetIncident(incidentCallContext.IncidentId);
CallHandler callHandler;
- ParticipantInfo callee;
+ InvitationParticipantInfo callee;
switch (incidentCallContext.CallType)
{
case IncidentCallType.BotMeeting:
diff --git a/Samples/V1.0Samples/RemoteMediaSamples/IncidentBot/IncidentBot.csproj b/Samples/V1.0Samples/RemoteMediaSamples/IncidentBot/IncidentBot.csproj
index f195a11b..5b23ad1c 100644
--- a/Samples/V1.0Samples/RemoteMediaSamples/IncidentBot/IncidentBot.csproj
+++ b/Samples/V1.0Samples/RemoteMediaSamples/IncidentBot/IncidentBot.csproj
@@ -24,7 +24,7 @@
-
+
diff --git a/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot.sln b/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot.sln
new file mode 100644
index 00000000..e3a705dd
--- /dev/null
+++ b/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29911.84
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleIvrBot", "SimpleIvrBot\SimpleIvrBot.csproj", "{89B2D455-60B7-4701-B30C-9F6C21FBE705}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Common", "..\..\Common\Sample.Common\Sample.Common.csproj", "{4FCDBE79-54CC-43F0-A6FC-674B5CD980E1}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {89B2D455-60B7-4701-B30C-9F6C21FBE705}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {89B2D455-60B7-4701-B30C-9F6C21FBE705}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {89B2D455-60B7-4701-B30C-9F6C21FBE705}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {89B2D455-60B7-4701-B30C-9F6C21FBE705}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4FCDBE79-54CC-43F0-A6FC-674B5CD980E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4FCDBE79-54CC-43F0-A6FC-674B5CD980E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4FCDBE79-54CC-43F0-A6FC-674B5CD980E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4FCDBE79-54CC-43F0-A6FC-674B5CD980E1}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {9076CB49-69D5-400A-9C2C-88AD1AC400A9}
+ EndGlobalSection
+EndGlobal
diff --git a/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/Bot/Bot.cs b/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/Bot/Bot.cs
index 8e709036..dc8ae3b0 100644
--- a/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/Bot/Bot.cs
+++ b/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/Bot/Bot.cs
@@ -37,15 +37,7 @@ namespace Sample.SimpleIvrBot.Bot
///
public class Bot
{
- ///
- /// The prompt audio name for transfering the call.
- ///
- ///
- /// message: "Please press 1 for Sales, press 2 for Service, press 3 for other questions".
- ///
- private const string BotIncomingPromptName = "BotIncomingPrompt";
-
- private readonly Uri botBaseUri;
+ private readonly Uri botBaseUri;
///
/// Initializes a new instance of the class.
@@ -84,16 +76,6 @@ public Bot(BotOptions options, IGraphLogger graphLogger)
this.AuthenticationProvider,
productInfo,
defaultProperties);
-
- // setup media prompt
- this.MediaMap[BotIncomingPromptName] = new MediaPrompt
- {
- MediaInfo = new MediaInfo
- {
- Uri = new Uri(options.BotBaseUrl, "audio/speech.wav").ToString(),
- ResourceId = Guid.NewGuid().ToString(),
- },
- };
}
///
@@ -126,19 +108,13 @@ public Bot(BotOptions options, IGraphLogger graphLogger)
///
public IGraphClient GraphApiClient { get; }
- ///
- /// Gets the prompts dictionary.
- ///
- public IDictionary MediaMap { get; } = new Dictionary();
-
///
/// Processes the notification asynchronously.
- /// Here we make sure we log the http request and
- /// catch/log any errors.
+ /// Here we make sure we log the http request and catch/log any errors.
///
/// The request.
/// The response.
- /// Returns after notification is processed.
+ /// The .
public async Task ProcessNotificationAsync(
HttpRequest request,
HttpResponse response)
@@ -305,69 +281,64 @@ private async Task NotificationProcessor_OnNotificationReceivedAsync(Notificatio
{
await this.BotAnswerIncomingCallAsync(call.Id, args.TenantId, args.ScenarioId).ConfigureAwait(false);
}
- else if (args.ChangeType == ChangeType.Updated && call.State == CallState.Established)
+ else if (args.ChangeType == ChangeType.Updated && (call.State == CallState.Established || call.State == CallState.Establishing))
{
- this.GraphLogger.Log(TraceLevel.Info, "In Established");
+ this.GraphLogger.Log(TraceLevel.Info, $"In {call.State}");
- if (call.ToneInfo == null && call.MediaState.Audio == MediaState.Active)
+ if (call.ToneInfo == null && call.MediaState?.Audio == MediaState.Active)
{
await this.BotPlayNotificationPromptAsync(call.Id, args.TenantId, args.ScenarioId).ConfigureAwait(false);
}
else if (call.ToneInfo?.SequenceId == 1)
{
- InvitationParticipantInfo transferTarget = null;
-
+ InvitationParticipantInfo transferTarget = null;
if (call.ToneInfo.Tone == Tone.Tone1)
+ {
+ transferTarget = new InvitationParticipantInfo
{
- transferTarget = new InvitationParticipantInfo
+ Identity = new IdentitySet
{
- Identity = new IdentitySet
+ User = new Identity
{
- User = new Identity
- {
- Id = ConfigurationManager.AppSetting["ObjectIds:First"],
- },
+ Id = ConfigurationManager.AppSetting["ObjectIds:First"],
},
- };
- }
+ },
+ };
+ }
else if (call.ToneInfo.Tone == Tone.Tone2)
- {
- transferTarget = new InvitationParticipantInfo
+ {
+ transferTarget = new InvitationParticipantInfo
+ {
+ Identity = new IdentitySet
{
- Identity = new IdentitySet
+ User = new Identity
{
- User = new Identity
- {
- Id = ConfigurationManager.AppSetting["ObjectIds:Second"],
- },
+ Id = ConfigurationManager.AppSetting["ObjectIds:Second"],
},
- };
- }
+ },
+ };
+ }
else
+ {
+ transferTarget = new InvitationParticipantInfo
{
- transferTarget = new InvitationParticipantInfo
- {
- Identity = new IdentitySet
- {
- User = new Identity
- {
- Id = ConfigurationManager.AppSetting["ObjectIds:Third"],
- },
- },
- };
- }
+ Identity = new IdentitySet
+ {
+ User = new Identity
+ {
+ Id = ConfigurationManager.AppSetting["ObjectIds:Third"],
+ },
+ },
+ };
+ }
await this.BotTransferCallAsync(transferTarget, call.Id, args.TenantId, args.ScenarioId).ConfigureAwait(false);
}
-
- return;
}
else if (args.ChangeType == ChangeType.Deleted && call.State == CallState.Terminated)
{
- this.GraphLogger.Log(TraceLevel.Info, "Call is Terminated now");
+ this.GraphLogger.Log(TraceLevel.Info, $"Call State:{call.State}");
}
-
- return;
}
else if (args.ResourceData is PlayPromptOperation playPromptOperation)
{
@@ -379,7 +350,7 @@ private async Task NotificationProcessor_OnNotificationReceivedAsync(Notificatio
Message = "No call id provided in PlayPromptOperation.ClientContext.",
});
}
- else if (playPromptOperation.CompletionReason == PlayPromptCompletionReason.CompletedSuccessfully && playPromptOperation.Status == OperationStatus.Completed)
+ else if (playPromptOperation.Status == OperationStatus.Completed)
{
await this.BotSubscribesToToneAsync(playPromptOperation.ClientContext, args.TenantId, args.ScenarioId).ConfigureAwait(false);
}
@@ -389,16 +360,13 @@ private async Task NotificationProcessor_OnNotificationReceivedAsync(Notificatio
///
/// Subscribes to Tone.
///
- /// Call Id.
- /// Tenant Id.
- /// ScenarioId.
- ///
- /// user subscribesToTone.
- ///
+ /// The call identifier.
+ /// The tenant identifier.
+ /// The scenario identifier.
+ /// The .
private async Task BotSubscribesToToneAsync(string callId, string tenantId, Guid scenarioId)
{
await this.GraphApiClient.SendAsync(this.RequestBuilder.Communications.Calls[callId].SubscribeToTone(callId).Request(), RequestType.Create, tenantId, scenarioId).ConfigureAwait(false);
- return;
}
///
@@ -407,20 +375,22 @@ private async Task BotSubscribesToToneAsync(string callId, string tenantId, Guid
/// The identifier of the call to transfer.
/// The tenant identifier.
/// The scenario identifier.
- ///
- /// When the call has been answered.
- ///
+ /// The .
private async Task BotAnswerIncomingCallAsync(string callId, string tenantId, Guid scenarioId)
{
- var mediaToPrefetch = new List();
- foreach (var m in this.MediaMap)
- {
- mediaToPrefetch.Add(m.Value.MediaInfo);
- }
-
var answerRequest = this.RequestBuilder.Communications.Calls[callId].Answer(
callbackUri: new Uri(this.botBaseUri, ControllerConstants.CallbackPrefix).ToString(),
- mediaConfig: new ServiceHostedMediaConfig { PreFetchMedia = mediaToPrefetch },
+ mediaConfig: new ServiceHostedMediaConfig
+ {
+ PreFetchMedia = new List()
+ {
+ new MediaInfo()
+ {
+ Uri = new Uri(this.botBaseUri, "audio/speech.wav").ToString(),
+ ResourceId = Guid.NewGuid().ToString(),
+ },
+ },
+ },
acceptedModalities: new List { Modality.Audio }).Request();
await this.GraphApiClient.SendAsync(answerRequest, RequestType.Create, tenantId, scenarioId).ConfigureAwait(false);
}
@@ -432,9 +402,7 @@ private async Task BotAnswerIncomingCallAsync(string callId, string tenantId, Gu
/// The call identifier.
/// The tenant identifier.
/// The scenario identifier.
- ///
- /// When the call has been answered.
- ///
+ /// The .
private async Task BotTransferCallAsync(InvitationParticipantInfo target, string callId, string tenantId, Guid scenarioId)
{
await this.GraphApiClient.SendAsync(this.RequestBuilder.Communications.Calls[callId].Transfer(target).Request(), RequestType.Create, tenantId, scenarioId).ConfigureAwait(false);
@@ -446,19 +414,26 @@ private async Task BotTransferCallAsync(InvitationParticipantInfo target, string
/// The call identifier.
/// The Tenant identifier.
/// The scenario identifier.
- ///
- /// Returns when prompt is played.
- ///
+ /// The .
private async Task BotPlayNotificationPromptAsync(string callId, string tenantId, Guid scenarioId)
{
- var prompts = new Prompt[] { this.MediaMap[BotIncomingPromptName] };
+ var prompts = new Prompt[]
+ {
+ new MediaPrompt
+ {
+ MediaInfo = new MediaInfo()
+ {
+ Uri = new Uri(this.botBaseUri, "audio/speech.wav").ToString(),
+ ResourceId = Guid.NewGuid().ToString(),
+ },
+ },
+ };
var playPromptRequest = this.RequestBuilder.Communications.Calls[callId].PlayPrompt(
prompts: prompts,
clientContext: callId).Request();
await this.GraphApiClient.SendAsync(playPromptRequest, RequestType.Create, tenantId, scenarioId).ConfigureAwait(false);
- return;
}
}
}
\ No newline at end of file
diff --git a/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/SimpleIvrBot.csproj b/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/SimpleIvrBot.csproj
index c6a19c0c..8d085a3e 100644
--- a/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/SimpleIvrBot.csproj
+++ b/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/SimpleIvrBot.csproj
@@ -2,27 +2,17 @@
netcoreapp2.1
- 5dedc36d-1522-49fc-ae69-0e7e9440d29a
- InProcess
- AnyCPU
- Sample.SimpleIvrBot
- Sample.SimpleIvrBot
- false
- x64
+
-
-
-
+
-
-
diff --git a/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/appsettings.json b/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/appsettings.json
index 72d017f4..79bb4fd3 100644
--- a/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/appsettings.json
+++ b/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/appsettings.json
@@ -11,7 +11,7 @@
"AppId": "%AppId%",
"AppSecret": "%AppSecret%",
"PlaceCallEndpointUrl": "https://graph.microsoft.com/v1.0",
- "BotBaseUrl": "%BotBaseUrl%"
+ "BotBaseUrl": "%ServiceDns%"
},
"objectIds": {
"First": "%objectId-1%",
diff --git a/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/wwwroot/audio/speech.wav b/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/wwwroot/audio/speech.wav
index e9eaa95d..5d498c89 100644
Binary files a/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/wwwroot/audio/speech.wav and b/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/wwwroot/audio/speech.wav differ
diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot.sln b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot.sln
new file mode 100644
index 00000000..9fe5f4ab
--- /dev/null
+++ b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot.sln
@@ -0,0 +1,38 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29102.190
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DCC9792D-0B15-4167-8A9F-BB5DDDA6E7D6}"
+ ProjectSection(SolutionItems) = preProject
+ ..\..\configure_cloud.ps1 = ..\..\configure_cloud.ps1
+ ..\Graph.props = ..\Graph.props
+ ..\nuget.config = ..\nuget.config
+ EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VoiceRecorderAndPlaybackBot", "VoiceRecorderAndPlaybackBot\VoiceRecorderAndPlaybackBot.csproj", "{FA70E97D-7018-4B08-A9D0-ABA9967AD7DC}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Common", "..\..\Common\Sample.Common\Sample.Common.csproj", "{3268E59C-90DC-4D7B-97EA-A1DBB2716DF3}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {FA70E97D-7018-4B08-A9D0-ABA9967AD7DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FA70E97D-7018-4B08-A9D0-ABA9967AD7DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FA70E97D-7018-4B08-A9D0-ABA9967AD7DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FA70E97D-7018-4B08-A9D0-ABA9967AD7DC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3268E59C-90DC-4D7B-97EA-A1DBB2716DF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3268E59C-90DC-4D7B-97EA-A1DBB2716DF3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3268E59C-90DC-4D7B-97EA-A1DBB2716DF3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3268E59C-90DC-4D7B-97EA-A1DBB2716DF3}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {49D3AAEB-2787-4CC0-876C-8F6C8E350FD7}
+ EndGlobalSection
+EndGlobal
diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Bot/Bot.cs b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Bot/Bot.cs
new file mode 100644
index 00000000..d9f257ee
--- /dev/null
+++ b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Bot/Bot.cs
@@ -0,0 +1,491 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+//
+
+namespace Sample.VoiceRecorderAndPlaybackBot.Bot
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.IO;
+ using System.Linq;
+ using System.Net;
+ using System.Net.Http;
+ using System.Net.Http.Headers;
+ using System.Threading.Tasks;
+ using Microsoft.AspNetCore.Http;
+ using Microsoft.AspNetCore.Http.Extensions;
+ using Microsoft.Extensions.Primitives;
+ using Microsoft.Graph;
+ using Microsoft.Graph.Communications.Client.Authentication;
+ using Microsoft.Graph.Communications.Client.Transport;
+ using Microsoft.Graph.Communications.Common;
+ using Microsoft.Graph.Communications.Common.Telemetry;
+ using Microsoft.Graph.Communications.Common.Transport;
+ using Microsoft.Graph.Communications.Core.Notifications;
+ using Microsoft.Graph.Communications.Core.Serialization;
+ using Newtonsoft.Json;
+ using Sample.Common;
+ using Sample.Common.Authentication;
+ using Sample.Common.Transport;
+ using Sample.VoiceRecorderAndPlaybackBot.Controller;
+ using Sample.VoiceRecorderAndPlaybackBot.Extensions;
+ using File = System.IO.File;
+ using TraceLevel = System.Diagnostics.TraceLevel;
+
+ ///
+ /// The core bot class.
+ ///
+ public class Bot
+ {
+ private readonly Uri botBaseUri;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The bot options.
+ /// The graph logger.
+ public Bot(BotOptions options, IGraphLogger graphLogger)
+ {
+ this.botBaseUri = options.BotBaseUrl;
+
+ this.GraphLogger = graphLogger;
+ var name = this.GetType().Assembly.GetName().Name;
+ this.AuthenticationProvider = new AuthenticationProvider(name, options.AppId, options.AppSecret, graphLogger);
+ this.Serializer = new CommsSerializer();
+
+ var authenticationWrapper = new AuthenticationWrapper(this.AuthenticationProvider);
+ this.NotificationProcessor = new NotificationProcessor(authenticationWrapper, this.Serializer);
+ this.NotificationProcessor.OnNotificationReceived += this.NotificationProcessor_OnNotificationReceived;
+ this.RequestBuilder = new GraphServiceClient(options.PlaceCallEndpointUrl.AbsoluteUri, authenticationWrapper);
+
+ // Add the default headers used by the graph client.
+ // This will include SdkVersion.
+ var defaultProperties = new List>>();
+ using (HttpClient tempClient = GraphClientFactory.Create(authenticationWrapper))
+ {
+ defaultProperties.AddRange(tempClient.DefaultRequestHeaders.Select(header => GraphProperty.RequestProperty(header.Key, header.Value)));
+ }
+
+ // graph client
+ var productInfo = new ProductInfoHeaderValue(
+ typeof(Bot).Assembly.GetName().Name,
+ typeof(Bot).Assembly.GetName().Version.ToString());
+ this.GraphApiClient = new GraphAuthClient(
+ this.GraphLogger,
+ this.Serializer.JsonSerializerSettings,
+ new HttpClient(),
+ this.AuthenticationProvider,
+ productInfo,
+ defaultProperties);
+ }
+
+ ///
+ /// Gets graph logger.
+ ///
+ public IGraphLogger GraphLogger { get; }
+
+ ///
+ /// Gets the authentication provider.
+ ///
+ public IRequestAuthenticationProvider AuthenticationProvider { get; }
+
+ ///
+ /// Gets the notification processor.
+ ///
+ public INotificationProcessor NotificationProcessor { get; }
+
+ ///
+ /// Gets the URI builder.
+ ///
+ public GraphServiceClient RequestBuilder { get; }
+
+ ///
+ /// Gets the serializer.
+ ///
+ public CommsSerializer Serializer { get; }
+
+ ///
+ /// Gets the stateless graph client.
+ ///
+ public IGraphClient GraphApiClient { get; }
+
+ ///
+ /// Processes the notification asynchronously.
+ /// Here we make sure we log the http request and catch/log any errors.
+ ///
+ /// The request.
+ /// The response.
+ /// The .
+ public async Task ProcessNotificationAsync(
+ HttpRequest request,
+ HttpResponse response)
+ {
+ // TODO: Parse out the scenario id and request id headers.
+ var headers = request.Headers.Select(
+ pair => new KeyValuePair>(pair.Key, pair.Value));
+
+ // Don't log content since we can't PII scrub here (we don't know the type).
+ var stopwatch = new Stopwatch();
+ stopwatch.Start();
+ this.GraphLogger.LogHttpMessage(
+ TraceLevel.Verbose,
+ TransactionDirection.Incoming,
+ HttpTraceType.HttpRequest,
+ request.GetDisplayUrl(),
+ request.Method,
+ obfuscatedContent: null,
+ headers: headers);
+
+ try
+ {
+ var httpRequest = request.CreateRequestMessage();
+ var results = await this.AuthenticationProvider.ValidateInboundRequestAsync(httpRequest).ConfigureAwait(false);
+ if (results.IsValid)
+ {
+ var httpResponse = await this.NotificationProcessor.ProcessNotificationAsync(httpRequest).ConfigureAwait(false);
+ await httpResponse.CreateHttpResponseAsync(response).ConfigureAwait(false);
+ }
+ else
+ {
+ var httpResponse = httpRequest.CreateResponse(HttpStatusCode.Forbidden);
+ await httpResponse.CreateHttpResponseAsync(response).ConfigureAwait(false);
+ }
+
+ headers = response.Headers.Select(
+ pair => new KeyValuePair>(pair.Key, pair.Value));
+
+ this.GraphLogger.LogHttpMessage(
+ TraceLevel.Verbose,
+ TransactionDirection.Incoming,
+ HttpTraceType.HttpResponse,
+ request.GetDisplayUrl(),
+ request.Method,
+ obfuscatedContent: null,
+ headers: headers,
+ responseCode: response.StatusCode,
+ responseTime: stopwatch.ElapsedMilliseconds);
+ }
+ catch (ServiceException e)
+ {
+ string obfuscatedContent = null;
+ if ((int)e.StatusCode >= 300)
+ {
+ response.StatusCode = (int)e.StatusCode;
+ await response.WriteAsync(e.ToString()).ConfigureAwait(false);
+ obfuscatedContent = this.GraphLogger.SerializeAndObfuscate(e, Formatting.Indented);
+ }
+ else if ((int)e.StatusCode >= 200)
+ {
+ response.StatusCode = (int)e.StatusCode;
+ }
+ else
+ {
+ response.StatusCode = (int)e.StatusCode;
+ await response.WriteAsync(e.ToString()).ConfigureAwait(false);
+ obfuscatedContent = this.GraphLogger.SerializeAndObfuscate(e, Formatting.Indented);
+ }
+
+ headers = response.Headers.Select(
+ pair => new KeyValuePair>(pair.Key, pair.Value));
+
+ if (e.ResponseHeaders?.Any() == true)
+ {
+ foreach (var pair in e.ResponseHeaders)
+ {
+ response.Headers.Add(pair.Key, new StringValues(pair.Value.ToArray()));
+ }
+
+ headers = headers.Concat(e.ResponseHeaders);
+ }
+
+ this.GraphLogger.LogHttpMessage(
+ TraceLevel.Error,
+ TransactionDirection.Incoming,
+ HttpTraceType.HttpResponse,
+ request.GetDisplayUrl(),
+ request.Method,
+ obfuscatedContent,
+ headers,
+ response.StatusCode,
+ responseTime: stopwatch.ElapsedMilliseconds);
+ }
+ catch (Exception e)
+ {
+ response.StatusCode = (int)HttpStatusCode.InternalServerError;
+ await response.WriteAsync(e.ToString()).ConfigureAwait(false);
+
+ var obfuscatedContent = this.GraphLogger.SerializeAndObfuscate(e, Formatting.Indented);
+ headers = response.Headers.Select(
+ pair => new KeyValuePair>(pair.Key, pair.Value));
+
+ this.GraphLogger.LogHttpMessage(
+ TraceLevel.Error,
+ TransactionDirection.Incoming,
+ HttpTraceType.HttpResponse,
+ request.GetDisplayUrl(),
+ request.Method,
+ obfuscatedContent,
+ headers,
+ response.StatusCode,
+ responseTime: stopwatch.ElapsedMilliseconds);
+ }
+ }
+
+ ///
+ /// Raised when the has received a notification.
+ ///
+ /// The instance containing the event data.
+ private void NotificationProcessor_OnNotificationReceived(NotificationEventArgs args)
+ {
+#pragma warning disable 4014
+ // Processing notification in the background.
+ // This ensures we're not holding on to the request.
+ this.NotificationProcessor_OnNotificationReceivedAsync(args).ForgetAndLogExceptionAsync(
+ this.GraphLogger,
+ $"Error processing notification {args.Notification.ResourceUrl} with scenario {args.ScenarioId}");
+#pragma warning restore 4014
+ }
+
+ ///
+ /// Raised when the has received a notification asynchronously.
+ ///
+ /// The instance containing the event data.
+ /// The .
+ private async Task NotificationProcessor_OnNotificationReceivedAsync(NotificationEventArgs args)
+ {
+ this.GraphLogger.CorrelationId = args.ScenarioId;
+ var headers = new[]
+ {
+ new KeyValuePair>(HttpConstants.HeaderNames.ScenarioId, new[] { args.ScenarioId.ToString() }),
+ new KeyValuePair>(HttpConstants.HeaderNames.ClientRequestId, new[] { args.RequestId.ToString() }),
+ new KeyValuePair>(HttpConstants.HeaderNames.Tenant, new[] { args.TenantId }),
+ };
+
+ // Create obfuscation content to match what we
+ // would have gotten from the service, then log.
+ var notifications = new CommsNotifications { Value = new[] { args.Notification } };
+ var obfuscatedContent = this.GraphLogger.SerializeAndObfuscate(notifications, Formatting.Indented);
+ this.GraphLogger.LogHttpMessage(
+ TraceLevel.Info,
+ TransactionDirection.Incoming,
+ HttpTraceType.HttpRequest,
+ args.CallbackUri.ToString(),
+ HttpMethods.Post,
+ obfuscatedContent,
+ headers,
+ correlationId: args.ScenarioId,
+ requestId: args.RequestId);
+
+ if (args.ResourceData is Call call)
+ {
+ if (args.ChangeType == ChangeType.Created && call.State == CallState.Incoming)
+ {
+ await this.BotAnswerIncomingCallAsync(call.Id, args.TenantId, args.ScenarioId).ConfigureAwait(false);
+ }
+ else if (args.ChangeType == ChangeType.Updated && call.State == CallState.Established && call.MediaState?.Audio == MediaState.Active)
+ {
+ // there can potentially be multiple established notifications for the same call,
+ // but since this is just sample code, it is not handled.
+ // your production code must handle such a case and not call record multiple times for the same call
+ await this.BotRecordsIncomingCallAsync(call.Id, args.TenantId, args.ScenarioId).ConfigureAwait(false);
+ }
+ else if (args.ChangeType == ChangeType.Deleted && call.State == CallState.Terminated)
+ {
+ this.CleanupCall(call.Id);
+ }
+ }
+ // Receiving updates for the play prompt operation.
+ else if (args.ResourceData is PlayPromptOperation playPromptOperation)
+ {
+ // checking for the call id sent in ClientContext.
+ if (string.IsNullOrWhiteSpace(playPromptOperation.ClientContext))
+ {
+ throw new ServiceException(new Error()
+ {
+ Message = "No call id provided in PlayPromptOperation.ClientContext.",
+ });
+ }
+ else if (playPromptOperation.Status == OperationStatus.Completed)
+ {
+ // The operation has been completed, hang up the call
+ await this.BotHangupCallAsync(playPromptOperation.ClientContext, args.TenantId, args.ScenarioId).ConfigureAwait(false);
+ this.GraphLogger.Log(TraceLevel.Info, $"Disconnecting the call.");
+ }
+ }
+ else if (args.ResourceData is RecordOperation recordOperation)
+ {
+ if (recordOperation.Status == OperationStatus.Completed && recordOperation.ResultInfo.Code == 200)
+ {
+ var recordingFileName = $"audio/recording-{recordOperation.ClientContext}.wav";
+
+ await this.DownloadRecording(recordingFileName, recordOperation).ConfigureAwait(false);
+
+ var prompts = new Prompt[] {
+ new MediaPrompt
+ {
+ MediaInfo = new MediaInfo()
+ {
+ Uri = new Uri(this.botBaseUri, recordingFileName).ToString()
+ },
+ },
+ };
+
+ await this.BotPlayPromptAsync(prompts, recordOperation.ClientContext, args.TenantId, args.ScenarioId).ConfigureAwait(false);
+ }
+ }
+ }
+
+ ///
+ /// Bot answers incoming call.
+ ///
+ /// The call identifier to answer.
+ /// The tenant identifier.
+ /// The scenario identifier.
+ /// The .
+ private async Task BotAnswerIncomingCallAsync(string callId, string tenantId, Guid scenarioId)
+ {
+ var answerRequest = this.RequestBuilder.Communications.Calls[callId].Answer(
+ callbackUri: new Uri(this.botBaseUri, ControllerConstants.CallbackPrefix).ToString(),
+ mediaConfig: new ServiceHostedMediaConfig {
+ PreFetchMedia = new List()
+ {
+ new MediaInfo()
+ {
+ Uri = new Uri(this.botBaseUri, "audio/speech.wav").ToString(),
+ ResourceId = Guid.NewGuid().ToString(),
+ }
+ }
+ },
+ acceptedModalities: new List { Modality.Audio }).Request();
+ await this.GraphApiClient.SendAsync(answerRequest, RequestType.Create, tenantId, scenarioId).ConfigureAwait(false);
+ }
+
+ ///
+ /// Bot Hangs up the call.
+ ///
+ /// The call id to answer.
+ /// The tenant identifier.
+ /// The scenario identifier.
+ /// The .
+ private async Task BotHangupCallAsync(string callId, string tenantId, Guid scenarioId)
+ {
+ var hangupRequest = this.RequestBuilder.Communications.Calls[callId].Request();
+ await this.GraphApiClient.SendAsync(hangupRequest, RequestType.Delete, tenantId, scenarioId).ConfigureAwait(false);
+ }
+
+ ///
+ /// Bot records incoming call.
+ ///
+ /// The call identifier to answer.
+ /// The tenant identifier.
+ /// The scenario identifier.
+ /// The .
+ private async Task BotRecordsIncomingCallAsync(string callId, string tenantId, Guid scenarioId)
+ {
+ var prompts = new Prompt[] {
+ new MediaPrompt
+ {
+ MediaInfo = new MediaInfo()
+ {
+ Uri = new Uri(this.botBaseUri, "audio/speech.wav").ToString(),
+ ResourceId = Guid.NewGuid().ToString(),
+ },
+ },
+ };
+
+ IEnumerable stopTones = new List() { "#", "1", "*" };
+ var recordRequest = this.RequestBuilder.Communications.Calls[callId].RecordResponse(
+ bargeInAllowed: true,
+ clientContext: callId, // set clientcontext as callid
+ prompts: prompts,
+ maxRecordDurationInSeconds: 10,
+ initialSilenceTimeoutInSeconds: 5,
+ maxSilenceTimeoutInSeconds: 2,
+ playBeep: true,
+ stopTones: stopTones).Request();
+
+ await this.GraphApiClient.SendAsync(recordRequest, RequestType.Create, tenantId, scenarioId).ConfigureAwait(false);
+ }
+
+ ///
+ /// Bot plays the given prompts.
+ ///
+ /// The prompts to play.
+ /// The call identifier.
+ /// The tenant identifier.
+ /// The scenario identifier.
+ /// A
+ private async Task BotPlayPromptAsync(IEnumerable prompts, string callId, string tenantId, Guid scenarioId)
+ {
+ var playPromptRequest = this.RequestBuilder.Communications.Calls[callId].PlayPrompt(
+ prompts: prompts,
+ clientContext: callId).Request();
+ await this.GraphApiClient.SendAsync(playPromptRequest, RequestType.Create, tenantId, scenarioId).ConfigureAwait(false);
+ }
+
+ ///
+ /// Cleans up the specified call.
+ ///
+ /// The id of the call being cleaned up.
+ private void CleanupCall(string callId)
+ {
+ this.GraphLogger.Log(TraceLevel.Info, $"Cleaning up call {callId}");
+
+ // Any media collected may not be persisted, so delete it.
+ // Make sure you are compliant with the laws and regulations of your area when it comes to call recording.
+ // Please consult with a legal counsel for more information.
+
+ var fileInfo = new FileInfo($"wwwroot/audio/recording-{callId}.wav");
+ if (File.Exists(fileInfo.FullName))
+ {
+ this.GraphLogger.Log(TraceLevel.Info, $"Deleting {fileInfo.FullName}");
+ File.Delete(fileInfo.FullName);
+ }
+ else
+ {
+ throw new ServiceException(new Error()
+ {
+ Message = "File does not exist to be deleted.",
+ });
+ }
+ }
+
+ ///
+ /// Downloads the recording.
+ ///
+ /// File name where recording is to be downloaded.
+ /// Record Operation.
+ /// The .
+ private async Task DownloadRecording(string recordingFileName, RecordOperation recordOperation)
+ {
+ using (var httpClient = new HttpClient())
+ {
+ var requestMessage = new HttpRequestMessage(
+ HttpMethod.Get,
+ new Uri(recordOperation.RecordingLocation));
+ requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", recordOperation.RecordingAccessToken);
+
+ var httpResponse = await httpClient.SendAsync(requestMessage).ConfigureAwait(false);
+
+ if (!httpResponse.IsSuccessStatusCode)
+ {
+ throw new ServiceException(new Error()
+ {
+ Message = "Unable to download the recording file.",
+ });
+ }
+ using (var stream = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false))
+ {
+ FileStream fileStream = null;
+ var fileInfo = new FileInfo($"wwwroot/{recordingFileName}");
+ using (fileStream = new FileStream(fileInfo.FullName, FileMode.Create, FileAccess.Write, FileShare.None))
+ {
+ await stream.CopyToAsync(fileStream).ConfigureAwait(false);
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Bot/BotOptions.cs b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Bot/BotOptions.cs
new file mode 100644
index 00000000..9c0c37b8
--- /dev/null
+++ b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Bot/BotOptions.cs
@@ -0,0 +1,35 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+//
+
+namespace Sample.VoiceRecorderAndPlaybackBot.Bot
+{
+ using System;
+
+ ///
+ /// The bot options class.
+ ///
+ public class BotOptions
+ {
+ ///
+ /// Gets or sets the application id.
+ ///
+ public string AppId { get; set; }
+
+ ///
+ /// Gets or sets the application secret.
+ ///
+ public string AppSecret { get; set; }
+
+ ///
+ /// Gets or sets the calls uri of the application.
+ ///
+ public Uri BotBaseUrl { get; set; }
+
+ ///
+ /// Gets or sets the comms platform endpoint uri.
+ ///
+ public Uri PlaceCallEndpointUrl { get; set; }
+ }
+}
diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Controllers/ControllerConstants.cs b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Controllers/ControllerConstants.cs
new file mode 100644
index 00000000..daf3d81d
--- /dev/null
+++ b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Controllers/ControllerConstants.cs
@@ -0,0 +1,18 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+//
+
+namespace Sample.VoiceRecorderAndPlaybackBot.Controller
+{
+ ///
+ /// Http route constants for routing requests.
+ ///
+ public class ControllerConstants
+ {
+ ///
+ /// Route prefix for all incoming requests.
+ ///
+ public const string CallbackPrefix = "/callback";
+ }
+}
\ No newline at end of file
diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Controllers/HomeController.cs b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Controllers/HomeController.cs
new file mode 100644
index 00000000..aa7f8621
--- /dev/null
+++ b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Controllers/HomeController.cs
@@ -0,0 +1,89 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+//
+
+namespace Sample.VoiceRecorderAndPlaybackBot.Controller
+{
+ using Microsoft.AspNetCore.Mvc;
+ using Sample.Common.Logging;
+
+ ///
+ /// The home controller class.
+ ///
+ public class HomeController : Controller
+ {
+ private readonly SampleObserver observer;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The observer.
+ public HomeController(SampleObserver observer)
+ {
+ this.observer = observer;
+ }
+
+ ///
+ /// Get the default content of home page.
+ ///
+ /// Default content.
+ [HttpGet("/")]
+ public string Get()
+ {
+ return "Home Page";
+ }
+
+ ///
+ /// Get the service logs.
+ ///
+ /// Skip specified lines.
+ /// Take specified lines.
+ /// The logs.
+ [HttpGet]
+ [Route("/logs")]
+ public IActionResult GetLogs(
+ [FromQuery] int skip = 0,
+ [FromQuery] int take = 1000)
+ {
+ this.AddRefreshHeader(3);
+ return this.Content(
+ this.observer.GetLogs(skip, take),
+ System.Net.Mime.MediaTypeNames.Text.Plain,
+ System.Text.Encoding.UTF8);
+ }
+
+ ///
+ /// Get the service logs.
+ ///
+ /// The filter.
+ /// Skip specified lines.
+ /// Take specified lines.
+ ///
+ /// The logs.
+ ///
+ [HttpGet]
+ [Route("/logs/{filter}")]
+ public IActionResult GetLogs(
+ string filter,
+ [FromQuery] int skip = 0,
+ [FromQuery] int take = 1000)
+ {
+ this.AddRefreshHeader(3);
+ return this.Content(
+ this.observer.GetLogs(filter, skip, take),
+ System.Net.Mime.MediaTypeNames.Text.Plain,
+ System.Text.Encoding.UTF8);
+ }
+
+ ///
+ /// Add refresh headers for browsers to download content.
+ ///
+ /// Refresh rate.
+ private void AddRefreshHeader(int seconds)
+ {
+ this.Response.Headers.Add("Cache-Control", "private,must-revalidate,post-check=1,pre-check=2,no-cache");
+ this.Response.Headers.Add("Refresh", seconds.ToString());
+ }
+ }
+}
diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Controllers/PlatformCallController.cs b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Controllers/PlatformCallController.cs
new file mode 100644
index 00000000..d3497de7
--- /dev/null
+++ b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Controllers/PlatformCallController.cs
@@ -0,0 +1,42 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+//
+
+namespace Sample.VoiceRecorderAndPlaybackBot.Controller
+{
+ using System.Threading.Tasks;
+ using Microsoft.AspNetCore.Mvc;
+ using Microsoft.Graph.Communications.Common.Telemetry;
+ using Sample.VoiceRecorderAndPlaybackBot.Bot;
+
+ ///
+ /// Entry point for handling call-related web hook requests.
+ ///
+ public class PlatformCallController : Controller
+ {
+ private readonly IGraphLogger graphLogger;
+ private readonly Bot bot;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The bot.
+ public PlatformCallController(Bot bot)
+ {
+ this.bot = bot;
+ this.graphLogger = bot.GraphLogger.CreateShim(nameof(PlatformCallController));
+ }
+
+ ///
+ /// Handle call back for bot calls user case.
+ ///
+ /// returns when task is done.
+ [HttpPost]
+ [Route(ControllerConstants.CallbackPrefix)]
+ public async Task OnIncomingBotCallUserRequestAsync()
+ {
+ await this.bot.ProcessNotificationAsync(this.Request, this.Response).ConfigureAwait(false);
+ }
+ }
+}
diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Extensions/BotBuilderExtensions.cs b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Extensions/BotBuilderExtensions.cs
new file mode 100644
index 00000000..2bfcadfb
--- /dev/null
+++ b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Extensions/BotBuilderExtensions.cs
@@ -0,0 +1,39 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+//
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+ using System;
+ using Sample.VoiceRecorderAndPlaybackBot.Bot;
+
+ ///
+ /// The bot builder extensions class.
+ ///
+ public static class BotBuilderExtensions
+ {
+ ///
+ /// Add bot feature.
+ ///
+ /// The service collection.
+ /// The updated service collection.
+ public static IServiceCollection AddBot(this IServiceCollection services)
+ => services.AddBot(_ => { });
+
+ ///
+ /// Add bot feature.
+ ///
+ /// The service collection.
+ /// The action for bot options.
+ /// The updated service collection.
+ public static IServiceCollection AddBot(this IServiceCollection services, Action botOptionsAction)
+ {
+ var options = new BotOptions();
+ botOptionsAction(options);
+ services.AddSingleton(options);
+
+ return services.AddSingleton();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Extensions/ControllerExtentions.cs b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Extensions/ControllerExtentions.cs
new file mode 100644
index 00000000..e2ae9d17
--- /dev/null
+++ b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Extensions/ControllerExtentions.cs
@@ -0,0 +1,73 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+//
+
+namespace Sample.VoiceRecorderAndPlaybackBot.Extensions
+{
+ using System;
+ using System.Linq;
+ using System.Net;
+ using System.Net.Http.Headers;
+ using Microsoft.AspNetCore.Http;
+ using Microsoft.AspNetCore.Mvc;
+ using Microsoft.Extensions.Primitives;
+ using Microsoft.Graph;
+
+ ///
+ /// The controller exceptions.
+ ///
+ public static class ControllerExtentions
+ {
+ ///
+ /// Convert exception to action result.
+ ///
+ /// The controller.
+ /// The exception.
+ /// The action result.
+ public static IActionResult Exception(this Controller controller, Exception exception)
+ {
+ IActionResult result;
+
+ if (exception is ServiceException e)
+ {
+ controller.HttpContext.Response.CopyHeaders(e.ResponseHeaders);
+
+ int statusCode = (int)e.StatusCode;
+
+ result = statusCode >= 300
+ ? controller.StatusCode(statusCode, e.ToString())
+ : controller.StatusCode((int)HttpStatusCode.InternalServerError, e.ToString());
+ }
+ else
+ {
+ result = controller.StatusCode((int)HttpStatusCode.InternalServerError, exception.ToString());
+ }
+
+ return result;
+ }
+
+ ///
+ /// Copy the response headers to controller.HttpContext.Response.
+ ///
+ /// The controller.
+ /// The headers.
+ private static void CopyHeaders(this HttpResponse response, HttpHeaders headers)
+ {
+ if (headers == null)
+ {
+ // do nothing as the source headers are null.
+ return;
+ }
+
+ foreach (var header in headers)
+ {
+ var values = header.Value?.ToArray();
+ if (values?.Any() == true)
+ {
+ response.Headers.Add(header.Key, new StringValues(values));
+ }
+ }
+ }
+ }
+}
diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Extensions/HttpExtensions.cs b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Extensions/HttpExtensions.cs
new file mode 100644
index 00000000..5191bab5
--- /dev/null
+++ b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Extensions/HttpExtensions.cs
@@ -0,0 +1,72 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+//
+
+namespace Sample.VoiceRecorderAndPlaybackBot.Extensions
+{
+ using System;
+ using System.Net.Http;
+ using System.Threading.Tasks;
+ using Microsoft.AspNetCore.Http;
+ using Microsoft.AspNetCore.Http.Extensions;
+
+ ///
+ /// Extensions for ASP.NET HTTP request and response.
+ ///
+ public static class HttpExtensions
+ {
+ ///
+ /// Creates the request message asynchronous.
+ ///
+ /// The request.
+ /// The .
+ public static HttpRequestMessage CreateRequestMessage(this HttpRequest request)
+ {
+ var displayUri = request.GetDisplayUrl();
+ var httpRequest = new HttpRequestMessage
+ {
+ RequestUri = new Uri(displayUri),
+ Method = new HttpMethod(request.Method),
+ };
+
+ if (request.ContentLength.HasValue && request.ContentLength.Value > 0)
+ {
+ httpRequest.Content = new StreamContent(request.Body);
+ }
+
+ // Copy headers
+ foreach (var header in request.Headers)
+ {
+ httpRequest.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
+ }
+
+ return httpRequest;
+ }
+
+ ///
+ /// Creates the HTTP response.
+ ///
+ /// The response.
+ /// The HTTP response.
+ /// The populated .
+ public static async Task CreateHttpResponseAsync(this HttpResponseMessage response, HttpResponse httpResponse)
+ {
+ httpResponse.StatusCode = (int)response.StatusCode;
+
+ if (response.Content != null)
+ {
+ var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+ await httpResponse.WriteAsync(content).ConfigureAwait(false);
+ }
+
+ // Copy headers
+ foreach (var header in response.Headers)
+ {
+ response.Headers.Add(header.Key, header.Value);
+ }
+
+ return httpResponse;
+ }
+ }
+}
diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Program.cs b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Program.cs
new file mode 100644
index 00000000..eb59080b
--- /dev/null
+++ b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Program.cs
@@ -0,0 +1,27 @@
+using Microsoft.AspNetCore;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace Sample.VoiceRecorderAndPlaybackBot
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ CreateHostBuilder(args).Build().Run();
+ }
+
+ public static IWebHostBuilder CreateHostBuilder(string[] args) =>
+ WebHost.CreateDefaultBuilder(args)
+ .ConfigureLogging((hostingContext, logging) =>
+ {
+ logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
+ logging.AddDebug();
+ logging.AddConsole();
+ logging.AddAzureWebAppDiagnostics();
+ })
+ .UseStartup();
+ }
+}
\ No newline at end of file
diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Properties/launchSettings.json b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Properties/launchSettings.json
new file mode 100644
index 00000000..75a7508e
--- /dev/null
+++ b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Properties/launchSettings.json
@@ -0,0 +1,28 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:2404",
+ "sslPort": 44390
+ }
+ },
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "VoiceRecorderAndPlaybackBot": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "applicationUrl": "https://localhost:5001;http://localhost:5000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/README.md b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/README.md
new file mode 100644
index 00000000..70945494
--- /dev/null
+++ b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/README.md
@@ -0,0 +1,99 @@
+# Introduction
+The sample demostrate a recording audio by user and playing back to user workflow.
+
+# Sample status
+1. The user will call bot and Bot will answer the call.
+2. Bot plays audio prompt message, indicating it is ready to record.
+3. Bot records what the user speaks, with maximum duration allowed 10 seconds.
+4. Bot plays back the recorded message.
+5. Bot hangs up the call.
+
+# Getting Started
+1. Installation process
+ * Enable an Azure subscription to host web sites and bot services.
+ * Install Visual Studio 2017
+ * Launch CommsSamples.sln in \Samples with Visual Studio 2017 (VS2017)
+ * Click menu Build/"Build Solution" to build the whole solution
+ * Create an BotService in an Azure subscription with Azure Portal (https://portal.azure.com), then enable both Teams & Skype channels on the Azure portal, and configure the calling Uri of the bot.
+ - Go to "Bot Services" resource type page, click "Add", select "Bot Channels Registration", click "Create", then follow the instructions.
+ - Write down the application ID **\{ApplicationId}** and **\{ApplicationSecret}** for next steps.
+ - Click "VoiceRecorderAndPlaybackBot" in "Bot Services" resource type page, Click "Channels", Then select "Microsoft Teams" and "Skype" channels and enable both of them.
+ - Click "edit" button of "Skype" channel, click "Calling" tab, select "Enable calling" radio button, then select "IVR - 1:1 IVR audio calls", and fill the Webhook (for calling) edit box with value "\{BotBaseUrl}/callback".
+ * Configure permissions for the Bot.
+ - This Bot doesnt need any permissions
+
+# Getting Started (Azure Version)
+1. Installation process
+ * Create web site in Azure
+ - Right click 1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/"Connected Services" and select "Add Connected Service" in project VoiceRecorderAndPlaybackBot in VS2017, then select Publish tab, and click "Create new profile" to lauch a dialog
+ - Select "App Service" then click "Create New" radio button, then click "Publish" button to create a App Service and publish the code on.
+ - Write down the web site root uri **\{BotBaseUrl}** for next steps.
+
+ * Update the following elements in appsettings.json file in project VoiceRecorderAndPlaybackBot.
+ - Bot/AppId: "**\{ApplicationId}**"
+ - Bot/AppSecret: "**\{ApplicationSecret}**"
+ - Bot/BotBaseUrl: "**\{BotBaseUrl}**"
+
+ * Publish the application again.
+ - Right click VoiceRecorderAndPlaybackBot/"Connected Services" and select "Add Connected Service" in project NotificationBot in VS2017, click "Publish" button.
+
+2. Update process
+ * Update code properly.
+ * Publish the application again.
+
+3. Software dependencies
+ * .NET Framework 471
+ * Nuget packages list are in \\Dependencies\Nuget in Solution Explorer of Visual Studio 2017
+
+4. Latest releases
+ * version 0.2
+
+5. API references
+
+# Getting Started (Local Run Version)
+1. Installation process
+ * Install Visual Studio 2017
+ * Launch CommsSamples.sln in \Samples with Visual Studio 2017 (VS2017)
+ * Click menu Build/"Build Solution" to build the whole solution
+ * Setup ngrok.
+ - Sign up for a free ngrok account. Once signed up, go to the ngrok [dashboard](https://dashboard.ngrok.com/) and get your auth token.
+ - Create an ngrok configuration file `ngrok.yml` as follows:
+ ```yaml
+ authtoken: %replace_with_auth_token_from_dashboard%
+ tunnels:
+ signaling:
+ addr: 9442
+ proto: http
+ media:
+ addr: 8445
+ proto: tcp
+ ```
+ - Start ngrok: `ngrok http https://localhost:44379 -host-header=localhost`. You will see an output like this:
+ ```ymal
+ Session Status online
+ Account YourName (Plan: Free)
+ Version x.x.xx
+ Region United States (us)
+ Web Interface http://127.0.0.1:4040
+ Forwarding http://e6c2321a.ngrok.io -> https://localhost:44379
+ Forwarding https://e6c2321a.ngrok.io -> https://localhost:44379
+ ```
+ - From **your** output, in line Forwarding (yours will be different) the first url`https://e6c2321a.ngrok.io` will be your bot base uri. Write down the bot base uri as **\{BotBaseUrl}** for next steps.
+ * Update the following elements in appsettings.json file in project NotificationBot.
+ - Bot/AppId: "**\{ApplicationId}**"
+ - Bot/AppSecret: "**\{ApplicationSecret}**"
+ - Bot/BotBaseUrl: "**\{BotBaseUrl}**"
+
+# Build and Test
+1. Create a tenant in O365, with Teams enabled.
+
+2. Create two users in O365, with Teams enabled.
+ * Write down the users' object IDs as \{UserObjectId-1} for user1 and \{UserObjectId-2} for user2 (optional).
+
+3. Install Teams client.
+
+4. Login to Teams
+
+5. Install the VoiceRecorderAndPlaybackBot in the client using steps mentioned here: https://microsoftgraph.github.io/microsoft-graph-comms-samples/docs/articles/calls/register-calling-bot.html#register-bot-in-microsoft-teams
+
+6. user1 should call VoiceRecorderAndPlaybackBot using the Teams client, and bot should play prompt indicating it is ready to record the user's audio.
diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Startup.cs b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Startup.cs
new file mode 100644
index 00000000..7f1d2854
--- /dev/null
+++ b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Startup.cs
@@ -0,0 +1,61 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Graph.Communications.Common.Telemetry;
+using Sample.Common.Logging;
+
+namespace Sample.VoiceRecorderAndPlaybackBot
+{
+ public class Startup
+ {
+ private readonly GraphLogger logger;
+ private readonly SampleObserver observer;
+
+ public Startup(IConfiguration configuration)
+ {
+ this.Configuration = configuration;
+ this.logger = new GraphLogger(typeof(Startup).Assembly.GetName().Name);
+ this.observer = new SampleObserver(this.logger);
+ }
+
+ public IConfiguration Configuration { get; }
+
+ // This method gets called by the runtime. Use this method to add services to the container.
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services
+ .AddSingleton(this.observer)
+ .AddSingleton(this.logger);
+
+ services
+ .AddBot(options => this.Configuration.Bind("Bot", options))
+ .AddMvc()
+ .SetCompatibilityVersion(CompatibilityVersion.Latest);
+ }
+
+ ///
+ /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+ ///
+ /// App builder.
+ /// Hosting environment.
+ /// /// The logger of ILogger instance.
+ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
+ {
+ this.logger.BindToILoggerFactory(loggerFactory);
+
+ if (env.IsDevelopment())
+ {
+ app.UseDeveloperExceptionPage();
+ }
+
+ app.UseHttpsRedirection();
+ app.UseStaticFiles();
+ app.UseCookiePolicy();
+
+ app.UseMvc();
+ }
+ }
+}
diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/VoiceRecorderAndPlaybackBot.csproj b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/VoiceRecorderAndPlaybackBot.csproj
new file mode 100644
index 00000000..755625b5
--- /dev/null
+++ b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/VoiceRecorderAndPlaybackBot.csproj
@@ -0,0 +1,23 @@
+
+
+
+ netcoreapp2.1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/appsettings.json b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/appsettings.json
new file mode 100644
index 00000000..fa4119d8
--- /dev/null
+++ b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/appsettings.json
@@ -0,0 +1,16 @@
+{
+ "Logging": {
+ "IncludeScopes": false,
+ "LogLevel": {
+ "Default": "Information",
+ "System": "Information",
+ "Microsoft": "Information"
+ }
+ },
+ "Bot": {
+ "AppId": "%AppId%",
+ "AppSecret": "%AppSecret%",
+ "PlaceCallEndpointUrl": "https://graph.microsoft.com/v1.0",
+ "BotBaseUrl": "%ServiceDns%"
+ }
+}
\ No newline at end of file
diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/wwwroot/audio/speech.wav b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/wwwroot/audio/speech.wav
new file mode 100644
index 00000000..fb060767
Binary files /dev/null and b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/wwwroot/audio/speech.wav differ
diff --git a/changelog.md b/changelog.md
index a2f08e0d..88591aa8 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,8 +1,18 @@
# Changelog for Microsoft Graph Communications SDK and Samples
This changelog covers what's changed in Microsoft Graph Communications SDK and its associated samples.
+## Mar 2020
+
+- Updated to latest /beta and /v1 contracts
+ - For a full list of changes refer to the [graph blog](https://developer.microsoft.com/en-us/graph/blogs/microsoft-graph-net-sdk-updates/).
+- Changed name of Compliance Recording to Policy Recording
+- Migrated the Policy Recording sample to the https://graph.microsoft.com/v1.0 endpoint.
+- Updated Communications libraries in v1 bots to use the 1.2.0.791 SDK.
+ - Exposed the Policy Recording API's in v1.
+- Updated Media library in v1 bots to 1.17.0.39-alpha
## Feb 2020
+
- Updated Compliance Recording sample docs.
## Jan 2020