RealTimeMediaCalling SDK and Samples - huebot and MeetingScreenshotsBot
This commit is contained in:
Родитель
69687a8d59
Коммит
bd53bd2e9c
|
@ -0,0 +1,63 @@
|
|||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=crlf
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
|
@ -0,0 +1,72 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
using Microsoft.Bot.Builder.Calling.Events;
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Contracts;
|
||||
using Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Contracts;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// EventArg for the OnAnswerAppHostedMediaCompleted event raised on <see cref="IRealTimeMediaCallService"/>.
|
||||
/// </summary>
|
||||
public class AnswerAppHostedMediaOutcomeEvent : OutcomeEventBase
|
||||
{
|
||||
/// <summary>
|
||||
/// EventArg for the OnAnswerAppHostedMediaCompleted event raised on <see cref="IRealTimeMediaCallService"/>.
|
||||
/// </summary>
|
||||
/// <param name="conversationResult">ConversationResult corresponding to the event</param>
|
||||
/// <param name="resultingWorkflow">Workflow to be returned on completion</param>
|
||||
/// <param name="outcome">outcome of the operation</param>
|
||||
public AnswerAppHostedMediaOutcomeEvent(ConversationResult conversationResult, RealTimeMediaWorkflow resultingWorkflow, AnswerAppHostedMediaOutcome outcome) : base(conversationResult, resultingWorkflow)
|
||||
{
|
||||
if (outcome == null)
|
||||
throw new ArgumentNullException(nameof(outcome));
|
||||
AnswerAppHostedMediaOutcome = outcome;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Outcome of AnswerAppHostedMedia action
|
||||
/// </summary>
|
||||
public AnswerAppHostedMediaOutcome AnswerAppHostedMediaOutcome { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Workflow associated with the AnswerAppHostedMediaOutcome event
|
||||
/// </summary>
|
||||
public RealTimeMediaWorkflow RealTimeMediaWorkflow
|
||||
{
|
||||
get { return ResultingWorkflow as RealTimeMediaWorkflow; }
|
||||
set { ResultingWorkflow = value; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Contracts;
|
||||
using Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Contracts;
|
||||
using Microsoft.Bot.Builder.Calling.Events;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// EventArg for the OnIncomingCallReceived event raised on <see cref="IRealTimeMediaCallService"/>.
|
||||
/// </summary>
|
||||
public class RealTimeMediaIncomingCallEvent : IncomingCallEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// EventArg for the RealTimeMediaIncomingCallEvent event raised on <see cref="IRealTimeMediaCallService"/>.
|
||||
/// </summary>
|
||||
/// <param name="conversation">Conversation for the incoming call</param>
|
||||
/// <param name="resultingWorkflow">Workflow to be returned on completion</param>
|
||||
public RealTimeMediaIncomingCallEvent(Conversation conversation, RealTimeMediaWorkflow resultingWorkflow): base(conversation, resultingWorkflow)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Workflow associated with the event
|
||||
/// </summary>
|
||||
public RealTimeMediaWorkflow RealTimeMediaWorkflow
|
||||
{
|
||||
get { return ResultingWorkflow as RealTimeMediaWorkflow; }
|
||||
set { ResultingWorkflow = value; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using Microsoft.Bot.Builder.Calling.Events;
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Contracts;
|
||||
using Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Contracts;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// EventArg for the OnWorkflowValidationFailed event raised on <see cref="IRealTimeMediaCallService"/>.
|
||||
/// </summary>
|
||||
public class RealTimeMediaWorkflowValidationOutcomeEvent : WorkflowValidationOutcomeEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// EventArg for the OnWorkflowValidationFailed event raised on <see cref="IRealTimeMediaCallService"/>.
|
||||
/// </summary>
|
||||
/// <param name="conversationResult">ConversationResult corresponding to the event</param>
|
||||
/// <param name="resultingWorkflow">Workflow to be returned on completion</param>
|
||||
/// <param name="outcome">outcome of the operation</param>
|
||||
public RealTimeMediaWorkflowValidationOutcomeEvent(
|
||||
ConversationResult conversationResult,
|
||||
RealTimeMediaWorkflow resultingWorkflow,
|
||||
WorkflowValidationOutcome outcome) : base(conversationResult, resultingWorkflow, outcome)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Workflow associated with the OnWorkflowValidationFailed event
|
||||
/// </summary>
|
||||
public RealTimeMediaWorkflow RealTimeMediaWorkflow
|
||||
{
|
||||
get { return ResultingWorkflow as RealTimeMediaWorkflow; }
|
||||
set { ResultingWorkflow = value; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for the bot to handle events for a real-time media call and to make outgoing requests.
|
||||
/// </summary>
|
||||
public interface IRealTimeMediaCall
|
||||
{
|
||||
/// <summary>
|
||||
/// Service that helps process incoming requests and send outgoing requests
|
||||
/// </summary>
|
||||
IRealTimeMediaCallService CallService { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder.Calling.Exceptions;
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Contracts;
|
||||
using Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Contracts;
|
||||
using Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Misc;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of responses returned to the bot
|
||||
/// </summary>
|
||||
public enum ResponseType
|
||||
{
|
||||
/// <summary>
|
||||
/// Request was accepted or processed.
|
||||
/// </summary>
|
||||
Accepted,
|
||||
|
||||
/// <summary>
|
||||
/// Request was semantically/syntactically invalid.
|
||||
/// </summary>
|
||||
BadRequest,
|
||||
|
||||
/// <summary>
|
||||
/// The call for the incoming callback or notification is no longer active.
|
||||
/// </summary>
|
||||
NotFound
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores the type of the response and the content
|
||||
/// </summary>
|
||||
public class ResponseResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of the response
|
||||
/// </summary>
|
||||
public readonly ResponseType ResponseType;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public readonly string Content;
|
||||
|
||||
/// <summary>
|
||||
/// Creates ResponseResult with the type of the response and the content
|
||||
/// </summary>
|
||||
public ResponseResult(ResponseType responseType, string content = null)
|
||||
{
|
||||
ResponseType = responseType;
|
||||
Content = content;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the incoming requests and invokes the appropriate handlers for the call
|
||||
/// </summary>
|
||||
interface IRealTimeCallProcessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Processes incoming call request
|
||||
/// </summary>
|
||||
/// <param name="content">Content from the request</param>
|
||||
/// <param name="skypeChainId">X-Microsoft-Skype-Chain-Id header value used to associate calls across different services</param>
|
||||
/// <returns></returns>
|
||||
Task<ResponseResult> ProcessIncomingCallAsync(string content, string skypeChainId);
|
||||
|
||||
/// <summary>
|
||||
/// Processes requests sent to the callback url
|
||||
/// </summary>
|
||||
/// <param name="content">Content from the request</param>
|
||||
/// <returns></returns>
|
||||
Task<ResponseResult> ProcessCallbackAsync(string content);
|
||||
|
||||
/// <summary>
|
||||
/// Processes requests sent to notification url
|
||||
/// </summary>
|
||||
/// <param name="content">Content from the request</param>
|
||||
/// <returns></returns>
|
||||
Task<ResponseResult> ProcessNotificationAsync(string content);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder.RealTimeMediaCalling.Events;
|
||||
using Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Contracts;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling
|
||||
{
|
||||
/// <summary>
|
||||
/// Service interface that invokes the appropriate events on an incoming real-time media call and provides functions to make outgoing requests for that call.
|
||||
/// </summary>
|
||||
public interface IRealTimeMediaCallService
|
||||
{
|
||||
/// <summary>
|
||||
/// Id used for correlating logs across different services
|
||||
/// </summary>
|
||||
string CorrelationId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when bot receives incoming call
|
||||
/// </summary>
|
||||
event Func<RealTimeMediaIncomingCallEvent, Task> OnIncomingCallReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when specified workflow fails to be validated by Bot platform
|
||||
/// </summary>
|
||||
event Func<RealTimeMediaWorkflowValidationOutcomeEvent, Task> OnWorkflowValidationFailed;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when the bot gets the outcome of AnswerAppHostedMedia action. If the operation was successful the call is established
|
||||
/// </summary>
|
||||
event Func<AnswerAppHostedMediaOutcomeEvent, Task> OnAnswerAppHostedMediaCompleted;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when the bot gets call state change notification
|
||||
/// </summary>
|
||||
event Func<CallStateChangeNotification, Task> OnCallStateChangeNotification;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when bot gets roster update notification
|
||||
/// </summary>
|
||||
event Func<RosterUpdateNotification, Task> OnRosterUpdateNotification;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when bot needs to cleanup the call
|
||||
/// </summary>
|
||||
event Func<Task> OnCallCleanup;
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to video or video-based screen sharing channel
|
||||
/// </summary>
|
||||
/// <param name="videoSubscription">Details regarding the subscription like the source to subscribe, socket on which subscription needs to be done, etc</param>
|
||||
/// <returns></returns>
|
||||
Task Subscribe(VideoSubscription videoSubscription);
|
||||
|
||||
/// <summary>
|
||||
/// Terminate the call
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task EndCall();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration settings for the bot
|
||||
/// </summary>
|
||||
public interface IRealTimeMediaCallServiceSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// The url where the calling callbacks to the bot will be sent. Needs to match the domain name of service and the route configured in the controllers.
|
||||
/// For example "https://testservice.azurewebsites.net/api/calling/callback"
|
||||
/// </summary>
|
||||
Uri CallbackUrl { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Url where notifications to the bot will be sent. Needs to match the domain name of service and the route configured in the controllers.
|
||||
/// For example "https://testservice.azurewebsites.net/api/calling/notification"
|
||||
/// </summary>
|
||||
Uri NotificationUrl { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling
|
||||
{
|
||||
internal class LoggingMessageHandler : DelegatingHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Log the request and response.
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
string requestUriText = request.RequestUri.ToString();
|
||||
string requestHeadersText = GetHeadersText(request.Headers);
|
||||
string requestBodyText = await GetBodyText(request.Content).ConfigureAwait(false);
|
||||
|
||||
Trace.TraceInformation($"Method: {request.Method.ToString()}, Uri {requestUriText}, Headers { requestHeadersText}, Body { requestBodyText}");
|
||||
HttpResponseMessage response = await SendAndLogAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
string responseHeadersText = GetHeadersText(response.Headers);
|
||||
|
||||
if (response.Content != null)
|
||||
{
|
||||
responseHeadersText =
|
||||
String.Join(
|
||||
Environment.NewLine,
|
||||
responseHeadersText,
|
||||
GetHeadersText(response.Content.Headers));
|
||||
}
|
||||
|
||||
string responseBodyText = await GetBodyText(response.Content).ConfigureAwait(false);
|
||||
Trace.TraceInformation($"Response: {responseBodyText}, Headers {responseHeadersText}");
|
||||
return response;
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage> SendAndLogAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Trace.TraceError("Exception occurred when calling SendAsync: {0}", e.ToString());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetHeadersText(HttpHeaders headers)
|
||||
{
|
||||
if (headers == null || !headers.Any())
|
||||
{
|
||||
return String.Empty;
|
||||
}
|
||||
|
||||
List<string> headerTexts = new List<string>();
|
||||
|
||||
foreach (KeyValuePair<string, IEnumerable<string>> h in headers)
|
||||
{
|
||||
headerTexts.Add(GetHeaderText(h));
|
||||
}
|
||||
|
||||
return String.Join(Environment.NewLine, headerTexts);
|
||||
}
|
||||
|
||||
private static string GetHeaderText(KeyValuePair<string, IEnumerable<string>> header)
|
||||
{
|
||||
return String.Format("{0}: {1}", header.Key, String.Join(",", header.Value));
|
||||
}
|
||||
|
||||
public static async Task<string> GetBodyText(HttpContent content)
|
||||
{
|
||||
if (content == null)
|
||||
{
|
||||
return "(empty body)";
|
||||
}
|
||||
string body = await content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
|
||||
string formattedJsonBody;
|
||||
if (TryFormatJsonBody(body, out formattedJsonBody))
|
||||
{
|
||||
body = formattedJsonBody;
|
||||
}
|
||||
|
||||
if (String.IsNullOrWhiteSpace(body))
|
||||
{
|
||||
return "(empty body)";
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
private static bool TryFormatJsonBody(string body, out string jsonBody)
|
||||
{
|
||||
jsonBody = null;
|
||||
|
||||
if (String.IsNullOrWhiteSpace(body))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
object parsedObject = JsonConvert.DeserializeObject(body);
|
||||
|
||||
if (parsedObject == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
jsonBody = JsonConvert.SerializeObject(parsedObject, Formatting.Indented);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{F1BFB900-F608-44BA-8A04-FDF5EC27EC1C}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Microsoft.Bot.Builder.RealTimeMediaCalling</RootNamespace>
|
||||
<AssemblyName>Microsoft.Bot.Builder.RealTimeMediaCalling</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<DocumentationFile>bin\Debug\Microsoft.Bot.Builder.RealTimeMediaCalling.XML</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<DocumentationFile>bin\Release\Microsoft.Bot.Builder.RealTimeMediaCalling.XML</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Autofac, Version=3.5.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
|
||||
<HintPath>packages\Autofac.3.5.2\lib\net40\Autofac.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Chronic, Version=0.3.2.0, Culture=neutral, PublicKeyToken=3bd1f1ef638b0d3c, processorArchitecture=MSIL">
|
||||
<HintPath>packages\Chronic.Signed.0.3.2\lib\net40\Chronic.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Bot.Builder, Version=3.5.8.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>packages\Microsoft.Bot.Builder.3.5.8.0\lib\net46\Microsoft.Bot.Builder.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Bot.Builder.Autofac, Version=3.5.8.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>packages\Microsoft.Bot.Builder.3.5.8.0\lib\net46\Microsoft.Bot.Builder.Autofac.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Bot.Builder.Calling, Version=3.0.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>packages\Microsoft.Bot.Builder.Calling.3.0.3\lib\net46\Microsoft.Bot.Builder.Calling.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Bot.Connector, Version=3.5.8.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>packages\Microsoft.Bot.Builder.3.5.8.0\lib\net46\Microsoft.Bot.Connector.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.IdentityModel.Protocol.Extensions, Version=1.0.2.33, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>packages\Microsoft.IdentityModel.Protocol.Extensions.1.0.2.206221351\lib\net45\Microsoft.IdentityModel.Protocol.Extensions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Rest.ClientRuntime, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>packages\Microsoft.Rest.ClientRuntime.2.3.2\lib\net45\Microsoft.Rest.ClientRuntime.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.IdentityModel.Tokens.Jwt, Version=4.0.20622.1351, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.IdentityModel.Tokens.Jwt.4.0.2.206221351\lib\net45\System.IdentityModel.Tokens.Jwt.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Net" />
|
||||
<Reference Include="System.Net.Http.Formatting, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Net.Http.WebRequest" />
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.Web.Http, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="IRealTimeMediaCall.cs" />
|
||||
<Compile Include="IRealTimeMediaCallProcessor.cs" />
|
||||
<Compile Include="LoggingMessageHandler.cs" />
|
||||
<Compile Include="RetryMessageHandler.cs" />
|
||||
<Compile Include="RealTimeMediaCallingContext.cs" />
|
||||
<Compile Include="Events\AnswerAppHostedMediaOutcomeEvent.cs" />
|
||||
<Compile Include="Events\RealTimeMediaIncomingCallEvent.cs" />
|
||||
<Compile Include="Events\RealTimeMediaWorkflowValidationOutcomeEvent.cs" />
|
||||
<Compile Include="IRealTimeMediaCallService.cs" />
|
||||
<Compile Include="Models\Contracts\RealTimeMediaActionConverter.cs" />
|
||||
<Compile Include="Models\Contracts\AnswerAppHostedMedia.cs" />
|
||||
<Compile Include="Models\Contracts\AnswerAppHostedMediaOutcome.cs" />
|
||||
<Compile Include="Models\Contracts\CallStateChangeNotification.cs" />
|
||||
<Compile Include="Models\Contracts\NotificationBase.cs" />
|
||||
<Compile Include="Models\Contracts\NotificationConverter.cs" />
|
||||
<Compile Include="Models\Contracts\NotificationResponse.cs" />
|
||||
<Compile Include="Models\Contracts\RealTimeMediaOperationOutcomeConverter.cs" />
|
||||
<Compile Include="Models\Contracts\RosterParticipant.cs" />
|
||||
<Compile Include="Models\Contracts\RosterUpdateNotification.cs" />
|
||||
<Compile Include="Models\Contracts\RealTimeMediaValidActions.cs" />
|
||||
<Compile Include="Models\Contracts\RealTimeMediaValidOutcomes.cs" />
|
||||
<Compile Include="Models\Contracts\VideoSubscription.cs" />
|
||||
<Compile Include="Models\Contracts\VideoSubscriptionOutcome.cs" />
|
||||
<Compile Include="Models\Contracts\RealTimeMediaWorkflow.cs" />
|
||||
<Compile Include="Models\Misc\NotificationType.cs" />
|
||||
<Compile Include="Models\Misc\RealTimeMediaSerializer.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="IRealTimeMediaCallingBotServiceSettings.cs" />
|
||||
<Compile Include="RealTimeMediaCalling.cs" />
|
||||
<Compile Include="RealTimeMediaCallRequestType.cs" />
|
||||
<Compile Include="RealTimeMediaCallingModule.cs" />
|
||||
<Compile Include="RealTimeMediaCallProcessor.cs" />
|
||||
<Compile Include="RealTimeMediaCallService.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
<None Include="createpackage.cmd" />
|
||||
<None Include="Microsoft.Bot.Builder.RealTimeMediaCalling.nuspec" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0"?>
|
||||
<package xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||
<metadata xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<id>Microsoft.Bot.Builder.RealTimeMediaCalling</id>
|
||||
<version>1.0.2-alpha</version>
|
||||
<authors>Microsoft</authors>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<description>
|
||||
Microsoft Bot Builder extension for Skype Real Time Media Calling API. It provides the required constructs to extend your bot to access media for incoming calls from Skype.
|
||||
</description>
|
||||
<projectUrl>https://github.com/Microsoft/BotBuilder-RealTimeMediaCalling</projectUrl>
|
||||
<iconUrl>http://docs.botframework.com/images/bot_icon.png</iconUrl>
|
||||
<summary>A Microsoft Bot Builder extension for Skype Real Time Media Calling API.</summary>
|
||||
<language>en-US</language>
|
||||
<dependencies>
|
||||
<dependency id="Microsoft.Bot.Builder.Calling" version="3.0.3" />
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="bin\release\Microsoft.Bot.Builder.RealTimeMediaCalling.dll" target="lib\net46" />
|
||||
<file src="bin\release\Microsoft.Bot.Builder.RealTimeMediaCalling.xml" target="lib\net46" />
|
||||
<file src="bin\release\Microsoft.Bot.Builder.RealTimeMediaCalling.pdb" target="lib\net46" />
|
||||
</files>
|
||||
</package>
|
|
@ -0,0 +1,22 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.25420.1
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Bot.Builder.RealTimeMediaCalling", "Microsoft.Bot.Builder.RealTimeMediaCalling.csproj", "{F1BFB900-F608-44BA-8A04-FDF5EC27EC1C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{F1BFB900-F608-44BA-8A04-FDF5EC27EC1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F1BFB900-F608-44BA-8A04-FDF5EC27EC1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F1BFB900-F608-44BA-8A04-FDF5EC27EC1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F1BFB900-F608-44BA-8A04-FDF5EC27EC1C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,75 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Contracts;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Misc;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// This is the action which customers can specify to indicate that the platform should accept the call but that the
|
||||
/// bot will host the media.
|
||||
/// </summary>
|
||||
[JsonObject(MemberSerialization.OptOut)]
|
||||
public class AnswerAppHostedMedia : ActionBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Create AnswerAppHostedMedia action as a standalone action
|
||||
/// </summary>
|
||||
public AnswerAppHostedMedia()
|
||||
: base(isStandaloneAction: true)
|
||||
{
|
||||
this.Action = RealTimeMediaValidActions.AnswerAppHostedMediaAction;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opaque object to pass media configuration from the bot to the ExecutionAgent.
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public JObject MediaConfiguration { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Validate the action.
|
||||
/// </summary>
|
||||
public override void Validate()
|
||||
{
|
||||
base.Validate();
|
||||
Utils.AssertArgument(this.Action == RealTimeMediaValidActions.AnswerAppHostedMediaAction, "Action was not AnswerAppHostedMedia");
|
||||
Utils.AssertArgument(this.MediaConfiguration != null, "MediaConfiguration must not be null.");
|
||||
Utils.AssertArgument(this.MediaConfiguration.ToString().Length <= MaxValues.MediaConfigurationLength, "MediaConfiguration must serialize to less than or equal to {0} characters.", MaxValues.MediaConfigurationLength);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Contracts;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// This is the outcome of the "answerAppHostedMedia" action. This is conveyed to the customer as POST to the customer CallBack Url.
|
||||
/// </summary>
|
||||
[JsonObject(MemberSerialization.OptOut)]
|
||||
public class AnswerAppHostedMediaOutcome : OperationOutcomeBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Create the outcome of AnswerAppHostedMedia action
|
||||
/// </summary>
|
||||
public AnswerAppHostedMediaOutcome()
|
||||
{
|
||||
this.Type = RealTimeMediaValidOutcomes.AnswerAppHostedMediaOutcome;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Misc;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// This concrete class defines the call state change notification schema.
|
||||
/// </summary>
|
||||
[JsonObject(MemberSerialization.OptOut)]
|
||||
public class CallStateChangeNotification : NotificationBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Call state types that will be used as part of call state change notification to the app.
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public CallState CurrentState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional code explaining reason for call state change.
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Default)]
|
||||
public string StateChangeCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create notification for call state changes.
|
||||
/// </summary>
|
||||
public CallStateChangeNotification()
|
||||
{
|
||||
this.Type = NotificationType.CallStateChange;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Misc;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// This base class defines a subset of properties which define a notification.
|
||||
/// CallStateNotification and RosterUpdates are examples of Notifications.
|
||||
/// </summary>
|
||||
[JsonObject(MemberSerialization.OptOut)]
|
||||
public abstract class NotificationBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Unique identifier for call leg.
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always, Order = -2)]
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Type of Notification
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public NotificationType Type { get; set; }
|
||||
|
||||
[JsonExtensionData]
|
||||
private IDictionary<string, JToken> _additionalData;
|
||||
|
||||
/// <summary>
|
||||
/// Extension data for the fields that are not part of schema.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public IDictionary<string, JToken> AdditionalData
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_additionalData == null)
|
||||
{
|
||||
_additionalData = new Dictionary<string, JToken>();
|
||||
}
|
||||
|
||||
return _additionalData;
|
||||
}
|
||||
set
|
||||
{
|
||||
_additionalData = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validation method to verify notification is well formed.
|
||||
/// </summary>
|
||||
public virtual void Validate()
|
||||
{
|
||||
Utils.AssertArgument(!String.IsNullOrWhiteSpace(this.Id), "Id cannot be null or empty");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Misc;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// By default Json.net doesn't know how to deserialize JSON data into Interfaces or abstract classes.
|
||||
/// This custom Converter helps deserialize "Notifications" specified in JSON into respective concrete "Notification" classes.
|
||||
/// </summary>
|
||||
internal class NotificationConverter : JsonCreationConverter<NotificationBase>
|
||||
{
|
||||
protected override NotificationBase Create(Type objectType, JObject jsonObject)
|
||||
{
|
||||
var type = (string)jsonObject.Property("type");
|
||||
if (String.Equals(type, NotificationType.RosterUpdate.ToString(), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new RosterUpdateNotification();
|
||||
}
|
||||
else if (String.Equals(type, NotificationType.CallStateChange.ToString(), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new CallStateChangeNotification();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException(String.Format("The given notification type '{0}' is not supported!", type));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Contracts;
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Misc;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// This class contains the response the customer sent for the notification POST to their callback url.
|
||||
/// </summary>
|
||||
[JsonObject(MemberSerialization.OptOut)]
|
||||
public class NotificationResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Callback link to call back the customer on, once we have processed the notification response from customer.
|
||||
///
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Default)]
|
||||
public CallbackLink Links { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Opaque string to facilitate app developers to pass their custom data in this field.
|
||||
/// This field is echo'd back in the 'result' POST for this 'response'.
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Default)]
|
||||
public string AppState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Validate the NotificationResponse
|
||||
/// </summary>
|
||||
public virtual void Validate()
|
||||
{
|
||||
if (this.Links != null)
|
||||
{
|
||||
Utils.AssertArgument(this.Links.Callback != null, "Callback link cannot be specified as null");
|
||||
Utils.AssertArgument(this.Links.Callback.IsAbsoluteUri, "Callback link must be an absolute uri");
|
||||
}
|
||||
ApplicationState.Validate(this.AppState);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Contracts;
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Misc;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// By default Json.net doesn't know how to deserialize JSON data into Interfaces or abstract classes.
|
||||
/// This custom Converter helps deserialize "actions" specified in JSON into respective concrete "action" classes.
|
||||
/// </summary>
|
||||
internal class RealTimeMediaActionConverter : JsonCreationConverter<ActionBase>
|
||||
{
|
||||
protected override ActionBase Create(Type objectType, JObject jsonObject)
|
||||
{
|
||||
var actionProperties = jsonObject.Properties().Where(p => p != null && p.Name != null && String.Equals(p.Name, "action", StringComparison.OrdinalIgnoreCase));
|
||||
string type = null;
|
||||
|
||||
if (actionProperties.Count() == 1)
|
||||
{
|
||||
type = (string)actionProperties.First();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException(String.Format("Expected single action."));
|
||||
}
|
||||
|
||||
if (String.Equals(type, RealTimeMediaValidActions.AnswerAppHostedMediaAction, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new AnswerAppHostedMedia();
|
||||
}
|
||||
else if (String.Equals(type, RealTimeMediaValidActions.VideoSubscriptionAction, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new VideoSubscription();
|
||||
}
|
||||
|
||||
throw new ArgumentException(String.Format("The given action '{0}' is not supported!", type));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Contracts;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// By default Json.net doesn't know how to deserialize JSON data into Interfaces or abstract classes.
|
||||
/// This custom Converter helps deserialize "operationOutcomes" specified in JSON into respective concrete "OperationOutcome" classes.
|
||||
/// </summary>
|
||||
internal class RealTimeMediaOperationOutcomeConverter : OperationOutcomeConverter
|
||||
{
|
||||
static RealTimeMediaOperationOutcomeConverter()
|
||||
{
|
||||
RealTimeMediaValidOutcomes.Initialize();
|
||||
}
|
||||
|
||||
protected override OperationOutcomeBase Create(Type objectType, JObject jsonObject)
|
||||
{
|
||||
var type = (string)jsonObject.Property("type");
|
||||
if (String.Equals(type, RealTimeMediaValidOutcomes.AnswerAppHostedMediaOutcome, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new AnswerAppHostedMediaOutcome();
|
||||
}
|
||||
else if (String.Equals(type, RealTimeMediaValidOutcomes.VideoSubscriptionOutcome, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new VideoSubscriptionOutcome();
|
||||
}
|
||||
|
||||
return base.Create(objectType, jsonObject);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Misc;
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Contracts;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// This is a helper class for validating actions specified by customers
|
||||
/// </summary>
|
||||
internal static class RealTimeMediaValidActions
|
||||
{
|
||||
/// <summary>
|
||||
/// AnswerAppHostedMediaAction
|
||||
/// </summary>
|
||||
public const string AnswerAppHostedMediaAction = "answerAppHostedMedia";
|
||||
|
||||
/// <summary>
|
||||
/// VideoSubscription
|
||||
/// </summary>
|
||||
public const string VideoSubscriptionAction = "videoSubscription";
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary of valid actions and their relative order
|
||||
/// +ve order reflect operations after and including call acceptance
|
||||
/// -ve order reflect operations pre-call answering . ex: reject/redirect/sequentialRing
|
||||
/// </summary>
|
||||
private readonly static Dictionary<string, int> actionOrder = new Dictionary<string, int>()
|
||||
{
|
||||
{AnswerAppHostedMediaAction, 1},
|
||||
{VideoSubscriptionAction, 1},
|
||||
};
|
||||
|
||||
private static readonly string[] exclusiveActions = new string[]
|
||||
{
|
||||
AnswerAppHostedMediaAction
|
||||
};
|
||||
|
||||
private static bool IsValidAction(string action)
|
||||
{
|
||||
return actionOrder.ContainsKey(action);
|
||||
}
|
||||
|
||||
private static bool IsExclusiveAction(string action)
|
||||
{
|
||||
return exclusiveActions.Contains(action);
|
||||
}
|
||||
|
||||
public static void Validate(string action)
|
||||
{
|
||||
Utils.AssertArgument(!String.IsNullOrWhiteSpace(action), "Action Name cannot be null or empty");
|
||||
Utils.AssertArgument(RealTimeMediaValidActions.IsValidAction(action), "{0} is not a valid action", action);
|
||||
}
|
||||
|
||||
public static void Validate(IEnumerable<ActionBase> actions)
|
||||
{
|
||||
Utils.AssertArgument(actions != null, "Null Actions List not allowed");
|
||||
ActionBase[] actionsToBeValidated = actions.ToArray();
|
||||
Utils.AssertArgument(actionsToBeValidated.Length > 0, "Empty Actions List not allowed");
|
||||
|
||||
if (actionsToBeValidated.Length > 1 && actionsToBeValidated.Any((a) => { return a.IsStandaloneAction; }))
|
||||
{
|
||||
Utils.AssertArgument(
|
||||
false,
|
||||
"The stand-alone action '{0}' cannot be specified with any other actions",
|
||||
(actionsToBeValidated.FirstOrDefault((a) => { return a.IsStandaloneAction; })).Action);
|
||||
}
|
||||
|
||||
// Validate each action is correct.
|
||||
for (int i = 0; i < actionsToBeValidated.Length; ++i)
|
||||
{
|
||||
Utils.AssertArgument(actionsToBeValidated[i] != null, "action {0} cannot be null", i);
|
||||
RealTimeMediaValidActions.Validate(actionsToBeValidated[i].Action);
|
||||
actionsToBeValidated[i].Validate();
|
||||
}
|
||||
|
||||
// Ensure that actions are not duplicated
|
||||
for (int i = 0; i < actionsToBeValidated.Length; ++i)
|
||||
{
|
||||
int actionCount = actionsToBeValidated.Where(a => a.Action == actionsToBeValidated[i].Action).Count();
|
||||
Utils.AssertArgument(actionCount <= 1, "Action {0} can not be specified multiple times in same workflow.", actionsToBeValidated[i].Action);
|
||||
}
|
||||
|
||||
// Some actions (AnswerAppHostedMedia) cannot be combined in one workflow.
|
||||
var exclusiveActions = actionsToBeValidated.Where(a => RealTimeMediaValidActions.IsExclusiveAction(a.Action)).ToArray();
|
||||
|
||||
if (exclusiveActions.Count() > 1)
|
||||
{
|
||||
Utils.AssertArgument(false, "Action {0} can not be specified with action {1}.", exclusiveActions[0].Action, exclusiveActions[1].Action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Contracts;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// This is a helper class for validating outcomes. This can be used by customers or by us (before we send the outcome on the wire)
|
||||
/// </summary>
|
||||
public static class RealTimeMediaValidOutcomes
|
||||
{
|
||||
/// <summary>
|
||||
/// AnswerAppHostedMediaOutcome
|
||||
/// </summary>
|
||||
public const string AnswerAppHostedMediaOutcome = "answerAppHostedMediaOutcome";
|
||||
|
||||
/// <summary>
|
||||
/// VideoSubscriptionOutcome
|
||||
/// </summary>
|
||||
public const string VideoSubscriptionOutcome = "videoSubscriptionOutcome";
|
||||
|
||||
internal static void Initialize()
|
||||
{
|
||||
ValidOutcomes.Outcomes.Add(AnswerAppHostedMediaOutcome);
|
||||
ValidOutcomes.Outcomes.Add(VideoSubscriptionOutcome);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Contracts;
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Misc;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// This class contains the workflow the customer sent for the OnInComingCall POST or any subsequent POST to their callback url.
|
||||
/// Basically this workflow defines the set of actions, the customer wants us to perform and then callback to them.
|
||||
/// </summary>
|
||||
[JsonObject(MemberSerialization.OptOut)]
|
||||
public class RealTimeMediaWorkflow : Workflow
|
||||
{
|
||||
/// <summary>
|
||||
/// This element indicates that application wants to receive notification updates.
|
||||
/// Call state notifications are added to this list by default and cannot be unsubscribed to.
|
||||
/// Subscriptions to rosterUpdate are only used for multiParty calls.
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Default)]
|
||||
public IEnumerable<NotificationType> NotificationSubscriptions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Validate the Workflow.
|
||||
/// </summary>
|
||||
/// <param name="expectEmptyActions"></param>
|
||||
public override void Validate(bool expectEmptyActions)
|
||||
{
|
||||
if (expectEmptyActions)
|
||||
{
|
||||
Utils.AssertArgument(this.Actions == null || this.Actions.Count() == 0, "Actions must either be null or empty collection");
|
||||
}
|
||||
else
|
||||
{
|
||||
RealTimeMediaValidActions.Validate(this.Actions);
|
||||
}
|
||||
|
||||
if (this.Links != null)
|
||||
{
|
||||
if (this.Links.Notification != null)
|
||||
{
|
||||
// Notification link is optional. Notification link - if specified - must be absolute https uri.
|
||||
Utils.AssertArgument(this.Links.Notification.IsAbsoluteUri, "Notification link must be an absolute uri");
|
||||
Utils.AssertArgument(this.Links.Notification.Scheme == "https", "Notification link must be an secure https uri");
|
||||
Utils.AssertArgument(this.NotificationSubscriptions != null, "Notification subscriptions must be specified if notification link is given");
|
||||
}
|
||||
|
||||
Utils.AssertArgument(this.Links.Callback != null, "Callback link cannot be specified as null");
|
||||
Utils.AssertArgument(this.Links.Callback.IsAbsoluteUri, "Callback link must be an absolute uri");
|
||||
Utils.AssertArgument(this.Links.Callback.Scheme == "https", "Callback link must be an secure HTTPS uri");
|
||||
}
|
||||
|
||||
ApplicationState.Validate(this.AppState);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Misc;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// This class defines a participant object within a rosterUpdate message
|
||||
/// </summary>
|
||||
[JsonObject(MemberSerialization.OptOut)]
|
||||
public class RosterParticipant
|
||||
{
|
||||
/// <summary>
|
||||
/// MRI of the participant
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always, Order = -2)]
|
||||
public string Identity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Participant Media Type . ex : audio
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public ModalityType MediaType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Direction of media . ex : SendReceive
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public string MediaStreamDirection { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This is the "sourceId" of the mediaStream as represented in the roster internal wire protocol.
|
||||
/// This is in actuality an uint but for future simplicity in mind, we are using a string to allow other types.
|
||||
/// This field will have a valid unique value for audio, video and vbss modality.
|
||||
/// ContentSharing modality doesn't have a sourceId.
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Default)]
|
||||
public string MediaStreamId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if participant is an Attendee or a Presenter if a content sharing session is ongoing
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
public ContentSharingRole ContentSharingRole { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Conversation leg id of this participant in the call.
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Default)]
|
||||
public string LegId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Validate the RosterParticipant.
|
||||
/// </summary>
|
||||
public void Validate()
|
||||
{
|
||||
Utils.AssertArgument(!String.IsNullOrWhiteSpace(this.Identity), "Identity of participant must be specified");
|
||||
Utils.AssertArgument(!String.IsNullOrWhiteSpace(MediaStreamDirection), "MediaStreamDirection must be specified");
|
||||
}
|
||||
|
||||
internal static void Validate(IEnumerable<RosterParticipant> rosterParticipants)
|
||||
{
|
||||
Utils.AssertArgument(((rosterParticipants != null) && (rosterParticipants.Count<RosterParticipant>() > 0)), "Participant list cannot be null or empty");
|
||||
foreach (RosterParticipant participant in rosterParticipants)
|
||||
{
|
||||
Utils.AssertArgument(participant != null, "Participant cannot be null");
|
||||
participant.Validate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the Role the participant might be playing in a Content Sharing session
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(StringEnumConverterWithDefault<ContentSharingRole>))]
|
||||
public enum ContentSharingRole
|
||||
{
|
||||
/// <summary>
|
||||
/// The participant is not in any content sharing session
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// The participant is the presenter in a content sharing session
|
||||
/// </summary>
|
||||
Presenter,
|
||||
|
||||
/// <summary>
|
||||
/// The participant is an attendee in a content sharing session
|
||||
/// </summary>
|
||||
Attendee,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// This defines the set of the properties that define a rosterUpdate.
|
||||
/// </summary>
|
||||
[JsonObject(MemberSerialization.OptOut)]
|
||||
public class RosterUpdateNotification : NotificationBase
|
||||
{
|
||||
/// <summary>
|
||||
/// List of participants in the conversation
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public IEnumerable<RosterParticipant> Participants { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a notification for roster updates
|
||||
/// </summary>
|
||||
public RosterUpdateNotification()
|
||||
{
|
||||
this.Type = NotificationType.RosterUpdate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate the notification.
|
||||
/// </summary>
|
||||
public override void Validate()
|
||||
{
|
||||
base.Validate();
|
||||
RosterParticipant.Validate(this.Participants);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Contracts;
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Misc;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// This class defines the details needed to subscribe to a participant for a video channel
|
||||
/// </summary>
|
||||
[JsonObject(MemberSerialization.OptOut)]
|
||||
public class VideoSubscription : ActionBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Sequence ID of video socket. Index from 0-9 that is passed in the MediaConfiguration
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public uint SocketId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Identity of the participant whose video is pinned if VideoMode is set to controlled
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.AllowNull)]
|
||||
public string ParticipantIdentity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the video is from the camera or from screen sharing
|
||||
/// Unknown, Video and VideoBasedScreenSharing are supported modalities for this request
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Default)]
|
||||
public ModalityType VideoModality { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the video resolution format.Default value is "sd360p".
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Default)]
|
||||
public ResolutionFormat VideoResolution { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a VideoSubscription action.
|
||||
/// </summary>
|
||||
public VideoSubscription()
|
||||
{
|
||||
this.Action = RealTimeMediaValidActions.VideoSubscriptionAction;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate the action.
|
||||
/// </summary>
|
||||
public override void Validate()
|
||||
{
|
||||
base.Validate();
|
||||
Utils.AssertArgument(this.Action == RealTimeMediaValidActions.VideoSubscriptionAction, "Action was not VideoSubscription");
|
||||
Utils.AssertArgument(this.VideoModality != ModalityType.Audio, "Audio modality is not supported for this operation");
|
||||
Utils.AssertArgument(!String.IsNullOrWhiteSpace(this.ParticipantIdentity), "Participant identity cannot be null or empty");
|
||||
Utils.AssertArgument(VideoModality != ModalityType.Unknown, "VideoModality cannot be unknown");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Contracts;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// This is the outcome of the "videoSubscription" action.
|
||||
/// </summary>
|
||||
[JsonObject(MemberSerialization.OptOut)]
|
||||
public class VideoSubscriptionOutcome : OperationOutcomeBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates outcome of type VideoSubscriptionOutcome.
|
||||
/// </summary>
|
||||
public VideoSubscriptionOutcome()
|
||||
{
|
||||
this.Type = RealTimeMediaValidOutcomes.VideoSubscriptionOutcome;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Misc;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// List of various notification types
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(StringEnumConverterWithDefault<NotificationType>))]
|
||||
public enum NotificationType
|
||||
{
|
||||
/// <summary>
|
||||
/// Not recognized notification type.
|
||||
/// </summary>
|
||||
Unknown,
|
||||
|
||||
/// <summary>
|
||||
/// Roster Update Notification
|
||||
/// </summary>
|
||||
RosterUpdate,
|
||||
|
||||
/// <summary>
|
||||
/// Call State change notification
|
||||
/// </summary>
|
||||
CallStateChange
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Contracts;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Misc
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class for serializing/deserializing
|
||||
/// </summary>
|
||||
public static class RealTimeMediaSerializer
|
||||
{
|
||||
private static readonly JsonSerializerSettings defaultSerializerSettings = GetSerializerSettings();
|
||||
private static readonly JsonSerializerSettings loggingSerializerSettings = GetSerializerSettings(Formatting.Indented);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize input object to string
|
||||
/// </summary>
|
||||
public static string SerializeToJson(object obj, bool forLogging = false)
|
||||
{
|
||||
return JsonConvert.SerializeObject(obj, forLogging ? loggingSerializerSettings : defaultSerializerSettings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize to JToken
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static JToken SerializeToJToken(Object obj)
|
||||
{
|
||||
return JToken.FromObject(obj, JsonSerializer.Create(defaultSerializerSettings));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserialize input string to object
|
||||
/// </summary>
|
||||
public static T DeserializeFromJson<T>(string json)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(json, defaultSerializerSettings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserialize from JToken
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="jToken"></param>
|
||||
/// <returns></returns>
|
||||
public static T DeserializeFromJToken<T>(JToken jToken)
|
||||
{
|
||||
return jToken.ToObject<T>(JsonSerializer.Create(defaultSerializerSettings));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns default serializer settings.
|
||||
/// </summary>
|
||||
public static JsonSerializerSettings GetSerializerSettings(Formatting formatting = Formatting.None)
|
||||
{
|
||||
return new JsonSerializerSettings()
|
||||
{
|
||||
Formatting = formatting,
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver(),
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
DateFormatHandling = DateFormatHandling.IsoDateFormat,
|
||||
DateTimeZoneHandling = DateTimeZoneHandling.Utc,
|
||||
ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
|
||||
Converters = new List<JsonConverter> { new StringEnumConverter { CamelCaseText = true }, new Contracts.RealTimeMediaActionConverter(), new RealTimeMediaOperationOutcomeConverter(), new NotificationConverter() },
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Microsoft.Bot.Builder.RealTimeMediaCalling")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Microsoft.Bot.Builder.RealTimeMediaCalling")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2017")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("f1bfb900-f608-44ba-8a04-fdf5ec27ec1c")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.2.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.2.0")]
|
|
@ -0,0 +1,210 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder.Calling.Exceptions;
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Contracts;
|
||||
using Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Contracts;
|
||||
using Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Misc;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling
|
||||
{
|
||||
/// <summary>
|
||||
/// Processes the incoming requests and invokes the appropriate handlers for the call
|
||||
/// </summary>
|
||||
public class RealTimeCallProcessor : IRealTimeCallProcessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Container for the current active calls on this instance.
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, RealTimeMediaCallService> _activeCalls;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration settings
|
||||
/// </summary>
|
||||
IRealTimeMediaCallServiceSettings _settings;
|
||||
|
||||
/// <summary>
|
||||
/// Function to create a bot to deliver events
|
||||
/// </summary>
|
||||
Func<IRealTimeMediaCallService, IRealTimeMediaCall> _makeBot;
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates the call processor
|
||||
/// </summary>
|
||||
/// <param name="settings">Configuration settings</param>
|
||||
/// <param name="makeBot">Function to create a bot</param>
|
||||
public RealTimeCallProcessor(IRealTimeMediaCallServiceSettings settings, Func<IRealTimeMediaCallService, IRealTimeMediaCall> makeBot)
|
||||
{
|
||||
if (settings == null)
|
||||
throw new ArgumentNullException(nameof(settings));
|
||||
|
||||
_settings = settings;
|
||||
_makeBot = makeBot;
|
||||
_activeCalls = new ConcurrentDictionary<string, RealTimeMediaCallService>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method responsible for processing the data sent with POST request to incoming call URL
|
||||
/// </summary>
|
||||
/// <param name="content">The content of request</param>
|
||||
/// <param name="skypeChainId">X-Microsoft-Skype-Chain-Id header value used to associate calls across different services</param>
|
||||
/// <returns>Returns the response that should be sent to the sender of POST request</returns>
|
||||
public async Task<ResponseResult> ProcessIncomingCallAsync(string content, string skypeChainId)
|
||||
{
|
||||
if (content == null)
|
||||
{
|
||||
return new ResponseResult(ResponseType.BadRequest);
|
||||
}
|
||||
|
||||
Conversation conversation;
|
||||
try
|
||||
{
|
||||
conversation = RealTimeMediaSerializer.DeserializeFromJson<Conversation>(content);
|
||||
if (conversation == null)
|
||||
{
|
||||
Trace.TraceWarning($"Could not deserialize the incoming request.. returning badrequest");
|
||||
return new ResponseResult(ResponseType.BadRequest);
|
||||
}
|
||||
|
||||
conversation.Validate();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.TraceWarning($"Exception in conversation validate {ex}");
|
||||
return new ResponseResult(ResponseType.BadRequest);
|
||||
}
|
||||
|
||||
RealTimeMediaCallService service = new RealTimeMediaCallService(conversation.Id, skypeChainId, _makeBot, _settings);
|
||||
var workflow = await service.HandleIncomingCall(conversation).ConfigureAwait(false);
|
||||
if (workflow == null)
|
||||
{
|
||||
throw new BotCallingServiceException("Incoming call not handled. No workflow produced for incoming call.");
|
||||
}
|
||||
workflow.Validate();
|
||||
|
||||
RealTimeMediaCallService prevService;
|
||||
if (_activeCalls.TryRemove(conversation.Id, out prevService))
|
||||
{
|
||||
Trace.TraceWarning($"Another call with the same Id {conversation.Id} exists. ending the old call");
|
||||
await prevService.LocalCleanup().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
_activeCalls[conversation.Id] = service;
|
||||
|
||||
var serializedResponse = RealTimeMediaSerializer.SerializeToJson(workflow);
|
||||
return new ResponseResult(ResponseType.Accepted, serializedResponse);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method responsible for processing the data sent with POST request to callback URL
|
||||
/// </summary>
|
||||
/// <param name="content">The content of request</param>
|
||||
/// <returns>Returns the response that should be sent to the sender of POST request</returns>
|
||||
public async Task<ResponseResult> ProcessCallbackAsync(string content)
|
||||
{
|
||||
ConversationResult conversationResult;
|
||||
if (content == null)
|
||||
{
|
||||
return new ResponseResult(ResponseType.BadRequest);
|
||||
}
|
||||
try
|
||||
{
|
||||
conversationResult = RealTimeMediaSerializer.DeserializeFromJson<ConversationResult>(content);
|
||||
if (conversationResult == null)
|
||||
{
|
||||
Trace.TraceWarning($"Could not deserialize the callback.. returning badrequest");
|
||||
return new ResponseResult(ResponseType.BadRequest);
|
||||
}
|
||||
|
||||
conversationResult.Validate();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.TraceWarning($"Exception in conversationResult validate {ex}");
|
||||
return new ResponseResult(ResponseType.BadRequest);
|
||||
}
|
||||
|
||||
RealTimeMediaCallService service;
|
||||
if (!_activeCalls.TryGetValue(conversationResult.Id, out service))
|
||||
{
|
||||
Trace.TraceWarning($"CallId {conversationResult.Id} not found");
|
||||
return new ResponseResult(ResponseType.NotFound);
|
||||
}
|
||||
return new ResponseResult(ResponseType.Accepted, await service.ProcessConversationResult(conversationResult).ConfigureAwait(false));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method responsible for processing the data sent with POST request to notification URL
|
||||
/// </summary>
|
||||
/// <param name="content">The content of request</param>
|
||||
/// <returns>Returns the response that should be sent to the sender of POST request</returns>
|
||||
public async Task<ResponseResult> ProcessNotificationAsync(string content)
|
||||
{
|
||||
NotificationBase notification;
|
||||
if (content == null)
|
||||
{
|
||||
return new ResponseResult(ResponseType.BadRequest);
|
||||
}
|
||||
try
|
||||
{
|
||||
Trace.TraceWarning($"Received notification {content}");
|
||||
notification = RealTimeMediaSerializer.DeserializeFromJson<NotificationBase>(content);
|
||||
if (notification == null)
|
||||
{
|
||||
Trace.TraceWarning($"Could not deserialize the notification.. returning badrequest");
|
||||
return new ResponseResult(ResponseType.BadRequest);
|
||||
}
|
||||
|
||||
notification.Validate();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.TraceWarning($"Exception in notification validate {ex}");
|
||||
return new ResponseResult(ResponseType.BadRequest);
|
||||
}
|
||||
|
||||
RealTimeMediaCallService service;
|
||||
if (!_activeCalls.TryGetValue(notification.Id, out service))
|
||||
{
|
||||
return new ResponseResult(ResponseType.NotFound, $"Call {notification.Id} not found");
|
||||
}
|
||||
|
||||
await service.ProcessNotificationResult(notification).ConfigureAwait(false);
|
||||
return new ResponseResult(ResponseType.Accepted);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of request to this bot
|
||||
/// </summary>
|
||||
public enum RealTimeMediaCallRequestType
|
||||
{
|
||||
/// <summary>
|
||||
/// A new call is received.
|
||||
/// </summary>
|
||||
IncomingCall,
|
||||
|
||||
/// <summary>
|
||||
/// Callback associated for the call is received.
|
||||
/// </summary>
|
||||
CallingEvent,
|
||||
|
||||
/// <summary>
|
||||
/// Notifications - like roster change/call status change for the call is received.
|
||||
/// </summary>
|
||||
NotificationEvent
|
||||
}
|
||||
}
|
|
@ -0,0 +1,435 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder.Calling.Events;
|
||||
using Microsoft.Bot.Builder.Calling.Exceptions;
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Contracts;
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Misc;
|
||||
using Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Misc;
|
||||
using Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Contracts;
|
||||
using Microsoft.Bot.Builder.RealTimeMediaCalling.Events;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling
|
||||
{
|
||||
/// <summary>
|
||||
/// Service that handles per call requests
|
||||
/// </summary>
|
||||
internal class RealTimeMediaCallService : IRealTimeMediaCallService
|
||||
{
|
||||
private readonly Uri _callbackUrl;
|
||||
private readonly Uri _notificationUrl;
|
||||
|
||||
private Uri _subscriptionLink;
|
||||
private Uri _callLink;
|
||||
|
||||
private IRealTimeMediaCall _bot;
|
||||
|
||||
private Timer _timer;
|
||||
private const int CallExpiredTimerInterval = 1000 * 60 * 10; //10 minutes
|
||||
|
||||
/// <summary>
|
||||
/// Id for this call
|
||||
/// </summary>
|
||||
public string CallLegId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// CorrelationId for this call.
|
||||
/// </summary>
|
||||
public string CorrelationId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when bot receives incoming call
|
||||
/// </summary>
|
||||
public event Func<RealTimeMediaIncomingCallEvent, Task> OnIncomingCallReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when the bot gets the outcome of Answer action. If the operation was successful the call is established
|
||||
/// </summary>
|
||||
public event Func<AnswerAppHostedMediaOutcomeEvent, Task> OnAnswerAppHostedMediaCompleted;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when specified workflow fails to be validated by Bot platform
|
||||
/// </summary>
|
||||
public event Func<RealTimeMediaWorkflowValidationOutcomeEvent, Task> OnWorkflowValidationFailed;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when bot receives call state change notification
|
||||
/// </summary>
|
||||
public event Func<CallStateChangeNotification, Task> OnCallStateChangeNotification;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when bot receives roster update notification
|
||||
/// </summary>
|
||||
public event Func<RosterUpdateNotification, Task> OnRosterUpdateNotification;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when bot needs to cleanup an existing call
|
||||
/// </summary>
|
||||
public event Func<Task> OnCallCleanup;
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates the service with settings to handle a call
|
||||
/// </summary>
|
||||
/// <param name="callId"></param>
|
||||
/// <param name="skypeChainId"></param>
|
||||
/// <param name="makeBot"></param>
|
||||
/// <param name="settings"></param>
|
||||
public RealTimeMediaCallService(string callId, string skypeChainId, Func<IRealTimeMediaCallService, IRealTimeMediaCall> makeBot, IRealTimeMediaCallServiceSettings settings)
|
||||
{
|
||||
if(string.IsNullOrEmpty(callId))
|
||||
{
|
||||
throw new ArgumentNullException("callId");
|
||||
}
|
||||
|
||||
if (makeBot == null)
|
||||
{
|
||||
throw new ArgumentNullException("makeBot");
|
||||
}
|
||||
|
||||
if (settings == null)
|
||||
{
|
||||
throw new ArgumentNullException("settings");
|
||||
}
|
||||
|
||||
if (settings.CallbackUrl == null || settings.NotificationUrl == null)
|
||||
{
|
||||
throw new ArgumentNullException("callback settings");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(skypeChainId))
|
||||
{
|
||||
CorrelationId = Guid.NewGuid().ToString();
|
||||
Trace.TraceInformation(
|
||||
$"RealTimeMediaCallService No SkypeChainId found. Generating {CorrelationId}");
|
||||
}
|
||||
else
|
||||
{
|
||||
CorrelationId = skypeChainId;
|
||||
}
|
||||
|
||||
CallLegId = callId;
|
||||
|
||||
_callbackUrl = settings.CallbackUrl;
|
||||
_notificationUrl = settings.NotificationUrl;
|
||||
_bot = makeBot(this);
|
||||
_timer = new Timer(CallExpiredTimerCallback, null, CallExpiredTimerInterval, Timeout.Infinite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Keeps track of receiving AnswerAppHostedMediaOutcome. If the answer does not come back, bot can start leaking sockets.
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
private void CallExpiredTimerCallback(object state)
|
||||
{
|
||||
Trace.TraceInformation(
|
||||
$"RealTimeMediaCallService [{CallLegId}]: CallExpiredTimerCallback called.. cleaning up the call");
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await LocalCleanup().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.TraceError(
|
||||
$"RealTimeMediaCallService [{CallLegId}]: Error in LocalCleanup {ex}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes notifications on the bot
|
||||
/// </summary>
|
||||
/// <param name="notification">Notification to be sent</param>
|
||||
/// <returns></returns>
|
||||
internal Task ProcessNotificationResult(NotificationBase notification)
|
||||
{
|
||||
Trace.TraceInformation(
|
||||
$"RealTimeMediaCallService [{CallLegId}]: Received the notification for {notification.Type} operation, callId: {notification.Id}");
|
||||
|
||||
switch (notification.Type)
|
||||
{
|
||||
case NotificationType.CallStateChange:
|
||||
return HandleCallStateChangeNotification(notification as CallStateChangeNotification);
|
||||
|
||||
case NotificationType.RosterUpdate:
|
||||
return HandleRosterUpdateNotification(notification as RosterUpdateNotification);
|
||||
}
|
||||
throw new BotCallingServiceException($"[{CallLegId}]: Unknown notification type {notification.Type}");
|
||||
}
|
||||
|
||||
private async Task HandleCallStateChangeNotification(CallStateChangeNotification notification)
|
||||
{
|
||||
Trace.TraceInformation($"RealTimeMediaCallService [{CallLegId}]: Received CallStateChangeNotification.. ");
|
||||
notification.Validate();
|
||||
|
||||
var eventHandler = OnCallStateChangeNotification;
|
||||
if (eventHandler != null)
|
||||
await eventHandler.Invoke(notification).ConfigureAwait(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
private async Task HandleRosterUpdateNotification(RosterUpdateNotification notification)
|
||||
{
|
||||
Trace.TraceInformation($"RealTimeMediaCallService [{CallLegId}]: Received RosterUpdateNotification");
|
||||
notification.Validate();
|
||||
|
||||
var eventHandler = OnRosterUpdateNotification;
|
||||
if (eventHandler != null)
|
||||
await eventHandler.Invoke(notification).ConfigureAwait(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes handlers for callback on the bot
|
||||
/// </summary>
|
||||
/// <param name="conversationResult">ConversationResult that has the details of the callback</param>
|
||||
/// <returns></returns>
|
||||
internal async Task<string> ProcessConversationResult(ConversationResult conversationResult)
|
||||
{
|
||||
conversationResult.Validate();
|
||||
var newWorkflowResult = await PassActionResultToHandler(conversationResult).ConfigureAwait(false);
|
||||
if (newWorkflowResult == null)
|
||||
{
|
||||
throw new BotCallingServiceException($"[{CallLegId}]: No workflow returned for AnswerAppHostedMediaOutcome");
|
||||
}
|
||||
|
||||
bool expectEmptyActions = false;
|
||||
if(conversationResult.OperationOutcome.Type == RealTimeMediaValidOutcomes.AnswerAppHostedMediaOutcome && conversationResult.OperationOutcome.Outcome == Outcome.Success)
|
||||
{
|
||||
Uri link;
|
||||
if (conversationResult.Links.TryGetValue("subscriptions", out link))
|
||||
{
|
||||
_subscriptionLink = link;
|
||||
Trace.TraceInformation($"RealTimeMediaCallService [{CallLegId}]: Caching subscription link {link}");
|
||||
}
|
||||
|
||||
if (conversationResult.Links.TryGetValue("call", out link))
|
||||
{
|
||||
_callLink = link;
|
||||
Trace.TraceInformation($"RealTimeMediaCallService [{CallLegId}]: Caching call link {link}");
|
||||
}
|
||||
expectEmptyActions = true;
|
||||
|
||||
Trace.TraceInformation($"RealTimeMediaCallService [{CallLegId}]: Disposing call expiry timer");
|
||||
_timer.Dispose();
|
||||
}
|
||||
|
||||
newWorkflowResult.Validate(expectEmptyActions);
|
||||
return RealTimeMediaSerializer.SerializeToJson(newWorkflowResult);
|
||||
}
|
||||
|
||||
private Task<RealTimeMediaWorkflow> PassActionResultToHandler(ConversationResult receivedConversationResult)
|
||||
{
|
||||
Trace.TraceInformation(
|
||||
$"RealTimeMediaCallService [{CallLegId}]: Received the outcome for {receivedConversationResult.OperationOutcome.Type} operation, callId: {receivedConversationResult.OperationOutcome.Id}");
|
||||
|
||||
switch (receivedConversationResult.OperationOutcome.Type)
|
||||
{
|
||||
case RealTimeMediaValidOutcomes.AnswerAppHostedMediaOutcome:
|
||||
return HandleAnswerAppHostedMediaOutcome(receivedConversationResult, receivedConversationResult.OperationOutcome as AnswerAppHostedMediaOutcome);
|
||||
|
||||
case ValidOutcomes.WorkflowValidationOutcome:
|
||||
return HandleWorkflowValidationOutcome(receivedConversationResult, receivedConversationResult.OperationOutcome as WorkflowValidationOutcome);
|
||||
}
|
||||
|
||||
throw new BotCallingServiceException($"[{CallLegId}]: Unknown conversation result type {receivedConversationResult.OperationOutcome.Type}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes handler for incoming call
|
||||
/// </summary>
|
||||
/// <param name="conversation">Conversation corresponding to the incoming call</param>
|
||||
/// <returns>WorkFlow to be executed for the call</returns>
|
||||
internal async Task<RealTimeMediaWorkflow> HandleIncomingCall(Conversation conversation)
|
||||
{
|
||||
Trace.TraceInformation($"RealTimeMediaCallService [{CallLegId}]: Received incoming call");
|
||||
var incomingCall = new RealTimeMediaIncomingCallEvent(conversation, CreateInitialWorkflow());
|
||||
var eventHandler = OnIncomingCallReceived;
|
||||
if (eventHandler != null)
|
||||
await eventHandler.Invoke(incomingCall).ConfigureAwait(false);
|
||||
else
|
||||
{
|
||||
Trace.TraceInformation($"RealTimeMediaCallService [{CallLegId}]: No handler specified for incoming call");
|
||||
return null;
|
||||
}
|
||||
|
||||
return incomingCall.RealTimeMediaWorkflow;
|
||||
}
|
||||
|
||||
private Task<RealTimeMediaWorkflow> HandleAnswerAppHostedMediaOutcome(ConversationResult conversationResult, AnswerAppHostedMediaOutcome answerAppHostedMediaOutcome)
|
||||
{
|
||||
var outcomeEvent = new AnswerAppHostedMediaOutcomeEvent(conversationResult, CreateInitialWorkflow(), answerAppHostedMediaOutcome);
|
||||
var eventHandler = OnAnswerAppHostedMediaCompleted;
|
||||
return InvokeHandlerIfSet(eventHandler, outcomeEvent);
|
||||
}
|
||||
|
||||
private Task<RealTimeMediaWorkflow> HandleWorkflowValidationOutcome(
|
||||
ConversationResult conversationResult,
|
||||
WorkflowValidationOutcome workflowValidationOutcome)
|
||||
{
|
||||
var outcomeEvent = new RealTimeMediaWorkflowValidationOutcomeEvent(conversationResult, CreateInitialWorkflow(), workflowValidationOutcome);
|
||||
var eventHandler = OnWorkflowValidationFailed;
|
||||
return InvokeHandlerIfSet(eventHandler, outcomeEvent);
|
||||
}
|
||||
|
||||
internal Task LocalCleanup()
|
||||
{
|
||||
var eventHandler = OnCallCleanup;
|
||||
return InvokeHandlerIfSet(eventHandler, "Cleanup");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to a video or video based screen sharing channel
|
||||
/// </summary>
|
||||
/// <param name="videoSubscription"></param>
|
||||
/// <returns></returns>
|
||||
public async Task Subscribe(VideoSubscription videoSubscription)
|
||||
{
|
||||
if (_subscriptionLink == null)
|
||||
{
|
||||
throw new InvalidOperationException($"[{CallLegId}]: No subscription link was present in the AnswerAppHostedMediaOutcome");
|
||||
}
|
||||
|
||||
videoSubscription.Validate();
|
||||
HttpContent content = new StringContent(RealTimeMediaSerializer.SerializeToJson(videoSubscription), Encoding.UTF8, JSONConstants.ContentType);
|
||||
|
||||
//Subscribe
|
||||
try
|
||||
{
|
||||
Trace.TraceInformation(
|
||||
$"RealTimeMediaCallService [{CallLegId}]: Sending subscribe request for " +
|
||||
$"user: {videoSubscription.ParticipantIdentity}" +
|
||||
$"subscriptionLink: {_subscriptionLink}");
|
||||
|
||||
//TODO: add retries & logging
|
||||
using (var request = new HttpRequestMessage(HttpMethod.Put, _subscriptionLink) { Content = content })
|
||||
{
|
||||
request.Headers.Add("X-Microsoft-Skype-Chain-ID", CorrelationId);
|
||||
request.Headers.Add("X-Microsoft-Skype-Message-ID", Guid.NewGuid().ToString());
|
||||
|
||||
var client = GetHttpClient();
|
||||
var response = await client.SendAsync(request).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
Trace.TraceInformation($"RealTimeMediaCallService [{CallLegId}]: Response to subscribe: {response}");
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Trace.TraceError($"RealTimeMediaCallService [{CallLegId}]: Received error while sending request to subscribe participant. Message: {exception}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static HttpClient GetHttpClient()
|
||||
{
|
||||
var clientHandler = new HttpClientHandler { AllowAutoRedirect = false };
|
||||
DelegatingHandler[] handlers = new DelegatingHandler[] { new RetryMessageHandler(), new LoggingMessageHandler() };
|
||||
HttpClient client = HttpClientFactory.Create(clientHandler, handlers);
|
||||
var assemblyVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("Microsoft-BotFramework-RealTimeMedia", assemblyVersion));
|
||||
return client;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends the call. Local cleanup will not be done
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task EndCall()
|
||||
{
|
||||
if (_callLink == null)
|
||||
{
|
||||
throw new InvalidOperationException($"[{CallLegId}]: No call link was present in the AnswerAppHostedMediaOutcome");
|
||||
}
|
||||
|
||||
using (var request = new HttpRequestMessage(HttpMethod.Delete, _callLink))
|
||||
{
|
||||
request.Headers.Add("X-Microsoft-Skype-Chain-ID", CorrelationId);
|
||||
request.Headers.Add("X-Microsoft-Skype-Message-ID", Guid.NewGuid().ToString());
|
||||
|
||||
var client = GetHttpClient();
|
||||
var response = await client.SendAsync(request).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
Trace.TraceInformation($"RealTimeMediaCallService [{CallLegId}]: Response to Delete: {response}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<RealTimeMediaWorkflow> InvokeHandlerIfSet<T>(Func<T, Task> action, T outcomeEventBase) where T : OutcomeEventBase
|
||||
{
|
||||
if (action != null)
|
||||
{
|
||||
await action.Invoke(outcomeEventBase).ConfigureAwait(false);
|
||||
return outcomeEventBase.ResultingWorkflow as RealTimeMediaWorkflow;
|
||||
}
|
||||
throw new BotCallingServiceException($"[{CallLegId}]: No event handler set for {outcomeEventBase.ConversationResult.OperationOutcome.Type} outcome");
|
||||
}
|
||||
|
||||
private async Task InvokeHandlerIfSet(Func<Task> action, string type)
|
||||
{
|
||||
if (action == null)
|
||||
{
|
||||
throw new BotCallingServiceException($"[{CallLegId}]: No event handler set for {type}");
|
||||
}
|
||||
|
||||
await action.Invoke().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private RealTimeMediaWorkflow CreateInitialWorkflow()
|
||||
{
|
||||
var workflow = new RealTimeMediaWorkflow();
|
||||
workflow.Links = GetCallbackLink();
|
||||
workflow.Actions = new List<ActionBase>();
|
||||
workflow.AppState = CallLegId;
|
||||
workflow.NotificationSubscriptions = new List<NotificationType>() { NotificationType.CallStateChange };
|
||||
return workflow;
|
||||
}
|
||||
|
||||
private CallbackLink GetCallbackLink()
|
||||
{
|
||||
return new CallbackLink() { Callback = _callbackUrl, Notification = _notificationUrl };
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Autofac;
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Misc;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling
|
||||
{
|
||||
/// <summary>
|
||||
/// The top level class that is used to register the bot for enabling real-time media communication
|
||||
/// </summary>
|
||||
public static class RealTimeMediaCalling
|
||||
{
|
||||
private static readonly IContainer Container;
|
||||
|
||||
static RealTimeMediaCalling()
|
||||
{
|
||||
var builder = new ContainerBuilder();
|
||||
builder.RegisterModule(new RealTimeMediaCallingModule_MakeBot());
|
||||
Container = builder.Build();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register the function to be called to create a bot along with configuration settings.
|
||||
/// </summary>
|
||||
/// <param name="makeCallingBot"> The factory method to make the real time media calling bot.</param>
|
||||
/// <param name="realTimeBotServiceSettings"> Configuration settings for the real time media calling bot.</param>
|
||||
public static void RegisterRealTimeMediaCallingBot(Func<IRealTimeMediaCallService, IRealTimeMediaCall> makeCallingBot, IRealTimeMediaCallServiceSettings realTimeBotServiceSettings)
|
||||
{
|
||||
Trace.TraceInformation($"Registering real-time media calling bot");
|
||||
if(realTimeBotServiceSettings.CallbackUrl == null)
|
||||
{
|
||||
throw new ArgumentNullException("callbackUrl");
|
||||
}
|
||||
|
||||
if (realTimeBotServiceSettings.NotificationUrl == null)
|
||||
{
|
||||
throw new ArgumentNullException("notificationUrl");
|
||||
}
|
||||
|
||||
RealTimeMediaCallingModule_MakeBot.Register(Container, makeCallingBot, realTimeBotServiceSettings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process an incoming request
|
||||
/// </summary>
|
||||
/// <param name="toBot"> The calling request sent to the bot.</param>
|
||||
/// <param name="callRequestType"> The type of calling request.</param>
|
||||
/// <returns> The response from the bot.</returns>
|
||||
public static async Task<HttpResponseMessage> SendAsync(HttpRequestMessage toBot, RealTimeMediaCallRequestType callRequestType)
|
||||
{
|
||||
using (var scope = RealTimeMediaCallingModule.BeginLifetimeScope(Container, toBot))
|
||||
{
|
||||
var context = scope.Resolve<RealTimeMediaCallingContext>();
|
||||
var parsedRequest = await context.ProcessRequest(callRequestType).ConfigureAwait(false);
|
||||
|
||||
if (parsedRequest.Faulted())
|
||||
{
|
||||
return GetResponseMessage(parsedRequest.ParseStatusCode, parsedRequest.Content);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
ResponseResult result;
|
||||
var callingBotService = scope.Resolve<IRealTimeCallProcessor>();
|
||||
switch (callRequestType)
|
||||
{
|
||||
case RealTimeMediaCallRequestType.IncomingCall:
|
||||
result = await callingBotService.ProcessIncomingCallAsync(parsedRequest.Content, parsedRequest.SkypeChainId).ConfigureAwait(false);
|
||||
break;
|
||||
|
||||
case RealTimeMediaCallRequestType.CallingEvent:
|
||||
result = await callingBotService.ProcessCallbackAsync(parsedRequest.Content).ConfigureAwait(false);
|
||||
break;
|
||||
|
||||
case RealTimeMediaCallRequestType.NotificationEvent:
|
||||
result = await callingBotService.ProcessNotificationAsync(parsedRequest.Content).ConfigureAwait(false);
|
||||
break;
|
||||
|
||||
default:
|
||||
result = new ResponseResult(ResponseType.BadRequest, $"Unsupported call request type: {callRequestType}");
|
||||
break;
|
||||
}
|
||||
|
||||
return GetHttpResponseForResult(result);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Trace.TraceError($"RealTimeMediaCallingConversation: {e}");
|
||||
return GetResponseMessage(HttpStatusCode.InternalServerError, e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static HttpResponseMessage GetResponseMessage(HttpStatusCode statusCode, string content)
|
||||
{
|
||||
HttpResponseMessage responseMessage = new HttpResponseMessage(statusCode);
|
||||
if (!string.IsNullOrEmpty(content))
|
||||
{
|
||||
responseMessage.Content = new StringContent(content);
|
||||
}
|
||||
return responseMessage;
|
||||
}
|
||||
|
||||
private static HttpResponseMessage GetHttpResponseForResult(ResponseResult result)
|
||||
{
|
||||
HttpResponseMessage responseMessage;
|
||||
switch(result.ResponseType)
|
||||
{
|
||||
case ResponseType.Accepted:
|
||||
responseMessage = new HttpResponseMessage(HttpStatusCode.Accepted);
|
||||
break;
|
||||
case ResponseType.BadRequest:
|
||||
responseMessage = new HttpResponseMessage(HttpStatusCode.BadRequest);
|
||||
break;
|
||||
case ResponseType.NotFound:
|
||||
responseMessage = new HttpResponseMessage(HttpStatusCode.NotFound);
|
||||
break;
|
||||
default:
|
||||
responseMessage = new HttpResponseMessage(HttpStatusCode.InternalServerError);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(result.Content))
|
||||
{
|
||||
responseMessage.Content = new StringContent(result.Content);
|
||||
}
|
||||
return responseMessage;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder.Calling;
|
||||
using Microsoft.Bot.Builder.Internals.Fibers;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling
|
||||
{
|
||||
/// <summary>
|
||||
/// The context for this request. It parses the request into <see cref="ParsedCallingRequest"/>.
|
||||
/// </summary>
|
||||
internal class RealTimeMediaCallingContext
|
||||
{
|
||||
/// <summary>
|
||||
/// The calling request.
|
||||
/// </summary>
|
||||
public readonly HttpRequestMessage Request;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of calling context.
|
||||
/// </summary>
|
||||
/// <param name="request"> The calling request.</param>
|
||||
public RealTimeMediaCallingContext(HttpRequestMessage request)
|
||||
{
|
||||
SetField.NotNull<HttpRequestMessage>(out this.Request, nameof(request), request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the calling request and returns <see cref="ParsedCallingRequest"/>.
|
||||
/// </summary>
|
||||
/// <param name="callType"> The type of request.</param>
|
||||
/// <returns> The parsed request.</returns>
|
||||
public virtual async Task<ParsedCallingRequest> ProcessRequest(RealTimeMediaCallRequestType callType)
|
||||
{
|
||||
ParsedCallingRequest parsedRequest;
|
||||
switch (callType)
|
||||
{
|
||||
case RealTimeMediaCallRequestType.IncomingCall:
|
||||
case RealTimeMediaCallRequestType.CallingEvent:
|
||||
case RealTimeMediaCallRequestType.NotificationEvent:
|
||||
parsedRequest = await ProcessRequestAsync();
|
||||
break;
|
||||
default:
|
||||
parsedRequest = CallingContext.GenerateParsedResults(HttpStatusCode.BadRequest, $"{callType} not accepted");
|
||||
break;
|
||||
}
|
||||
parsedRequest.SkypeChainId = CallingContext.ExtractSkypeChainId(this.Request);
|
||||
return parsedRequest;
|
||||
}
|
||||
|
||||
private async Task<ParsedCallingRequest> ProcessRequestAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Request.Content == null)
|
||||
{
|
||||
Trace.TraceError("No content in the request");
|
||||
return CallingContext.GenerateParsedResults(HttpStatusCode.BadRequest);
|
||||
}
|
||||
|
||||
var content = await Request.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
return CallingContext.GenerateParsedResults(HttpStatusCode.OK, content);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Trace.TraceError($"Failed to process the notification request, exception: {e}");
|
||||
return CallingContext.GenerateParsedResults(HttpStatusCode.InternalServerError, e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Autofac;
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Misc;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling
|
||||
{
|
||||
/// <summary>
|
||||
/// The top level class that is used to register the bot for enabling real-time media communication
|
||||
/// </summary>
|
||||
public static partial class RealTimeMediaCallingConversation
|
||||
{
|
||||
public static readonly IContainer Container;
|
||||
|
||||
static RealTimeMediaCallingConversation()
|
||||
{
|
||||
var builder = new ContainerBuilder();
|
||||
builder.RegisterModule(new RealTimeMediaCallingModule_MakeBot());
|
||||
Container = builder.Build();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register the function to be called to create a bot along with configuration settings.
|
||||
/// </summary>
|
||||
/// <param name="MakeCallingBot"> The factory method to make the calling bot.</param>
|
||||
public static void RegisterRealTimeMediaCallingBot(Func<IRealTimeMediaCallService, IRealTimeMediaCall> MakeCallingBot, IRealTimeMediaCallServiceSettings realTimeBotServiceSettings)
|
||||
{
|
||||
Trace.TraceInformation($"Registering real-time media calling bot");
|
||||
if(realTimeBotServiceSettings.CallbackUrl == null)
|
||||
{
|
||||
throw new ArgumentNullException("callbackUrl");
|
||||
}
|
||||
|
||||
if (realTimeBotServiceSettings.NotificationUrl == null)
|
||||
{
|
||||
throw new ArgumentNullException("notificationUrl");
|
||||
}
|
||||
|
||||
RealTimeMediaCallingModule_MakeBot.Register(Container, MakeCallingBot, realTimeBotServiceSettings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process an incoming request
|
||||
/// </summary>
|
||||
/// <param name="toBot"> The calling request sent to the bot.</param>
|
||||
/// <param name="callRequestType"> The type of calling request.</param>
|
||||
/// <returns> The response from the bot.</returns>
|
||||
public static async Task<HttpResponseMessage> SendAsync(HttpRequestMessage toBot, RealTimeMediaCallRequestType callRequestType)
|
||||
{
|
||||
using (var scope = RealTimeMediaCallingModule.BeginLifetimeScope(Container, toBot))
|
||||
{
|
||||
var context = scope.Resolve<RealTimeMediaCallingContext>();
|
||||
var parsedRequest = await context.ProcessRequest(callRequestType).ConfigureAwait(false);
|
||||
|
||||
if (parsedRequest.Faulted())
|
||||
{
|
||||
return Utils.GetResponseMessage(parsedRequest.ParseStatusCode, parsedRequest.Content);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
ResponseResult result;
|
||||
var callingBotService = scope.Resolve<IRealTimeCallProcessor>();
|
||||
switch (callRequestType)
|
||||
{
|
||||
case RealTimeMediaCallRequestType.IncomingCall:
|
||||
result = await callingBotService.ProcessIncomingCallAsync(parsedRequest.Content, parsedRequest.SkypeChaindId).ConfigureAwait(false);
|
||||
break;
|
||||
|
||||
case RealTimeMediaCallRequestType.CallingEvent:
|
||||
result = await callingBotService.ProcessCallbackAsync(parsedRequest.Content).ConfigureAwait(false);
|
||||
break;
|
||||
|
||||
case RealTimeMediaCallRequestType.NotificationEvent:
|
||||
result = await callingBotService.ProcessNotificationAsync(parsedRequest.Content).ConfigureAwait(false);
|
||||
break;
|
||||
|
||||
default:
|
||||
result = new ResponseResult(ResponseType.BadRequest, $"Unsupported call request type: {callRequestType}");
|
||||
break;
|
||||
}
|
||||
|
||||
return GetHttpResponseForResult(result);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Trace.TraceError($"RealTimeMediaCallingConversation: {e}");
|
||||
return Utils.GetResponseMessage(HttpStatusCode.InternalServerError, e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static HttpResponseMessage GetHttpResponseForResult(ResponseResult result)
|
||||
{
|
||||
HttpResponseMessage responseMessage;
|
||||
switch(result.ResponseType)
|
||||
{
|
||||
case ResponseType.Accepted:
|
||||
responseMessage = new HttpResponseMessage(HttpStatusCode.Accepted);
|
||||
break;
|
||||
case ResponseType.BadRequest:
|
||||
responseMessage = new HttpResponseMessage(HttpStatusCode.BadRequest);
|
||||
break;
|
||||
case ResponseType.NotFound:
|
||||
responseMessage = new HttpResponseMessage(HttpStatusCode.NotFound);
|
||||
break;
|
||||
default:
|
||||
responseMessage = new HttpResponseMessage(HttpStatusCode.InternalServerError);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(result.Content))
|
||||
{
|
||||
responseMessage.Content = new StringContent(result.Content);
|
||||
}
|
||||
return responseMessage;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using Autofac;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling
|
||||
{
|
||||
/// <summary>
|
||||
/// Autofac module for real-time media calling components.
|
||||
/// </summary>
|
||||
internal sealed class RealTimeMediaCallingModule : Module
|
||||
{
|
||||
public static readonly object LifetimeScopeTag = typeof(RealTimeMediaCallingModule);
|
||||
|
||||
public static ILifetimeScope BeginLifetimeScope(ILifetimeScope scope, HttpRequestMessage request)
|
||||
{
|
||||
var inner = scope.BeginLifetimeScope(LifetimeScopeTag);
|
||||
inner.Resolve<HttpRequestMessage>(TypedParameter.From(request));
|
||||
return inner;
|
||||
}
|
||||
|
||||
protected override void Load(ContainerBuilder builder)
|
||||
{
|
||||
base.Load(builder);
|
||||
|
||||
builder
|
||||
.Register((c, p) => p.TypedAs<HttpRequestMessage>())
|
||||
.AsSelf()
|
||||
.InstancePerMatchingLifetimeScope(LifetimeScopeTag);
|
||||
|
||||
builder
|
||||
.RegisterType<RealTimeMediaCallingContext>()
|
||||
.AsSelf()
|
||||
.InstancePerMatchingLifetimeScope(LifetimeScopeTag);
|
||||
|
||||
builder
|
||||
.Register(c => new RealTimeCallProcessor(c.Resolve<IRealTimeMediaCallServiceSettings>(), c.Resolve<Func<IRealTimeMediaCallService, IRealTimeMediaCall>>()))
|
||||
.AsSelf()
|
||||
.As<IRealTimeCallProcessor>()
|
||||
.SingleInstance();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Module for real-media calling
|
||||
/// </summary>
|
||||
internal sealed class RealTimeMediaCallingModule_MakeBot : Module
|
||||
{
|
||||
protected override void Load(ContainerBuilder builder)
|
||||
{
|
||||
base.Load(builder);
|
||||
|
||||
builder.RegisterModule(new RealTimeMediaCallingModule());
|
||||
|
||||
builder
|
||||
.Register((c, p) => p.TypedAs<Func<IRealTimeMediaCallService, IRealTimeMediaCall>>())
|
||||
.AsSelf()
|
||||
.SingleInstance();
|
||||
|
||||
builder
|
||||
.Register((c, p) => p.TypedAs<IRealTimeMediaCallServiceSettings>())
|
||||
.AsSelf()
|
||||
.SingleInstance();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register the function to create a bot and to retrieve bot settings
|
||||
/// </summary>
|
||||
/// <param name="scope"></param>
|
||||
/// <param name="makeCallingBot"></param>
|
||||
/// <param name="RealTimeMediaCallingSettings"></param>
|
||||
public static void Register(ILifetimeScope scope, Func<IRealTimeMediaCallService, IRealTimeMediaCall> makeCallingBot, IRealTimeMediaCallServiceSettings RealTimeMediaCallingSettings)
|
||||
{
|
||||
scope.Resolve<Func<IRealTimeMediaCallService, IRealTimeMediaCall>>(TypedParameter.From(makeCallingBot));
|
||||
scope.Resolve<IRealTimeMediaCallServiceSettings>(TypedParameter.From(RealTimeMediaCallingSettings));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.RealTimeMediaCalling
|
||||
{
|
||||
internal class RetryMessageHandler : DelegatingHandler
|
||||
{
|
||||
private const int RetryCount = 3;
|
||||
private static TimeSpan RetryDelay = TimeSpan.FromMilliseconds(100);
|
||||
|
||||
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
return RunWithRetries(
|
||||
RetryCount,
|
||||
RetryDelay,
|
||||
async () => await base.SendAsync(request, cancellationToken),
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
public static async Task<HttpResponseMessage> RunWithRetries(
|
||||
int maxRetries,
|
||||
TimeSpan delay,
|
||||
Func<Task<HttpResponseMessage>> retryableMethod,
|
||||
CancellationToken cancelToken = default(CancellationToken))
|
||||
{
|
||||
try
|
||||
{
|
||||
var exceptions = new List<Exception>();
|
||||
|
||||
var retryCount = 0;
|
||||
while (true)
|
||||
{
|
||||
if (cancelToken.IsCancellationRequested)
|
||||
{
|
||||
exceptions.Add(new OperationCanceledException());
|
||||
throw new AggregateException(exceptions);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return await retryableMethod();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Trace.TraceError("Error sending request " + e.ToString());
|
||||
exceptions.Add(e);
|
||||
|
||||
if (++retryCount > maxRetries)
|
||||
{
|
||||
throw new AggregateException(exceptions);
|
||||
}
|
||||
}
|
||||
|
||||
Trace.TraceInformation("Retrying request.. RetryCount" + retryCount);
|
||||
if (delay > TimeSpan.Zero)
|
||||
{
|
||||
await Task.Delay(delay, cancelToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (AggregateException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new AggregateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
|
@ -0,0 +1,12 @@
|
|||
@echo off
|
||||
echo *** Building Microsoft.Bot.Builder.RealTimeMediaCalling
|
||||
setlocal
|
||||
setlocal enabledelayedexpansion
|
||||
setlocal enableextensions
|
||||
set errorlevel=0
|
||||
mkdir ..\nuget
|
||||
erase /s ..\nuget\Microsoft.Bot.Builder.RealTimeMediaCalling*nupkg
|
||||
msbuild /property:Configuration=release Microsoft.Bot.Builder.RealTimeMediaCalling.csproj
|
||||
for /f %%v in ('powershell -noprofile "(Get-Command .\bin\release\Microsoft.Bot.Builder.RealTimeMediaCalling.dll).FileVersionInfo.FileVersion"') do set version=%%v
|
||||
.\packages\NuGet.CommandLine.3.5.0\tools\NuGet.exe pack Microsoft.Bot.Builder.RealTimeMediaCalling.nuspec -symbols -properties version=%version% -OutputDirectory ..\nuget
|
||||
echo *** Finished building Microsoft.Bot.Builder.RealTimeMediaCalling
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Autofac" version="3.5.2" targetFramework="net46" />
|
||||
<package id="Chronic.Signed" version="0.3.2" targetFramework="net46" />
|
||||
<package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net46" />
|
||||
<package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net46" />
|
||||
<package id="Microsoft.Bot.Builder" version="3.5.8.0" targetFramework="net46" />
|
||||
<package id="Microsoft.Bot.Builder.Calling" version="3.0.3" targetFramework="net46" />
|
||||
<package id="Microsoft.IdentityModel.Protocol.Extensions" version="1.0.2.206221351" targetFramework="net46" />
|
||||
<package id="Microsoft.Rest.ClientRuntime" version="2.3.2" targetFramework="net46" />
|
||||
<package id="Newtonsoft.Json" version="8.0.3" targetFramework="net46" />
|
||||
<package id="NuGet.CommandLine" version="3.5.0" targetFramework="net46" developmentDependency="true" />
|
||||
<package id="System.IdentityModel.Tokens.Jwt" version="4.0.2.206221351" targetFramework="net46" />
|
||||
</packages>
|
30
README.md
30
README.md
|
@ -1,3 +1,31 @@
|
|||
# Contributing
|
||||
# Real-time media calling with Skype
|
||||
|
||||
The Real-Time Media Platform for Bots adds a new dimension to how bots can interact with users by enabling real-time voice, video and screen sharing modalities. This provides the capability to build compelling and interactive entertainment, educational, and assistance bots. Users communicate with real-time media bots using Skype.
|
||||
|
||||
This is an advanced capability which allows the bot to send and receive voice and video content *frame by frame*. The bot has "raw" access to the voice, video and screen-sharing real-time modalities. For example, as the user speaks, the bot will receive 50 audio frames per second, with each frame containing 20 milliseconds (ms) of audio. The bot can perform real-time speech recognition as the audio frames are received, rather than having to wait for a recording after the user has stopped speaking. The bot can also send high-definition-resolution video to the user at 30 frames per second, along with audio.
|
||||
|
||||
The Real-Time Media Platform for Bots works with the Skype Calling Cloud to take care of call setup and media session establishment, enabling the bot to engage in a voice and (optionally) video conversation with a Skype caller. The platform provides a simple "socket"-like API for the bot to send and receive media, and handles the real-time encoding and decoding of media, using codecs such as SILK for audio and H.264 for video. A real-time media bot may also participate in Skype group video calls with multiple participants.
|
||||
|
||||
## Developer resources
|
||||
|
||||
### SDKs
|
||||
|
||||
To develop a real-time media bot, you must install these NuGet packages within your Visual Studio project:
|
||||
|
||||
- [Bot Builder Real-Time Media Calling for .NET](https://www.nuget.org/packages?q=Bot.Builder.RealTimeMediaCalling)
|
||||
- [Microsoft.Skype.Bots.Media .NET library](https://www.nuget.org/packages?q=Microsoft.Skype.Bots.Media)
|
||||
|
||||
### Get started
|
||||
|
||||
|
||||
**[Review the documentation](http://docs.botframework.com)** to get started with the Bot Builder-RealTimeMediaCalling SDK!
|
||||
|
||||
|
||||
Get started quickly with our samples:
|
||||
|
||||
* Bot Builder-RealTimeMediaCalling samples [GitHub repo](https://github.com/Microsoft/BotBuilder-RealTimeMediaCalling/Samples)
|
||||
*
|
||||
|
||||
## Contributing
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
|
|
|
@ -0,0 +1,602 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
using FrontEnd.Http;
|
||||
using FrontEnd.Logging;
|
||||
using FrontEnd.Media;
|
||||
using Microsoft.Bing.Speech;
|
||||
using Microsoft.Skype.Bots.Media;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CorrelationId = FrontEnd.Logging.CorrelationId;
|
||||
|
||||
namespace FrontEnd.Call
|
||||
{
|
||||
/// <summary>
|
||||
/// This class handles media related logic for a call.
|
||||
/// </summary>
|
||||
internal class MediaSession : IDisposable
|
||||
{
|
||||
#region Fields
|
||||
/// <summary>
|
||||
/// The long dictation URL
|
||||
/// </summary>
|
||||
private static readonly Uri LongDictationUrl = new Uri(@"wss://speech.platform.bing.com/api/service/recognition/continuous");
|
||||
|
||||
/// <summary>
|
||||
/// Unique correlationID of this particular call.
|
||||
/// </summary>
|
||||
private readonly string _correlationId;
|
||||
|
||||
/// <summary>
|
||||
/// The audio socket created for this particular call.
|
||||
/// </summary>
|
||||
private readonly AudioSocket _audioSocket;
|
||||
|
||||
/// <summary>
|
||||
/// The video socket created for this particular call.
|
||||
/// </summary>
|
||||
private readonly VideoSocket _videoSocket;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the call has been disposed
|
||||
/// </summary>
|
||||
private int _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the MediaPlatform is ready for video to be sent
|
||||
/// </summary>
|
||||
private bool _sendVideo;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the MediaPlatform is ready for audio to be sent
|
||||
/// </summary>
|
||||
private bool _sendAudio;
|
||||
|
||||
/// <summary>
|
||||
/// DefaultHueColor for the video looped back
|
||||
/// </summary>
|
||||
private Color DefaultHueColor = Color.Blue;
|
||||
|
||||
/// <summary>
|
||||
/// Speech client to talk to the bing speech services
|
||||
/// </summary>
|
||||
private SpeechClient _speechClient;
|
||||
|
||||
/// <summary>
|
||||
/// Stream used by the speech for recognition
|
||||
/// </summary>
|
||||
private SpeechRecognitionPcmStream _recognitionStream;
|
||||
|
||||
/// <summary>
|
||||
/// TokenSource to cancel the speech recognition
|
||||
/// </summary>
|
||||
private CancellationTokenSource _recognitionCts;
|
||||
|
||||
/// <summary>
|
||||
/// Wait handle to make sure the speech recognition was stopped
|
||||
/// </summary>
|
||||
private readonly ManualResetEvent _speechRecoginitionFinished;
|
||||
|
||||
private const int _speechRecognitionTaskTimeOut = 2000;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
/// <summary>
|
||||
/// The Id of this call.
|
||||
/// </summary>
|
||||
public string Id { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The opaque media configuration object sent back to the Skype platform when answering a call.
|
||||
/// </summary>
|
||||
public JObject MediaConfiguration { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of IRealTimeMediaCall that handles incoming and outgoing requests
|
||||
/// </summary>
|
||||
public readonly RealTimeMediaCall RealTimeMediaCall;
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
/// <summary>
|
||||
/// Create a new instance of the MediaSession.
|
||||
/// </summary>
|
||||
/// <param name="callerSkypeId"></param>
|
||||
/// <param name="startOutbound"></param>
|
||||
public MediaSession(string id, string correlationId, RealTimeMediaCall call)
|
||||
{
|
||||
_correlationId = CorrelationId.GetCurrentId();
|
||||
this.Id = id;
|
||||
RealTimeMediaCall = call;
|
||||
_speechRecoginitionFinished = new ManualResetEvent(false);
|
||||
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"[{this.Id}]: Call created");
|
||||
|
||||
try
|
||||
{
|
||||
_audioSocket = new AudioSocket(new AudioSocketSettings
|
||||
{
|
||||
StreamDirections = StreamDirection.Sendrecv,
|
||||
SupportedAudioFormat = AudioFormat.Pcm16K, // audio format is currently fixed at PCM 16 KHz.
|
||||
CallId = correlationId
|
||||
});
|
||||
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"[{this.Id}]:Created AudioSocket");
|
||||
|
||||
_videoSocket = new VideoSocket(new VideoSocketSettings
|
||||
{
|
||||
StreamDirections = StreamDirection.Sendrecv,
|
||||
ReceiveColorFormat = VideoColorFormat.NV12,
|
||||
|
||||
//We loop back the video in this sample. The MediaPlatform always sends only NV12 frames. So include only NV12 video in supportedSendVideoFormats
|
||||
SupportedSendVideoFormats = new List<VideoFormat>() {
|
||||
VideoFormat.NV12_270x480_15Fps,
|
||||
VideoFormat.NV12_320x180_15Fps,
|
||||
VideoFormat.NV12_360x640_15Fps,
|
||||
VideoFormat.NV12_424x240_15Fps,
|
||||
VideoFormat.NV12_480x270_15Fps,
|
||||
VideoFormat.NV12_480x848_30Fps,
|
||||
VideoFormat.NV12_640x360_15Fps,
|
||||
VideoFormat.NV12_720x1280_30Fps,
|
||||
VideoFormat.NV12_848x480_30Fps,
|
||||
VideoFormat.NV12_960x540_30Fps,
|
||||
VideoFormat.NV12_424x240_15Fps
|
||||
},
|
||||
CallId = correlationId
|
||||
});
|
||||
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"[{this.Id}]: Created VideoSocket");
|
||||
|
||||
|
||||
//audio socket events
|
||||
_audioSocket.AudioMediaReceived += OnAudioMediaReceived;
|
||||
_audioSocket.AudioSendStatusChanged += OnAudioSendStatusChanged;
|
||||
|
||||
//Video socket events
|
||||
_videoSocket.VideoMediaReceived += OnVideoMediaReceived;
|
||||
_videoSocket.VideoSendStatusChanged += OnVideoSendStatusChanged;
|
||||
|
||||
MediaConfiguration = MediaPlatform.CreateMediaConfiguration(_audioSocket, _videoSocket);
|
||||
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"[{this.Id}]: MediaConfiguration={MediaConfiguration.ToString(Formatting.Indented)}");
|
||||
|
||||
StartSpeechRecognition();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(new CallerInfo(), LogContext.FrontEnd, "Error in MediaSession creation" + ex.ToString());
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribes all audio/video send/receive-related events, cancels tasks and disposes sockets
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _disposed, 1, 0) == 0)
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"[{this.Id}]: Disposing Call");
|
||||
_sendAudio = false;
|
||||
|
||||
if (_audioSocket != null)
|
||||
{
|
||||
_audioSocket.AudioMediaReceived -= OnAudioMediaReceived;
|
||||
_audioSocket.AudioSendStatusChanged -= OnAudioSendStatusChanged;
|
||||
_audioSocket.Dispose();
|
||||
}
|
||||
_sendVideo = false;
|
||||
|
||||
if (_videoSocket != null)
|
||||
{
|
||||
_videoSocket.VideoMediaReceived -= OnVideoMediaReceived;
|
||||
_videoSocket.VideoSendStatusChanged -= OnVideoSendStatusChanged;
|
||||
_videoSocket.Dispose();
|
||||
}
|
||||
|
||||
_recognitionCts?.Cancel();
|
||||
if (!_speechRecoginitionFinished.WaitOne(_speechRecognitionTaskTimeOut))
|
||||
{
|
||||
Log.Error(new CallerInfo(), LogContext.FrontEnd, "SpeechRecoginition task did not finish within expected time");
|
||||
}
|
||||
|
||||
_recognitionCts?.Dispose();
|
||||
_recognitionStream?.Dispose();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(new CallerInfo(), LogContext.FrontEnd, $"[{this.Id}]: Ignoring exception in dispose {ex}");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
private void StartSpeechRecognition()
|
||||
{
|
||||
_recognitionCts = new CancellationTokenSource();
|
||||
|
||||
string speechSubscription = Service.Instance.Configuration.SpeechSubscription;
|
||||
Preferences preferences = new Preferences("en-US", LongDictationUrl, new CognitiveServicesAuthorizationProvider(speechSubscription), enableAudioBuffering: false);
|
||||
|
||||
_recognitionStream = new SpeechRecognitionPcmStream(16000);
|
||||
|
||||
var deviceMetadata = new DeviceMetadata(DeviceType.Far, DeviceFamily.Unknown, NetworkType.Unknown, OsName.Windows, "1607", "Dell", "T3600");
|
||||
var applicationMetadata = new ApplicationMetadata("HueBot", "1.0.0");
|
||||
var requestMetadata = new RequestMetadata(Guid.NewGuid(), deviceMetadata, applicationMetadata, "HueBot");
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
do
|
||||
{
|
||||
try
|
||||
{
|
||||
//create a speech client
|
||||
using (_speechClient = new SpeechClient(preferences))
|
||||
{
|
||||
_speechClient.SubscribeToRecognitionResult(this.OnRecognitionResult);
|
||||
|
||||
await _speechClient.RecognizeAsync(new SpeechInput(_recognitionStream, requestMetadata), _recognitionCts.Token);
|
||||
}
|
||||
Log.Info(new CallerInfo(), LogContext.Media, $"[{this.Id}]: Speech recognize completed.");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(new CallerInfo(), LogContext.Media, $"[{this.Id}]: Speech recognize threw exception {exception.ToString()}");
|
||||
}
|
||||
|
||||
if (_recognitionCts.IsCancellationRequested)
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.Media, $"[{this.Id}]:Speech recognition cancelled because it was cancelled or max exception count was hit");
|
||||
break;
|
||||
}
|
||||
|
||||
Stream oldStream = _recognitionStream;
|
||||
Log.Info(new CallerInfo(), LogContext.Media, $"[{this.Id}]: Restart speech recognition as the call is still alive. Speech recognition could have been completed because of babble/silence timeout");
|
||||
|
||||
_recognitionStream = new SpeechRecognitionPcmStream(16000);
|
||||
oldStream.Dispose();
|
||||
} while (true);
|
||||
|
||||
_speechRecoginitionFinished.Set();
|
||||
}
|
||||
).ForgetAndLogException(string.Format("Failed to start the SpeechRecognition Task for Id: {0}", Id));
|
||||
}
|
||||
|
||||
#region Event Handling Methods
|
||||
#region Speech
|
||||
public Task OnRecognitionResult(RecognitionResult result)
|
||||
{
|
||||
CorrelationId.SetCurrentId(_correlationId);
|
||||
if (result.RecognitionStatus != RecognitionStatus.Success)
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.Media, $"[{this.Id}]: Speech recognize result {result.RecognitionStatus}");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.Media, $"[{this.Id}]: Speech recognize success");
|
||||
}
|
||||
|
||||
//since we had a success recognition
|
||||
try
|
||||
{
|
||||
foreach (RecognitionPhrase phrase in result.Phrases)
|
||||
{
|
||||
string message = phrase.DisplayText.ToLower();
|
||||
Log.Info(new CallerInfo(), LogContext.Media, $"[{this.Id}]: Received from speech api {message}");
|
||||
|
||||
int redIndex = message.LastIndexOf("red");
|
||||
int blueIndex = message.LastIndexOf("blue");
|
||||
int greenIndex = message.LastIndexOf("green");
|
||||
|
||||
int colorIndex = Math.Max(greenIndex, Math.Max(redIndex, blueIndex));
|
||||
if (colorIndex == -1)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
if (colorIndex == redIndex)
|
||||
{
|
||||
DefaultHueColor = Color.Red;
|
||||
}
|
||||
else if (colorIndex == blueIndex)
|
||||
{
|
||||
DefaultHueColor = Color.Blue;
|
||||
}
|
||||
else
|
||||
{
|
||||
DefaultHueColor = Color.Green;
|
||||
}
|
||||
|
||||
Log.Info(new CallerInfo(), LogContext.Media, $"[{this.Id}]: Changing hue to {DefaultHueColor.ToString()}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.Media, $"[{this.Id}]: Exception in OnRecognitionResult {ex.ToString()}");
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Audio
|
||||
/// <summary>
|
||||
/// Callback for informational updates from the media plaform about audio status changes.
|
||||
/// Once the status becomes active, audio can be loopbacked
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void OnAudioSendStatusChanged(object sender, AudioSendStatusChangedEventArgs e)
|
||||
{
|
||||
CorrelationId.SetCurrentId(_correlationId);
|
||||
Log.Info(
|
||||
new CallerInfo(),
|
||||
LogContext.Media,
|
||||
$"[{this.Id}]: AudioSendStatusChangedEventArgs(MediaSendStatus={e.MediaSendStatus})"
|
||||
);
|
||||
|
||||
if (e.MediaSendStatus == MediaSendStatus.Active && _sendAudio == false)
|
||||
{
|
||||
_sendAudio = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback from the media platform when raw audio received. This method sends the raw
|
||||
/// audio to the transcriber. The audio is also loopbacked to the user.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void OnAudioMediaReceived(object sender, AudioMediaReceivedEventArgs e)
|
||||
{
|
||||
if (!_sendAudio)
|
||||
{
|
||||
e.Buffer.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
CorrelationId.SetCurrentId(_correlationId);
|
||||
Log.Verbose(
|
||||
new CallerInfo(),
|
||||
LogContext.Media,
|
||||
"[{0}] [AudioMediaReceivedEventArgs(Data=<{1}>, Length={2}, Timestamp={3}, AudioFormat={4})]",
|
||||
this.Id,
|
||||
e.Buffer.Data.ToString(),
|
||||
e.Buffer.Length,
|
||||
e.Buffer.Timestamp,
|
||||
e.Buffer.AudioFormat);
|
||||
|
||||
try
|
||||
{
|
||||
var audioSendBuffer = new AudioSendBuffer(e.Buffer, AudioFormat.Pcm16K, (UInt64)DateTime.Now.Ticks);
|
||||
_audioSocket.Send(audioSendBuffer);
|
||||
|
||||
byte[] buffer = new byte[e.Buffer.Length];
|
||||
Marshal.Copy(e.Buffer.Data, buffer, 0, (int)e.Buffer.Length);
|
||||
|
||||
//If the recognize had completed with error/timeout, the underlying stream might have been swapped out on us and disposed.
|
||||
//so ignore the objectDisposedException
|
||||
try
|
||||
{
|
||||
_recognitionStream.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.Media, $"[{this.Id}]: Write on recognitionStream threw ObjectDisposed");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(new CallerInfo(), LogContext.Media, $"[{this.Id}]: Caught exception when attempting to send audio buffer {ex.ToString()}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
e.Buffer.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Video
|
||||
/// <summary>
|
||||
/// Callback from the media platform when raw video is received. This is loopbacked to the user after adding the hue of the user's choice
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void OnVideoMediaReceived(object sender, VideoMediaReceivedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
CorrelationId.SetCurrentId(_correlationId);
|
||||
|
||||
Log.Verbose(
|
||||
new CallerInfo(),
|
||||
LogContext.Media,
|
||||
"[{0}] [VideoMediaReceivedEventArgs(Data=<{1}>, Length={2}, Timestamp={3}, Width={4}, Height={5}, ColorFormat={6}, FrameRate={7})]",
|
||||
this.Id,
|
||||
e.Buffer.Data.ToString(),
|
||||
e.Buffer.Length,
|
||||
e.Buffer.Timestamp,
|
||||
e.Buffer.VideoFormat.Width,
|
||||
e.Buffer.VideoFormat.Height,
|
||||
e.Buffer.VideoFormat.VideoColorFormat,
|
||||
e.Buffer.VideoFormat.FrameRate);
|
||||
|
||||
|
||||
byte[] buffer = new byte[e.Buffer.Length];
|
||||
Marshal.Copy(e.Buffer.Data, buffer, 0, (int)e.Buffer.Length);
|
||||
|
||||
VideoMediaBuffer videoRenderMediaBuffer = e.Buffer as VideoMediaBuffer;
|
||||
AddHue(DefaultHueColor, buffer, e.Buffer.VideoFormat.Width, e.Buffer.VideoFormat.Height);
|
||||
|
||||
VideoFormat sendVideoFormat = GetSendVideoFormat(e.Buffer.VideoFormat);
|
||||
var videoSendBuffer = new VideoSendBuffer(buffer, (uint)buffer.Length, sendVideoFormat);
|
||||
_videoSocket.Send(videoSendBuffer);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(new CallerInfo(), LogContext.Media, $"[{this.Id}]: Exception in VideoMediaReceived {ex.ToString()}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
e.Buffer.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private enum Color
|
||||
{
|
||||
Red,
|
||||
Blue,
|
||||
Green
|
||||
};
|
||||
|
||||
private VideoFormat GetSendVideoFormat(VideoFormat videoFormat)
|
||||
{
|
||||
VideoFormat sendVideoFormat;
|
||||
switch (videoFormat.Width)
|
||||
{
|
||||
case 270:
|
||||
sendVideoFormat = VideoFormat.NV12_270x480_15Fps;
|
||||
break;
|
||||
case 320:
|
||||
sendVideoFormat = VideoFormat.NV12_320x180_15Fps;
|
||||
break;
|
||||
case 360:
|
||||
sendVideoFormat = VideoFormat.NV12_360x640_15Fps;
|
||||
break;
|
||||
case 424:
|
||||
sendVideoFormat = VideoFormat.NV12_424x240_15Fps;
|
||||
break;
|
||||
case 480:
|
||||
if (videoFormat.Height == 270)
|
||||
{
|
||||
sendVideoFormat = VideoFormat.NV12_480x270_15Fps;
|
||||
break;
|
||||
}
|
||||
sendVideoFormat = VideoFormat.NV12_480x848_30Fps;
|
||||
break;
|
||||
case 640:
|
||||
sendVideoFormat = VideoFormat.NV12_640x360_15Fps;
|
||||
break;
|
||||
case 720:
|
||||
sendVideoFormat = VideoFormat.NV12_720x1280_30Fps;
|
||||
break;
|
||||
case 848:
|
||||
sendVideoFormat = VideoFormat.NV12_848x480_30Fps;
|
||||
break;
|
||||
case 960:
|
||||
sendVideoFormat = VideoFormat.NV12_960x540_30Fps;
|
||||
break;
|
||||
default:
|
||||
sendVideoFormat = VideoFormat.NV12_424x240_15Fps;
|
||||
break;
|
||||
}
|
||||
|
||||
return sendVideoFormat;
|
||||
}
|
||||
|
||||
private void AddHue(Color color, byte[] buffer, int width, int height)
|
||||
{
|
||||
int start = 0;
|
||||
int widthXheight = width * height;
|
||||
int count = widthXheight / 2, length = buffer.Length;
|
||||
|
||||
while (start < length)
|
||||
{
|
||||
//skip y
|
||||
start += widthXheight;
|
||||
|
||||
//read u,v
|
||||
int max = Math.Min(start + count + 1, length);
|
||||
|
||||
for (int i = start; i < max; i += 2)
|
||||
{
|
||||
switch (color)
|
||||
{
|
||||
case Color.Red:
|
||||
SubtractWithoutRollover(buffer, i, 16);
|
||||
AddWithoutRollover(buffer, i + 1, 50);
|
||||
break;
|
||||
|
||||
case Color.Blue:
|
||||
AddWithoutRollover(buffer, i, 50);
|
||||
SubtractWithoutRollover(buffer, i + 1, 8);
|
||||
break;
|
||||
|
||||
case Color.Green:
|
||||
SubtractWithoutRollover(buffer, i, 33);
|
||||
SubtractWithoutRollover(buffer, i + 1, 41);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
start += count;
|
||||
}
|
||||
}
|
||||
|
||||
private void SubtractWithoutRollover(byte[] buffer, int index, byte value)
|
||||
{
|
||||
if (buffer[index] >= value)
|
||||
{
|
||||
buffer[index] -= value;
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer[index] = byte.MinValue;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
private void AddWithoutRollover(byte[] buffer, int index, byte value)
|
||||
{
|
||||
int val = Convert.ToInt32(buffer[index]) + value;
|
||||
buffer[index] = (byte)Math.Min(val, byte.MaxValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback for informational updates from the media plaform about video status changes.
|
||||
/// Once the Status becomes active, then video can be sent.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void OnVideoSendStatusChanged(object sender, VideoSendStatusChangedEventArgs e)
|
||||
{
|
||||
CorrelationId.SetCurrentId(_correlationId);
|
||||
|
||||
Log.Info(
|
||||
new CallerInfo(),
|
||||
LogContext.Media,
|
||||
"[{0}]: [VideoSendStatusChangedEventArgs(MediaSendStatus=<{1}>;PreferredVideoSourceFormat=<{2}>]",
|
||||
this.Id,
|
||||
e.MediaSendStatus,
|
||||
e.PreferredVideoSourceFormat.VideoColorFormat);
|
||||
|
||||
if (e.MediaSendStatus == MediaSendStatus.Active && _sendVideo == false)
|
||||
{
|
||||
//Start sending video once the Video Status changes to Active
|
||||
Log.Info(new CallerInfo(), LogContext.Media, $"[{this.Id}] Start sending video");
|
||||
|
||||
_sendVideo = true;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FrontEnd.Logging;
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Contracts;
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Misc;
|
||||
using Microsoft.Bot.Builder.RealTimeMediaCalling;
|
||||
using Microsoft.Bot.Builder.RealTimeMediaCalling.Events;
|
||||
using Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Contracts;
|
||||
using Microsoft.Bot.Connector;
|
||||
using FrontEnd.Http;
|
||||
|
||||
namespace FrontEnd.Call
|
||||
{
|
||||
/// <summary>
|
||||
/// This class does all the signaling needed to handle a call.
|
||||
/// </summary>
|
||||
internal class RealTimeMediaCall : IRealTimeMediaCall
|
||||
{
|
||||
/// <summary>
|
||||
/// MediaSession that handles media related details
|
||||
/// </summary>
|
||||
public MediaSession MediaSession { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Service that helps parse incoming requests and provide corresponding events
|
||||
/// </summary>
|
||||
public IRealTimeMediaCallService CallService { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Id generated locally that is unique to each RealTimeMediaCall
|
||||
/// </summary>
|
||||
public readonly string CallId;
|
||||
|
||||
/// <summary>
|
||||
/// CorrelationId that needs to be set in the media platform for correlating logs across services
|
||||
/// </summary>
|
||||
public readonly string CorrelationId;
|
||||
|
||||
public RealTimeMediaCall(IRealTimeMediaCallService callService)
|
||||
{
|
||||
if (callService == null)
|
||||
throw new ArgumentNullException(nameof(callService));
|
||||
|
||||
CallService = callService;
|
||||
CorrelationId = callService.CorrelationId;
|
||||
CallId = CorrelationId + ":" + Guid.NewGuid().ToString();
|
||||
|
||||
//Register for the events
|
||||
CallService.OnIncomingCallReceived += OnIncomingCallReceived;
|
||||
CallService.OnAnswerAppHostedMediaCompleted += OnAnswerAppHostedMediaCompleted;
|
||||
CallService.OnCallStateChangeNotification += OnCallStateChangeNotification;
|
||||
CallService.OnCallCleanup += OnCallCleanup;
|
||||
}
|
||||
|
||||
private Task OnIncomingCallReceived(RealTimeMediaIncomingCallEvent incomingCallEvent)
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"[{CallId}] OnIncomingCallReceived");
|
||||
|
||||
MediaSession = new MediaSession(CallId, CorrelationId, this);
|
||||
incomingCallEvent.RealTimeMediaWorkflow.Actions = new ActionBase[]
|
||||
{
|
||||
new AnswerAppHostedMedia
|
||||
{
|
||||
MediaConfiguration = MediaSession.MediaConfiguration,
|
||||
OperationId = Guid.NewGuid().ToString()
|
||||
}
|
||||
};
|
||||
|
||||
incomingCallEvent.RealTimeMediaWorkflow.NotificationSubscriptions = new NotificationType[] { NotificationType.CallStateChange};
|
||||
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"[{CallId}] Answering the call");
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task OnAnswerAppHostedMediaCompleted(AnswerAppHostedMediaOutcomeEvent answerAppHostedMediaOutcomeEvent)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"[{CallId}] OnAnswerAppHostedMediaCompleted");
|
||||
AnswerAppHostedMediaOutcome answerAppHostedMediaOutcome = answerAppHostedMediaOutcomeEvent.AnswerAppHostedMediaOutcome;
|
||||
if (answerAppHostedMediaOutcome.Outcome == Outcome.Failure)
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"[{CallId}] AnswerAppHostedMedia failed with reason: {answerAppHostedMediaOutcome.FailureReason}");
|
||||
//cleanup internal resources
|
||||
MediaSession.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
answerAppHostedMediaOutcomeEvent.RealTimeMediaWorkflow.NotificationSubscriptions = new NotificationType[] { NotificationType.CallStateChange};
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
} catch (Exception ex)
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"[{CallId}] threw {ex.ToString()}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This gets called when the user hangs up the call to the bot
|
||||
/// </summary>
|
||||
/// <param name="callStateChangeNotification"></param>
|
||||
/// <returns></returns>
|
||||
private Task OnCallStateChangeNotification(CallStateChangeNotification callStateChangeNotification)
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"[{CallId}] Received CallStateChangeNotification with AudioVideoCallStateType={callStateChangeNotification.CurrentState.ToString()}");
|
||||
|
||||
if (callStateChangeNotification.CurrentState == CallState.Terminated)
|
||||
{
|
||||
MediaSession.Dispose();
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When the IRealTimeMediaCallService detects an error and cleans up the call locally
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private Task OnCallCleanup()
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"[{CallId}] Received OnCallCleanup");
|
||||
if (MediaSession != null)
|
||||
{
|
||||
MediaSession.Dispose();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
// ------------------------------------------------------------------------------------------------
|
||||
// <copyright file="SpeechRecognitionPcmStream.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
using System.IO;
|
||||
|
||||
namespace FrontEnd.Call
|
||||
{
|
||||
/// <summary>
|
||||
/// This class creates stream with wav header included. Mainly used for
|
||||
/// adding wav header in stream received from speech service.
|
||||
/// </summary>
|
||||
public class SpeechRecognitionPcmStream : SpeechRecognitionStream
|
||||
{
|
||||
private readonly short _compressionCode;
|
||||
private readonly short _numberOfChannels;
|
||||
private readonly int _sampleRate;
|
||||
private readonly int _avgBytesPerSecond;
|
||||
|
||||
public SpeechRecognitionPcmStream(int samplingRate)
|
||||
: this(samplingRate, 0)
|
||||
{
|
||||
}
|
||||
|
||||
public SpeechRecognitionPcmStream(int samplingRate, long length)
|
||||
{
|
||||
_compressionCode = 0x01; //PCM
|
||||
_numberOfChannels = 0x01; //No Stereo
|
||||
_sampleRate = samplingRate;
|
||||
_avgBytesPerSecond = samplingRate * 2;
|
||||
|
||||
InitializeHeader(length);
|
||||
}
|
||||
|
||||
private void WriteRiffChunk(BinaryWriter bw)
|
||||
{
|
||||
bw.Write(0x46464952); //'RIFF'
|
||||
bw.Write(50); //a 0sec wav file is atleast 58 bytes
|
||||
bw.Write(0x45564157); //'WAVE'
|
||||
}
|
||||
|
||||
private void WriteFmtChunk(BinaryWriter bw)
|
||||
{
|
||||
bw.Write(0x20746D66); //'fmt '
|
||||
|
||||
bw.Write(16); //16 bytes of format. We produce no 'extra format info'
|
||||
|
||||
bw.Write(_compressionCode); //2bytes
|
||||
bw.Write(_numberOfChannels); //2bytes
|
||||
bw.Write(_sampleRate); //4bytes
|
||||
bw.Write(_avgBytesPerSecond); //4bytes
|
||||
bw.Write((short)2); //alignment
|
||||
bw.Write((short)16); //significant bits per sample
|
||||
}
|
||||
|
||||
private void WriteFactChunk(BinaryWriter bw)
|
||||
{
|
||||
bw.Write(0x74636166); //'fact' chunk ID
|
||||
bw.Write(4); //4 byte Fact Chunk size
|
||||
bw.Write(0); //4 byte chunk data.
|
||||
}
|
||||
|
||||
private void WriteDataChunk(BinaryWriter bw, long length)
|
||||
{
|
||||
bw.Write(0x61746164); //'data' chunk ID
|
||||
bw.Write((int)length); //initially, we have no data, so we set the chunk size to 0
|
||||
}
|
||||
|
||||
private void InitializeHeader(long length)
|
||||
{
|
||||
var bw = new BinaryWriter(this);
|
||||
|
||||
WriteRiffChunk(bw);
|
||||
WriteFmtChunk(bw);
|
||||
WriteFactChunk(bw);
|
||||
WriteDataChunk(bw, length);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,387 @@
|
|||
// ------------------------------------------------------------------------------------------------
|
||||
// <copyright file="SpeechRecognitionStream.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace FrontEnd.Call
|
||||
{
|
||||
/// <summary>
|
||||
/// Stream to allow reading and writing simultaneously from different threads/tasks.
|
||||
/// Most of the code for this class is from oxford source code, aka project Truman.
|
||||
/// </summary>
|
||||
public class SpeechRecognitionStream : Stream
|
||||
{
|
||||
/// <summary>
|
||||
/// Queue of buffers to read from
|
||||
/// </summary>
|
||||
private readonly BlockingCollection<byte[]> _readQueue = new BlockingCollection<byte[]>(new ConcurrentQueue<byte[]>());
|
||||
|
||||
/// <summary>
|
||||
/// Set to 1 when Stream has been disposed
|
||||
/// </summary>
|
||||
private int _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// The location of the read pointer on the current buff
|
||||
/// </summary>
|
||||
private int _currentReadBufferLocation;
|
||||
|
||||
/// <summary>
|
||||
/// The current read buffer
|
||||
/// </summary>
|
||||
private byte[] _currentReadBuffer;
|
||||
|
||||
/// <summary>
|
||||
/// Numbers of bytes in the readQueue
|
||||
/// </summary>
|
||||
private uint _pendingBytes;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the current stream supports reading.
|
||||
/// </summary>
|
||||
public override bool CanRead => true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the current stream supports writing.
|
||||
/// </summary>
|
||||
public override bool CanWrite => !AudioEnded;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the current stream supports seeking. Always returns false.
|
||||
/// </summary>
|
||||
public override bool CanSeek => false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether MarkEndOfStream has been called or not
|
||||
/// </summary>
|
||||
public bool AudioEnded { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the length of the current stream. Not supported
|
||||
/// </summary>
|
||||
/// <param name="value">The desired length of the current stream in bytes.</param>
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks the end of this stream
|
||||
/// </summary>
|
||||
public void MarkEndOfStream()
|
||||
{
|
||||
if (AudioEnded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_readQueue.Add(null);
|
||||
_readQueue.CompleteAdding();
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// The stream was already completed. Harmless.
|
||||
}
|
||||
|
||||
AudioEnded = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of bytes available for reading
|
||||
/// </summary>
|
||||
/// <returns>Returns the bytes available to read</returns>
|
||||
public uint GetBytesAvailable()
|
||||
{
|
||||
return _pendingBytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes
|
||||
/// read.
|
||||
/// </summary>
|
||||
/// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source.</param>
|
||||
/// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the current stream</param>
|
||||
/// <param name="count">The maximum number of bytes to be read from the current stream.</param>
|
||||
/// <returns>The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.</returns>
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
DisposedCheck();
|
||||
|
||||
if (buffer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
}
|
||||
|
||||
if (offset < 0)
|
||||
{
|
||||
throw new ArgumentException("Offset cannot be negative", nameof(offset));
|
||||
}
|
||||
|
||||
if (count < 0 || buffer.Length < offset + count)
|
||||
{
|
||||
throw new ArgumentException("Invalid value. Count = " + count + " Offset = " + offset + " Buffer length = " + buffer.Length, nameof(count));
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If the read queue is complete, just return 0 to indicate EoS
|
||||
if (_currentReadBuffer == null)
|
||||
{
|
||||
if (_readQueue.IsCompleted)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// okay readQueue is not complete - let's read the next buffer
|
||||
_currentReadBuffer = TakeNextBuffer();
|
||||
_currentReadBufferLocation = 0;
|
||||
|
||||
// we don't have any more data in the queue return 0 to indicate EoS
|
||||
if (_currentReadBuffer == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// here the basic principle is if the user requests 1024 bytes
|
||||
// and there is say 256 bytes written to in the stream we will loop four times
|
||||
// while blocking in each chunk (at the TakeNextBuffer statement) for more bytes to arrive
|
||||
int bytesRead = 0;
|
||||
while (true)
|
||||
{
|
||||
// figure out the max # of bytes to read.
|
||||
// either it is the remaining bytes from the currentBuffer or the count of bytes requested
|
||||
// by the user, whichever is smaller
|
||||
int copyLength = Math.Min(_currentReadBuffer.Length - _currentReadBufferLocation, count);
|
||||
|
||||
// copy it to the buffer from the currentReadBuffer
|
||||
Array.Copy(_currentReadBuffer, _currentReadBufferLocation, buffer, offset, copyLength);
|
||||
|
||||
// increment the location based on the number of bytes copied
|
||||
_currentReadBufferLocation += copyLength;
|
||||
|
||||
// increment offset by the number of bytes read
|
||||
offset += copyLength;
|
||||
|
||||
// keep a total count of number of bytes read
|
||||
bytesRead += copyLength;
|
||||
|
||||
// subtract from count the number of bytes read
|
||||
count -= copyLength;
|
||||
|
||||
if (_currentReadBufferLocation >= _currentReadBuffer.Length)
|
||||
{
|
||||
_currentReadBuffer = null;
|
||||
_currentReadBufferLocation = 0;
|
||||
}
|
||||
|
||||
// if count is 0 break out - we have satisfied the number of bytes requested by the user
|
||||
if (count == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// get the bytes from the readQueue (this could potentially block)
|
||||
_currentReadBuffer = TakeNextBuffer();
|
||||
_currentReadBufferLocation = 0;
|
||||
|
||||
// TakeNextBuffer (the readQueue) returns null when we have no more bytes to add (see MarkEndOfStream)
|
||||
if (_currentReadBuffer == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// add it to the total pendingBytes
|
||||
_pendingBytes -= (uint)bytesRead;
|
||||
|
||||
// return the number of bytes read during this read request
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a sequence of bytes to the current stream and advances the current position
|
||||
/// within this stream by the number of bytes written.
|
||||
/// </summary>
|
||||
/// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</param>
|
||||
/// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current stream.</param>
|
||||
/// <param name="count">The number of bytes to be written to the current stream.</param>
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
DisposedCheck();
|
||||
// If stream has been already closed then we can just discard any incoming buffer
|
||||
if (AudioEnded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (buffer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
}
|
||||
|
||||
if (offset < 0)
|
||||
{
|
||||
throw new ArgumentException("Offset cannot be negative", nameof(offset));
|
||||
}
|
||||
|
||||
if (count < 0 || buffer.Length < offset + count)
|
||||
{
|
||||
throw new ArgumentException("Invalid value", nameof(count));
|
||||
}
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
// copy it, we don't want someone to hold on to a reference to the buffer array and modify it
|
||||
byte[] localBuffer = new byte[count];
|
||||
Buffer.BlockCopy(buffer, offset, localBuffer, 0, count);
|
||||
_readQueue.Add(localBuffer);
|
||||
|
||||
_pendingBytes += (uint)localBuffer.Length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When we know that a buffer isn't going to be reused, allow the buffer to be posted directly, saving an extra allocation and copy
|
||||
/// </summary>
|
||||
/// <param name="buffer">Audio buffer that won't be rewritten over</param>
|
||||
public void Post(byte[] buffer)
|
||||
{
|
||||
DisposedCheck();
|
||||
|
||||
if (buffer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
}
|
||||
|
||||
var len = buffer.Length;
|
||||
if (len > 0)
|
||||
{
|
||||
_readQueue.Add(buffer);
|
||||
_pendingBytes += (uint)len;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length in bytes of the stream. Not supported.
|
||||
/// </summary>
|
||||
public override long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the position within the current stream. Not supported.
|
||||
/// </summary>
|
||||
public override long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the position within the current stream.
|
||||
/// </summary>
|
||||
/// <param name="offset">offset param</param>
|
||||
/// <param name="origin">origin param</param>
|
||||
/// <returns>long return</returns>
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. Not supported.
|
||||
/// </summary>
|
||||
public override void Flush()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of the stream
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if invoked from user code</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (Interlocked.Exchange(ref _disposed, 1) == 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Can only perform managed operations if disposing is true
|
||||
if (disposing)
|
||||
{
|
||||
// ps 112121 - Ensure the reader is unblocked first
|
||||
MarkEndOfStream();
|
||||
}
|
||||
|
||||
// Call the base dispose method
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes a buffer from the read queue.
|
||||
/// </summary>
|
||||
/// <returns>The next available buffer or null if the queue is disposed</returns>
|
||||
/// <remarks>
|
||||
/// The method blocks if a buffer is unavailable.
|
||||
/// </remarks>
|
||||
private byte[] TakeNextBuffer()
|
||||
{
|
||||
byte[] buffer = null;
|
||||
|
||||
try
|
||||
{
|
||||
buffer = _readQueue.Take();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
//// This exception will occur if the above Take occurs just after CompleteAdding
|
||||
//// is called on the queue.
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
//// This exception will occur if disposal occurs while a reader is blocked on the Take()
|
||||
}
|
||||
catch (ArgumentNullException)
|
||||
{
|
||||
//// This exception will occur if disposal occurs while a reader is blocked on the Take()
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check for the object being disposed
|
||||
/// </summary>
|
||||
private void DisposedCheck()
|
||||
{
|
||||
if (_disposed == 1)
|
||||
{
|
||||
throw new ObjectDisposedException("SpeechRecognitionStream");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,358 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\packages\Bond.CSharp.4.2.1\build\Bond.CSharp.props" Condition="Exists('..\packages\Bond.CSharp.4.2.1\build\Bond.CSharp.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{BD841814-05F3-498D-834D-CC7AFE2A8F1A}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>FrontEnd</RootNamespace>
|
||||
<AssemblyName>FrontEnd</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Autofac, Version=3.5.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Autofac.3.5.2\lib\net40\Autofac.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Bond, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Bond.Core.CSharp.4.2.1\lib\net45\Bond.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Bond.Attributes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Bond.Core.CSharp.4.2.1\lib\net45\Bond.Attributes.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Bond.IO, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Bond.Core.CSharp.4.2.1\lib\net45\Bond.IO.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Bond.JSON, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Bond.Runtime.CSharp.4.2.1\lib\net45\Bond.JSON.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Chronic, Version=0.3.2.0, Culture=neutral, PublicKeyToken=3bd1f1ef638b0d3c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Chronic.Signed.0.3.2\lib\net40\Chronic.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Bing.Messaging, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Bing.Speech.2.0.2\lib\net45\Microsoft.Bing.Messaging.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Bing.Speech, Version=2.0.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Bing.Speech.2.0.2\lib\net45\Microsoft.Bing.Speech.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Bot.Builder, Version=3.5.8.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Bot.Builder.3.5.8.0\lib\net46\Microsoft.Bot.Builder.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Bot.Builder.Autofac, Version=3.5.8.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Bot.Builder.3.5.8.0\lib\net46\Microsoft.Bot.Builder.Autofac.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Bot.Builder.Calling, Version=3.0.4.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Bot.Builder.Calling.3.0.4\lib\net46\Microsoft.Bot.Builder.Calling.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Bot.Builder.RealTimeMediaCalling, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Bot.Builder.RealTimeMediaCalling.1.0.2-alpha\lib\net46\Microsoft.Bot.Builder.RealTimeMediaCalling.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Bot.Connector, Version=3.5.8.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Bot.Builder.3.5.8.0\lib\net46\Microsoft.Bot.Connector.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.IdentityModel.Protocol.Extensions, Version=1.0.2.33, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.IdentityModel.Protocol.Extensions.1.0.2.206221351\lib\net45\Microsoft.IdentityModel.Protocol.Extensions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin.Host.HttpListener, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin.Hosting, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Owin.Hosting.3.0.1\lib\net45\Microsoft.Owin.Hosting.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin.Security, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin.Security.Jwt, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Owin.Security.Jwt.3.0.1\lib\net45\Microsoft.Owin.Security.Jwt.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin.Security.OAuth, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Owin.Security.OAuth.3.0.1\lib\net45\Microsoft.Owin.Security.OAuth.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin.Security.OpenIdConnect, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Owin.Security.OpenIdConnect.3.0.1\lib\net45\Microsoft.Owin.Security.OpenIdConnect.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Rest.ClientRuntime, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Rest.ClientRuntime.2.3.2\lib\net45\Microsoft.Rest.ClientRuntime.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Skype.Bots.Media, Version=1.5.0.1177, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=AMD64">
|
||||
<HintPath>..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\lib\Microsoft.Skype.Bots.Media.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<SpecificVersion>True</SpecificVersion>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.VisualStudio.Threading, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.VisualStudio.Threading.14.1.131\lib\net45\Microsoft.VisualStudio.Threading.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.VisualStudio.Validation, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.VisualStudio.Validation.14.1.111\lib\net45\Microsoft.VisualStudio.Validation.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.WindowsAzure.Configuration, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.1\lib\net40\Microsoft.WindowsAzure.Configuration.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.IdentityModel" />
|
||||
<Reference Include="System.IdentityModel.Tokens.Jwt, Version=4.0.20622.1351, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.IdentityModel.Tokens.Jwt.4.0.2.206221351\lib\net45\System.IdentityModel.Tokens.Jwt.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Net" />
|
||||
<Reference Include="System.Net.Http.Formatting, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Net.Http.WebRequest" />
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.Threading.Tasks.Dataflow, Version=4.5.25.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Threading.Tasks.Dataflow.4.5.25\lib\dotnet\System.Threading.Tasks.Dataflow.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Web.Http, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Web.Http.Owin, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="CallLogic\MediaSession.cs" />
|
||||
<Compile Include="CallLogic\RealTimeMediaCall.cs" />
|
||||
<Compile Include="CallLogic\SpeechRecognitionPCMStream.cs" />
|
||||
<Compile Include="CallLogic\SpeechRecognitionStream.cs" />
|
||||
<Compile Include="Http\CallController.cs" />
|
||||
<Compile Include="Http\CallEndpointStartup.cs" />
|
||||
<Compile Include="Http\CognitiveServicesAuthorizationProvider.cs" />
|
||||
<Compile Include="Http\ExceptionLogger.cs" />
|
||||
<Compile Include="Http\HttpRouteConstants.cs" />
|
||||
<Compile Include="Http\LoggingMessageHandler.cs" />
|
||||
<Compile Include="Http\RealTimeMediaCallingBotServiceSettings.cs" />
|
||||
<Compile Include="IConfiguration.cs" />
|
||||
<Compile Include="Logging\CallerInfo.cs" />
|
||||
<Compile Include="Logging\CorrelationId.cs" />
|
||||
<Compile Include="Logging\Log.cs" />
|
||||
<Compile Include="MediaLogic\AudioSendBuffer.cs" />
|
||||
<Compile Include="MediaLogic\VideoSendBuffer.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Service.cs" />
|
||||
<Compile Include="Utilities.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\InstallMPServiceImpCounters.ps1">
|
||||
<Link>skype_media_lib\InstallMPServiceImpCounters.ps1</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\MediaPerf.ini">
|
||||
<Link>skype_media_lib\MediaPerf.ini</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\MediaPlatformStartupScript.bat">
|
||||
<Link>skype_media_lib\MediaPlatformStartupScript.bat</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\TraceFormat\default.tmx">
|
||||
<Link>skype_media_lib\TraceFormat\default.tmx</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\TraceFormat\default_media.tmx">
|
||||
<Link>skype_media_lib\TraceFormat\default_media.tmx</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="app.config" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\EtlReader.dll">
|
||||
<Link>skype_media_lib\EtlReader.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\MediaPerf.dll">
|
||||
<Link>skype_media_lib\MediaPerf.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\MediaPerf.h">
|
||||
<Link>skype_media_lib\MediaPerf.h</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Microsoft.Applications.Telemetry.dll">
|
||||
<Link>skype_media_lib\Microsoft.Applications.Telemetry.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Microsoft.Applications.Telemetry.Server.dll">
|
||||
<Link>skype_media_lib\Microsoft.Applications.Telemetry.Server.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Microsoft.Bond.dll">
|
||||
<Link>skype_media_lib\Microsoft.Bond.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Microsoft.Bond.Interfaces.dll">
|
||||
<Link>skype_media_lib\Microsoft.Bond.Interfaces.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Microsoft.Rtc.ClsContracts.dll">
|
||||
<Link>skype_media_lib\Microsoft.Rtc.ClsContracts.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Microsoft.Rtc.ClsLite.dll">
|
||||
<Link>skype_media_lib\Microsoft.Rtc.ClsLite.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Microsoft.Rtc.Internal.Media.dll">
|
||||
<Link>skype_media_lib\Microsoft.Rtc.Internal.Media.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Microsoft.Rtc.Internal.Media.MediaApi.dll">
|
||||
<Link>skype_media_lib\Microsoft.Rtc.Internal.Media.MediaApi.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Microsoft.Skype.ECS.Client.dll">
|
||||
<Link>skype_media_lib\Microsoft.Skype.ECS.Client.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\MPAzAppHost.dll">
|
||||
<Link>skype_media_lib\MPAzAppHost.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\MPServiceHostLib.dll">
|
||||
<Link>skype_media_lib\MPServiceHostLib.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\MPServiceImp.dll">
|
||||
<Link>skype_media_lib\MPServiceImp.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\rtmcodecs.dll">
|
||||
<Link>skype_media_lib\rtmcodecs.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\RtmMvrCs.dll">
|
||||
<Link>skype_media_lib\RtmMvrCs.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\rtmpal.dll">
|
||||
<Link>skype_media_lib\rtmpal.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Skype.ECS.Criteria.dll">
|
||||
<Link>skype_media_lib\Skype.ECS.Criteria.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Skype.ECS.Utilities.dll">
|
||||
<Link>skype_media_lib\Skype.ECS.Utilities.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\SkypeRT.dll">
|
||||
<Link>skype_media_lib\SkypeRT.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\TraceFormat\default.xml">
|
||||
<Link>skype_media_lib\TraceFormat\default.xml</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\packages\Bond.CSharp.4.2.1\build\Bond.CSharp.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Bond.CSharp.4.2.1\build\Bond.CSharp.props'))" />
|
||||
<Error Condition="!Exists('..\packages\Bond.CSharp.4.2.1\build\Bond.CSharp.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Bond.CSharp.4.2.1\build\Bond.CSharp.targets'))" />
|
||||
<Error Condition="!Exists('..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\build\Microsoft.Skype.Bots.Media.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\build\Microsoft.Skype.Bots.Media.targets'))" />
|
||||
</Target>
|
||||
<Import Project="..\packages\Bond.CSharp.4.2.1\build\Bond.CSharp.targets" Condition="Exists('..\packages\Bond.CSharp.4.2.1\build\Bond.CSharp.targets')" />
|
||||
<Import Project="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\build\Microsoft.Skype.Bots.Media.targets" Condition="Exists('..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\build\Microsoft.Skype.Bots.Media.targets')" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
|
@ -0,0 +1,93 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http;
|
||||
using FrontEnd.Call;
|
||||
using FrontEnd.Logging;
|
||||
using Microsoft.Bot.Builder.RealTimeMediaCalling;
|
||||
using Microsoft.Bot.Connector;
|
||||
|
||||
namespace FrontEnd.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// CallContoller is the enty point for handling incoming call signaling HTTP requests from Skype platform.
|
||||
/// </summary>
|
||||
[RoutePrefix(HttpRouteConstants.CallSignalingRoutePrefix)]
|
||||
public class CallController : ApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// Instantiate a CallController with a specific ICallProcessor (e.g. for testing).
|
||||
/// </summary>
|
||||
/// <param name="callProcessor"></param>
|
||||
static CallController()
|
||||
{
|
||||
RealTimeMediaCalling.RegisterRealTimeMediaCallingBot(c => { return new RealTimeMediaCall(c); },
|
||||
new RealTimeMediaCallingBotServiceSettings());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle an incoming call.
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Route(HttpRouteConstants.OnIncomingCallRoute)]
|
||||
[BotAuthentication]
|
||||
public async Task<HttpResponseMessage> OnIncomingCall(HttpRequestMessage request)
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"Received HTTP {request.Method}, {request.RequestUri}");
|
||||
|
||||
var response = await RealTimeMediaCalling.SendAsync(request, RealTimeMediaCallRequestType.IncomingCall).ConfigureAwait(false);
|
||||
|
||||
// Enforce the connection close to ensure that requests are evenly load balanced so
|
||||
// calls do no stick to one instance of the worker role.
|
||||
response.Headers.ConnectionClose = true;
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle a callback for an existing call.
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Route(HttpRouteConstants.OnCallbackRoute)]
|
||||
[BotAuthentication]
|
||||
public async Task<HttpResponseMessage> OnCallback(HttpRequestMessage request)
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"Received HTTP {request.Method}, {request.RequestUri}");
|
||||
|
||||
var response = await RealTimeMediaCalling.SendAsync(request, RealTimeMediaCallRequestType.CallingEvent).ConfigureAwait(false);
|
||||
|
||||
// Enforce the connection close to ensure that requests are evenly load balanced so
|
||||
// calls do no stick to one instance of the worker role.
|
||||
response.Headers.ConnectionClose = true;
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle a notification for an existing call.
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Route(HttpRouteConstants.OnNotificationRoute)]
|
||||
[BotAuthentication]
|
||||
public async Task<HttpResponseMessage> OnNotification(HttpRequestMessage request)
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"Notification Received HTTP {request.Method} on {request.RequestUri}");
|
||||
|
||||
var response = await RealTimeMediaCalling.SendAsync(request, RealTimeMediaCallRequestType.NotificationEvent).ConfigureAwait(false);
|
||||
|
||||
// Enforce the connection close to ensure that requests are evenly load balanced so
|
||||
// calls do no stick to one instance of the worker role.
|
||||
response.Headers.ConnectionClose = true;
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.ExceptionHandling;
|
||||
using FrontEnd.Logging;
|
||||
using Owin;
|
||||
using Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Misc;
|
||||
|
||||
namespace FrontEnd.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// Initialize the httpConfiguration for OWIN
|
||||
/// </summary>
|
||||
public class CallEndpointStartup
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration settings like Auth, Routes for OWIN
|
||||
/// </summary>
|
||||
/// <param name="app"></param>
|
||||
|
||||
public void Configuration(IAppBuilder app)
|
||||
{
|
||||
HttpConfiguration httpConfig = new HttpConfiguration();
|
||||
httpConfig.MapHttpAttributeRoutes();
|
||||
httpConfig.MessageHandlers.Add(new LoggingMessageHandler(isIncomingMessageHandler: true, logContext: LogContext.FrontEnd));
|
||||
|
||||
httpConfig.Services.Add(typeof(IExceptionLogger), new ExceptionLogger());
|
||||
httpConfig.Formatters.JsonFormatter.SerializerSettings = RealTimeMediaSerializer.GetSerializerSettings();
|
||||
httpConfig.EnsureInitialized();
|
||||
|
||||
app.UseWebApi(httpConfig);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="CognitiveServicesAuthorizationProvider.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
// MIT License
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ""Software""), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace FrontEnd.Http
|
||||
{
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Bing.Speech;
|
||||
|
||||
/// <summary>
|
||||
/// Cognitive Services Authorization Provider to contact bing speech services
|
||||
/// </summary>
|
||||
public sealed class CognitiveServicesAuthorizationProvider : IAuthorizationProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// The fetch token URI
|
||||
/// </summary>
|
||||
private const string FetchTokenUri = "https://api.cognitive.microsoft.com/sts/v1.0";
|
||||
|
||||
/// <summary>
|
||||
/// The subscription key
|
||||
/// </summary>
|
||||
private readonly string _subscriptionKey;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CognitiveServicesAuthorizationProvider" /> class.
|
||||
/// </summary>
|
||||
/// <param name="subscriptionKey">The subscription identifier.</param>
|
||||
public CognitiveServicesAuthorizationProvider(string subscriptionKey)
|
||||
{
|
||||
if (subscriptionKey == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(subscriptionKey));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(subscriptionKey))
|
||||
{
|
||||
throw new ArgumentException(nameof(subscriptionKey));
|
||||
}
|
||||
|
||||
_subscriptionKey = subscriptionKey;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authorization token asynchronously.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous read operation. The value of the string parameter contains the next the authorization token.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// This method should always return a valid authorization token at the time it is called.
|
||||
/// </remarks>
|
||||
public Task<string> GetAuthorizationTokenAsync()
|
||||
{
|
||||
return FetchToken(FetchTokenUri, this._subscriptionKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches the token.
|
||||
/// </summary>
|
||||
/// <param name="fetchUri">The fetch URI.</param>
|
||||
/// <param name="subscriptionKey">The subscription key.</param>
|
||||
/// <returns>An access token.</returns>
|
||||
private static async Task<string> FetchToken(string fetchUri, string subscriptionKey)
|
||||
{
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", subscriptionKey);
|
||||
var uriBuilder = new UriBuilder(fetchUri);
|
||||
uriBuilder.Path += "/issueToken";
|
||||
|
||||
using (var result = await client.PostAsync(uriBuilder.Uri.AbsoluteUri, null).ConfigureAwait(false))
|
||||
{
|
||||
return await result.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http.ExceptionHandling;
|
||||
using FrontEnd.Logging;
|
||||
|
||||
namespace FrontEnd.Http
|
||||
{
|
||||
internal class ExceptionLogger : IExceptionLogger
|
||||
{
|
||||
public ExceptionLogger()
|
||||
{
|
||||
}
|
||||
|
||||
public Task LogAsync(ExceptionLoggerContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
Log.Error(new CallerInfo(), LogContext.FrontEnd, "Exception processing HTTP request. {0}", context.Exception.ToString());
|
||||
return Task.FromResult<object>(null);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
namespace FrontEnd.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// HTTP route constants for routing requests to CallController methods.
|
||||
/// </summary>
|
||||
public static class HttpRouteConstants
|
||||
{
|
||||
/// <summary>
|
||||
/// Route prefix for all incoming requests.
|
||||
/// </summary>
|
||||
public const string CallSignalingRoutePrefix = "api/calling";
|
||||
|
||||
/// <summary>
|
||||
/// Route for incoming calls.
|
||||
/// </summary>
|
||||
public const string OnIncomingCallRoute = "call";
|
||||
|
||||
/// <summary>
|
||||
/// Route for existing call callbacks.
|
||||
/// </summary>
|
||||
public const string OnCallbackRoute = "callback";
|
||||
|
||||
/// <summary>
|
||||
/// Route for existing call notifications.
|
||||
/// </summary>
|
||||
public const string OnNotificationRoute = "notification";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,340 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
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.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using FrontEnd.Logging;
|
||||
|
||||
namespace FrontEnd.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class to log HTTP requests and responses and to set the CorrelationID based on the X-Microsoft-Skype-Chain-ID header
|
||||
/// value of incoming HTTP requests from Skype platform.
|
||||
/// </summary>
|
||||
internal class LoggingMessageHandler : DelegatingHandler
|
||||
{
|
||||
public const string CidHeaderName = "X-Microsoft-Skype-Chain-ID";
|
||||
|
||||
private readonly bool isIncomingMessageHandler;
|
||||
private readonly LogContext logContext;
|
||||
private string[] urlIgnorers;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new LoggingMessageHandler.
|
||||
/// </summary>
|
||||
public LoggingMessageHandler(bool isIncomingMessageHandler, LogContext logContext, string[] urlIgnorers = null)
|
||||
{
|
||||
this.isIncomingMessageHandler = isIncomingMessageHandler;
|
||||
this.logContext = logContext;
|
||||
this.urlIgnorers = urlIgnorers;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log the request and response.
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
string requestCid;
|
||||
string responseCid;
|
||||
|
||||
if (this.isIncomingMessageHandler)
|
||||
{
|
||||
requestCid = AdoptCorrelationId(request.Headers);
|
||||
}
|
||||
else
|
||||
{
|
||||
requestCid = SetCorrelationId(request.Headers);
|
||||
}
|
||||
|
||||
bool ignore =
|
||||
this.urlIgnorers != null &&
|
||||
this.urlIgnorers.Any(ignorer => request.RequestUri.ToString().IndexOf(ignorer, StringComparison.OrdinalIgnoreCase) >= 0);
|
||||
|
||||
if (ignore)
|
||||
{
|
||||
return await SendAndLogAsync(request, cancellationToken);
|
||||
}
|
||||
|
||||
string localMessageId = Guid.NewGuid().ToString();
|
||||
string requestUriText = request.RequestUri.ToString();
|
||||
string requestHeadersText = GetHeadersText(request.Headers);
|
||||
|
||||
if (request.Content != null)
|
||||
{
|
||||
requestHeadersText =
|
||||
String.Join(
|
||||
Environment.NewLine,
|
||||
requestHeadersText,
|
||||
GetHeadersText(request.Content.Headers));
|
||||
}
|
||||
|
||||
string requestBodyText = await GetBodyText(request.Content);
|
||||
|
||||
Log.Info(new CallerInfo(), logContext, "|| correlationId={0} || local.msgid={1} ||{2}{3}:: {4} {5}{6}{7}{8}{9}{10}$$END$$",
|
||||
requestCid, localMessageId,
|
||||
Environment.NewLine,
|
||||
this.isIncomingMessageHandler ? "Incoming" : "Outgoing",
|
||||
request.Method.ToString(),
|
||||
requestUriText,
|
||||
Environment.NewLine,
|
||||
requestHeadersText,
|
||||
Environment.NewLine,
|
||||
requestBodyText,
|
||||
Environment.NewLine);
|
||||
|
||||
Stopwatch stopwatch = Stopwatch.StartNew();
|
||||
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
|
||||
Log.Info(
|
||||
new CallerInfo(),
|
||||
logContext,
|
||||
"{0} HTTP request with Local id={1} took {2}ms.",
|
||||
this.isIncomingMessageHandler ? "Incoming" : "Outgoing",
|
||||
localMessageId,
|
||||
stopwatch.ElapsedMilliseconds);
|
||||
|
||||
if (this.isIncomingMessageHandler)
|
||||
{
|
||||
responseCid = SetCorrelationId(response.Headers);
|
||||
}
|
||||
else
|
||||
{
|
||||
responseCid = AdoptCorrelationId(response.Headers);
|
||||
}
|
||||
|
||||
this.WarnIfDifferent(requestCid, responseCid);
|
||||
|
||||
HttpStatusCode statusCode = response.StatusCode;
|
||||
|
||||
string responseUriText = request.RequestUri.ToString();
|
||||
string responseHeadersText = GetHeadersText(response.Headers);
|
||||
|
||||
if (response.Content != null)
|
||||
{
|
||||
responseHeadersText =
|
||||
String.Join(
|
||||
Environment.NewLine,
|
||||
responseHeadersText,
|
||||
GetHeadersText(response.Content.Headers));
|
||||
}
|
||||
|
||||
string responseBodyText = await GetBodyText(response.Content);
|
||||
|
||||
Log.Info(new CallerInfo(), logContext, "|| correlationId={0} || statuscode={1} || local.msgid={2} ||{3}Response to {4}:: {5} {6}{7}{8} {9}{10}{11}{12}{13}{14}$$END$$",
|
||||
CorrelationId.GetCurrentId(), statusCode, localMessageId,
|
||||
Environment.NewLine,
|
||||
this.isIncomingMessageHandler ? "incoming" : "outgoing",
|
||||
request.Method.ToString(),
|
||||
responseUriText,
|
||||
Environment.NewLine,
|
||||
((int)response.StatusCode).ToString(),
|
||||
response.StatusCode.ToString(),
|
||||
Environment.NewLine,
|
||||
responseHeadersText,
|
||||
Environment.NewLine,
|
||||
responseBodyText,
|
||||
Environment.NewLine);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage> SendAndLogAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await base.SendAsync(request, cancellationToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(new CallerInfo(), LogContext.FrontEnd, "Exception occurred when calling SendAsync: {0}", e.ToString());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void WarnIfDifferent(string requestCid, string responseCid)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(requestCid) || string.IsNullOrWhiteSpace(responseCid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.Equals(requestCid, responseCid))
|
||||
{
|
||||
Log.Warning(
|
||||
new CallerInfo(), LogContext.FrontEnd,
|
||||
"The correlationId of the {0} request, {1}, is different from the {2} response, {3}.",
|
||||
this.isIncomingMessageHandler ? "incoming" : "outgoing",
|
||||
requestCid,
|
||||
this.isIncomingMessageHandler ? "outgoing" : "outgoing",
|
||||
responseCid);
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetHeadersText(HttpHeaders headers)
|
||||
{
|
||||
if (headers == null || !headers.Any())
|
||||
{
|
||||
return String.Empty;
|
||||
}
|
||||
|
||||
List<string> headerTexts = new List<string>();
|
||||
|
||||
foreach (KeyValuePair<string, IEnumerable<string>> h in headers)
|
||||
{
|
||||
headerTexts.Add(GetHeaderText(h));
|
||||
}
|
||||
|
||||
return String.Join(Environment.NewLine, headerTexts);
|
||||
}
|
||||
|
||||
private static string GetHeaderText(KeyValuePair<string, IEnumerable<string>> header)
|
||||
{
|
||||
return String.Format("{0}: {1}", header.Key, String.Join(",", header.Value));
|
||||
}
|
||||
|
||||
private static string AdoptCorrelationId(HttpHeaders headers)
|
||||
{
|
||||
string correlationId = null;
|
||||
IEnumerable<string> correlationIdHeaderValues;
|
||||
if (headers.TryGetValues(CidHeaderName, out correlationIdHeaderValues))
|
||||
{
|
||||
correlationId = correlationIdHeaderValues.FirstOrDefault();
|
||||
CorrelationId.SetCurrentId(correlationId);
|
||||
}
|
||||
|
||||
return correlationId;
|
||||
}
|
||||
|
||||
private static string SetCorrelationId(HttpHeaders headers)
|
||||
{
|
||||
string correlationId = CorrelationId.GetCurrentId();
|
||||
if (!string.IsNullOrWhiteSpace(correlationId))
|
||||
{
|
||||
headers.Add(CidHeaderName, correlationId);
|
||||
}
|
||||
|
||||
return correlationId;
|
||||
}
|
||||
|
||||
public static async Task<string> GetBodyText(HttpContent content)
|
||||
{
|
||||
if (content == null)
|
||||
{
|
||||
return "(empty body)";
|
||||
}
|
||||
|
||||
if (content.IsMimeMultipartContent())
|
||||
{
|
||||
Stream stream = await content.ReadAsStreamAsync();
|
||||
|
||||
if (!stream.CanSeek)
|
||||
{
|
||||
return "(cannot log body because HTTP stream cannot seek)";
|
||||
}
|
||||
|
||||
StringBuilder multipartBodyBuilder = new StringBuilder();
|
||||
MultipartMemoryStreamProvider streamProvider = new MultipartMemoryStreamProvider();
|
||||
await content.ReadAsMultipartAsync<MultipartMemoryStreamProvider>(streamProvider, (int)stream.Length);
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var multipartContent in streamProvider.Contents)
|
||||
{
|
||||
multipartBodyBuilder.AppendLine("-- beginning of multipart content --");
|
||||
|
||||
// Headers
|
||||
string headerText = GetHeadersText(multipartContent.Headers);
|
||||
multipartBodyBuilder.AppendLine(headerText);
|
||||
|
||||
// Body of message
|
||||
string multipartBody = await multipartContent.ReadAsStringAsync();
|
||||
string formattedJsonBody;
|
||||
|
||||
if (TryFormatJsonBody(multipartBody, out formattedJsonBody))
|
||||
{
|
||||
multipartBody = formattedJsonBody;
|
||||
}
|
||||
|
||||
if (String.IsNullOrWhiteSpace(multipartBody))
|
||||
{
|
||||
multipartBodyBuilder.AppendLine("(empty body)");
|
||||
}
|
||||
else
|
||||
{
|
||||
multipartBodyBuilder.AppendLine(multipartBody);
|
||||
}
|
||||
|
||||
multipartBodyBuilder.AppendLine("-- end of multipart content --");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Reset the stream position so consumers of this class can re-read the multipart content.
|
||||
stream.Position = 0;
|
||||
}
|
||||
|
||||
return multipartBodyBuilder.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
string body = await content.ReadAsStringAsync();
|
||||
|
||||
string formattedJsonBody;
|
||||
if (TryFormatJsonBody(body, out formattedJsonBody))
|
||||
{
|
||||
body = formattedJsonBody;
|
||||
}
|
||||
|
||||
if (String.IsNullOrWhiteSpace(body))
|
||||
{
|
||||
return "(empty body)";
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryFormatJsonBody(string body, out string jsonBody)
|
||||
{
|
||||
jsonBody = null;
|
||||
|
||||
if (String.IsNullOrWhiteSpace(body))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
object parsedObject = JsonConvert.DeserializeObject(body);
|
||||
|
||||
if (parsedObject == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
jsonBody = JsonConvert.SerializeObject(parsedObject, Formatting.Indented);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
using System;
|
||||
using Microsoft.Bot.Builder.RealTimeMediaCalling;
|
||||
|
||||
namespace FrontEnd.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// Service settings to configure the RealTimeMediaCalling
|
||||
/// </summary>
|
||||
public class RealTimeMediaCallingBotServiceSettings : IRealTimeMediaCallServiceSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// The url where the callbacks for the calls to this bot needs to be sent.
|
||||
/// For example "https://testservice.azurewebsites.net/api/calling/callback"
|
||||
/// </summary>
|
||||
public Uri CallbackUrl { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The url where the notifications for the calls to this bot needs to be sent.
|
||||
/// For example "https://testservice.azurewebsites.net/api/calling/notification"
|
||||
/// </summary>
|
||||
public Uri NotificationUrl { get; private set; }
|
||||
|
||||
public RealTimeMediaCallingBotServiceSettings()
|
||||
{
|
||||
CallbackUrl = Service.Instance.Configuration.CallControlCallbackUrl;
|
||||
NotificationUrl = Service.Instance.Configuration.NotificationCallbackUrl;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
using Microsoft.Skype.Bots.Media;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FrontEnd
|
||||
{
|
||||
/// <summary>
|
||||
/// IConfiguration contains the static configuration information the application needs
|
||||
/// to run such as the urls it needs to listen on, credentials to communicate with
|
||||
/// Bing translator, settings for media.platform, etc.
|
||||
///
|
||||
/// The concrete implementation AzureConfiguration gets the configuration from Azure. However,
|
||||
/// other concrete classes could be created to allow the application to run outside of Azure
|
||||
/// for testing.
|
||||
/// </summary>
|
||||
public interface IConfiguration : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// List of HTTP urls the app should listen on for incoming call
|
||||
/// signaling requests from Skype Platform.
|
||||
/// </summary>
|
||||
IEnumerable<Uri> CallControlListeningUrls { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The base callback URL for this instance. To ensure that all requests
|
||||
/// for a given call go to the same instance, this Url is unique to each
|
||||
/// instance by way of its instance input endpoint port.
|
||||
/// </summary>
|
||||
Uri CallControlCallbackUrl { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The template for call notifications like call state change notifications.
|
||||
/// To ensure that all requests for a given call go to the same instance, this Url
|
||||
/// is unique to each instance by way of its instance input endpoint port.
|
||||
/// </summary>
|
||||
Uri NotificationCallbackUrl { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Speech subscription credentials
|
||||
/// </summary>
|
||||
string SpeechSubscription { get; }
|
||||
|
||||
/// <summary>
|
||||
/// MicrosoftAppId generated at the time of registration of the bot
|
||||
/// </summary>
|
||||
string MicrosoftAppId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Settings for the bot media platform
|
||||
/// </summary>
|
||||
MediaPlatformSettings MediaPlatformSettings { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace FrontEnd.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Class that encapsulates the caller's (creator's) information. This is helpful to provide more context in log statements.
|
||||
/// </summary>
|
||||
public class CallerInfo
|
||||
{
|
||||
private static ConcurrentDictionary<int, string> toStringCache = new ConcurrentDictionary<int, string>();
|
||||
|
||||
/// <summary>
|
||||
/// The name of the method or property of the caller
|
||||
/// </summary>
|
||||
public string MemberName { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The full path of the source file of the caller
|
||||
/// </summary>
|
||||
public string FilePath { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The line number of the source file of the caller
|
||||
/// </summary>
|
||||
public int LineNumber { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the CallerInfo class
|
||||
/// </summary>
|
||||
public CallerInfo(
|
||||
[CallerMemberName] string memberName = "",
|
||||
[CallerFilePath] string filePath = "",
|
||||
[CallerLineNumber] int lineNumber = 0
|
||||
)
|
||||
{
|
||||
this.MemberName = memberName;
|
||||
this.FilePath = filePath;
|
||||
this.LineNumber = lineNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the hashcode for this instance
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return MemberName.GetHashCode() ^ FilePath.GetHashCode() ^ LineNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String representation of the caller's info
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return toStringCache.GetOrAdd(this.GetHashCode(), hc => String.Format(
|
||||
"{0},{1}({2})",
|
||||
this.MemberName,
|
||||
Path.GetFileName(this.FilePath),
|
||||
this.LineNumber
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
using System;
|
||||
using System.Runtime.Remoting.Messaging;
|
||||
|
||||
namespace FrontEnd.Logging
|
||||
{
|
||||
internal class CorrelationId
|
||||
{
|
||||
private class Holder : MarshalByRefObject
|
||||
{
|
||||
public string Id;
|
||||
}
|
||||
|
||||
internal const string LogicalDataName = "FrontEnd.Logging.CorrelationId";
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current correlation ID. This is necessary to call in event handler callbacks because the event producer
|
||||
/// may not be aware of the call id.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
public static void SetCurrentId(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Holder holder = CallContext.LogicalGetData(LogicalDataName) as Holder;
|
||||
if (holder == null)
|
||||
{
|
||||
CallContext.LogicalSetData(LogicalDataName, new Holder { Id = value });
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
holder.Id = value;
|
||||
}
|
||||
catch (AppDomainUnloadedException)
|
||||
{
|
||||
CallContext.LogicalSetData(LogicalDataName, new Holder { Id = value });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current correlation id.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetCurrentId()
|
||||
{
|
||||
Holder holder = CallContext.LogicalGetData(LogicalDataName) as Holder;
|
||||
if (holder != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
return holder.Id;
|
||||
}
|
||||
catch (AppDomainUnloadedException)
|
||||
{
|
||||
CallContext.FreeNamedDataSlot(LogicalDataName);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace FrontEnd.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Different contexts for which log statements are produced. Each of these contexts
|
||||
/// has a corresponding TraceSource entry in the WorkerRole's app.config file.
|
||||
/// </summary>
|
||||
public enum LogContext
|
||||
{
|
||||
FrontEnd,
|
||||
Media
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper class for logging. This class provides a common mechanism for logging throughout the application.
|
||||
/// </summary>
|
||||
public static class Log
|
||||
{
|
||||
private static readonly Dictionary<LogContext, TraceSource> traceSources = new Dictionary<LogContext, TraceSource>();
|
||||
|
||||
static Log()
|
||||
{
|
||||
foreach (LogContext context in Enum.GetValues(typeof(LogContext)))
|
||||
{
|
||||
traceSources[context] = new TraceSource(context.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if Verbose method is on
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsVerboseOn(LogContext context)
|
||||
{
|
||||
TraceSource traceSource = traceSources[context];
|
||||
return traceSource.Switch.Level >= SourceLevels.Verbose || traceSource.Switch.Level == SourceLevels.All;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verbose logging of the message
|
||||
/// </summary>
|
||||
/// <param name="callerInfo"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="format"></param>
|
||||
/// <param name="args"></param>
|
||||
public static void Verbose(CallerInfo callerInfo, LogContext context, string format, params object[] args)
|
||||
{
|
||||
Log.Write(TraceEventType.Verbose, callerInfo, context, format, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Info level logging of the message
|
||||
/// </summary>
|
||||
/// <param name="callerInfo"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="format"></param>
|
||||
/// <param name="args"></param>
|
||||
public static void Info(CallerInfo callerInfo, LogContext context, string format, params object[] args)
|
||||
{
|
||||
Log.Write(TraceEventType.Information, callerInfo, context, format, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Warning level logging of the message
|
||||
/// </summary>
|
||||
/// <param name="callerInfo"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="format"></param>
|
||||
/// <param name="args"></param>
|
||||
public static void Warning(CallerInfo callerInfo, LogContext context, string format, params object[] args)
|
||||
{
|
||||
Log.Write(TraceEventType.Warning, callerInfo, context, format, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Error level logging of the message
|
||||
/// </summary>
|
||||
/// <param name="callerInfo"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="format"></param>
|
||||
/// <param name="args"></param>
|
||||
public static void Error(CallerInfo callerInfo, LogContext context, string format, params object[] args)
|
||||
{
|
||||
Log.Write(TraceEventType.Error, callerInfo, context, format, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flush the log trace sources
|
||||
/// </summary>
|
||||
public static void Flush()
|
||||
{
|
||||
foreach (TraceSource traceSource in traceSources.Values)
|
||||
{
|
||||
traceSource.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
private static void Write(TraceEventType level, CallerInfo callerInfo, LogContext context, string format, params object[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
string correlationId = CorrelationId.GetCurrentId() ?? "-";
|
||||
string callerInfoString = (callerInfo == null) ? "-" : callerInfo.ToString();
|
||||
string tracePrefix = "[" + correlationId + " " + callerInfoString + "] ";
|
||||
if (args.Length == 0)
|
||||
{
|
||||
traceSources[context].TraceEvent(level, 0, tracePrefix + format);
|
||||
}
|
||||
else
|
||||
{
|
||||
traceSources[context].TraceEvent(level, 0, string.Format(tracePrefix + format, args));
|
||||
}
|
||||
|
||||
}catch(Exception ex)
|
||||
{
|
||||
Trace.TraceError("Error in Log.cs" + ex.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using FrontEnd.Logging;
|
||||
using Microsoft.Skype.Bots.Media;
|
||||
|
||||
|
||||
namespace FrontEnd.Media
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an Audio Buffer for Send and also implements Dispose
|
||||
/// </summary>
|
||||
class AudioSendBuffer : AudioMediaBuffer
|
||||
{
|
||||
private bool _disposed;
|
||||
|
||||
[DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
|
||||
public static extern void CopyMemory(IntPtr dest, IntPtr src, uint count);
|
||||
|
||||
public AudioSendBuffer(AudioMediaBuffer mediaBuffer, AudioFormat format, ulong timeStamp)
|
||||
{
|
||||
IntPtr unmanagedBuffer = Marshal.AllocHGlobal((int)mediaBuffer.Length);
|
||||
CopyMemory(unmanagedBuffer, mediaBuffer.Data, (uint)mediaBuffer.Length);
|
||||
|
||||
Data = unmanagedBuffer;
|
||||
Length = mediaBuffer.Length;
|
||||
AudioFormat = format;
|
||||
Timestamp = (long)timeStamp;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
Marshal.FreeHGlobal(Data);
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using FrontEnd.Logging;
|
||||
using Microsoft.Skype.Bots.Media;
|
||||
|
||||
namespace FrontEnd.Media
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a Video Buffer for Send and also implements Dispose
|
||||
/// </summary>
|
||||
class VideoSendBuffer : VideoMediaBuffer
|
||||
{
|
||||
private bool _disposed;
|
||||
|
||||
public VideoSendBuffer(byte[] buffer, uint length, VideoFormat format)
|
||||
{
|
||||
IntPtr ptrToBuffer = Marshal.AllocHGlobal(buffer.Length);
|
||||
Marshal.Copy(buffer, 0, ptrToBuffer, buffer.Length);
|
||||
|
||||
Data = ptrToBuffer;
|
||||
Length = length;
|
||||
VideoFormat = format;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
Marshal.FreeHGlobal(Data);
|
||||
Data = IntPtr.Zero;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("FrontEnd")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("FrontEnd")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2016")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("bd841814-05f3-498d-834d-cc7afe2a8f1a")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
|
@ -0,0 +1,103 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
using System;
|
||||
using FrontEnd.Http;
|
||||
using Microsoft.Owin.Hosting;
|
||||
using Microsoft.Skype.Bots.Media;
|
||||
|
||||
namespace FrontEnd
|
||||
{
|
||||
/// <summary>
|
||||
/// Service is the main entry point independent of Azure. Anyone instantiating Service needs to first
|
||||
/// initialize the DependencyResolver. Calling Start() on the Service starts the HTTP server that will
|
||||
/// listen for incoming Conversation requests from the Skype Platform.
|
||||
/// </summary>
|
||||
public class Service
|
||||
{
|
||||
private readonly object _syncLock = new object();
|
||||
private bool _initialized;
|
||||
|
||||
private IDisposable _callHttpServer;
|
||||
private bool _started = false;
|
||||
public readonly string DefaultSendVideoFormat;
|
||||
|
||||
public IConfiguration Configuration { get; private set; }
|
||||
|
||||
public static readonly Service Instance = new Service();
|
||||
|
||||
/// <summary>
|
||||
/// Instantiate a custom server (e.g. for testing).
|
||||
/// </summary>
|
||||
/// <param name="listeningUris">HTTP urls to listen for incoming call signaling requests.</param>
|
||||
/// <param name="callProcessor">The call processor instance.</param>
|
||||
public void Initialize(IConfiguration config)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (_initialized)
|
||||
{
|
||||
throw new InvalidOperationException("Service is already initialized");
|
||||
}
|
||||
}
|
||||
|
||||
Configuration = config;
|
||||
|
||||
MediaPlatform.Initialize(config.MediaPlatformSettings);
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start the service.
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (_started)
|
||||
{
|
||||
throw new InvalidOperationException("The service is already started.");
|
||||
}
|
||||
|
||||
// Start HTTP server for calls
|
||||
StartOptions callStartOptions = new StartOptions();
|
||||
foreach (Uri url in Configuration.CallControlListeningUrls)
|
||||
{
|
||||
callStartOptions.Urls.Add(url.ToString());
|
||||
}
|
||||
|
||||
this._callHttpServer = WebApp.Start(
|
||||
callStartOptions,
|
||||
(appBuilder) =>
|
||||
{
|
||||
var startup = new CallEndpointStartup();
|
||||
startup.Configuration(appBuilder);
|
||||
});
|
||||
|
||||
_started = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop the service.
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (!this._started)
|
||||
{
|
||||
throw new InvalidOperationException("The service is already stopped.");
|
||||
}
|
||||
|
||||
this._started = false;
|
||||
}
|
||||
|
||||
this._callHttpServer.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using FrontEnd.Logging;
|
||||
|
||||
namespace FrontEnd
|
||||
{
|
||||
internal static class Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension for Task to execute the task in background and log any exception
|
||||
/// </summary>
|
||||
/// <param name="task"></param>
|
||||
/// <param name="description"></param>
|
||||
public static async void ForgetAndLogException(this Task task, string description = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
//ignore
|
||||
Log.Error(new CallerInfo(),
|
||||
LogContext.FrontEnd,
|
||||
"Caught an Exception running the task: {0} \n StackTrace: {1}", e.Message, e.StackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.IdentityModel.Tokens.Jwt" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.20622.1351" newVersion="4.0.20622.1351" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.IdentityModel.Logging" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-1.0.0.127" newVersion="1.0.0.127" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.IdentityModel.Protocol.Extensions" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-1.0.2.33" newVersion="1.0.2.33" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Threading.Tasks.Dataflow" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.5.25.0" newVersion="4.5.25.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" /></startup></configuration>
|
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Autofac" version="3.5.2" targetFramework="net461" />
|
||||
<package id="Bond.Core.CSharp" version="4.2.1" targetFramework="net461" />
|
||||
<package id="Bond.CSharp" version="4.2.1" targetFramework="net461" />
|
||||
<package id="Bond.Runtime.CSharp" version="4.2.1" targetFramework="net461" />
|
||||
<package id="Chronic.Signed" version="0.3.2" targetFramework="net461" />
|
||||
<package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net461" />
|
||||
<package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net461" />
|
||||
<package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net461" />
|
||||
<package id="Microsoft.Bing.Speech" version="2.0.2" targetFramework="net461" />
|
||||
<package id="Microsoft.Bot.Builder" version="3.5.8.0" targetFramework="net461" />
|
||||
<package id="Microsoft.Bot.Builder.Calling" version="3.0.4" targetFramework="net461" />
|
||||
<package id="Microsoft.Bot.Builder.RealTimeMediaCalling" version="1.0.2-alpha" targetFramework="net461" />
|
||||
<package id="Microsoft.IdentityModel.Protocol.Extensions" version="1.0.2.206221351" targetFramework="net461" />
|
||||
<package id="Microsoft.Owin" version="3.0.1" targetFramework="net461" />
|
||||
<package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net452" />
|
||||
<package id="Microsoft.Owin.Hosting" version="3.0.1" targetFramework="net461" />
|
||||
<package id="Microsoft.Owin.Security" version="3.0.1" targetFramework="net461" />
|
||||
<package id="Microsoft.Owin.Security.Jwt" version="3.0.1" targetFramework="net461" />
|
||||
<package id="Microsoft.Owin.Security.OAuth" version="3.0.1" targetFramework="net461" />
|
||||
<package id="Microsoft.Owin.Security.OpenIdConnect" version="3.0.1" targetFramework="net461" />
|
||||
<package id="Microsoft.Rest.ClientRuntime" version="2.3.2" targetFramework="net461" />
|
||||
<package id="Microsoft.Skype.Bots.Media" version="1.5.0.1177-alpha" targetFramework="net461" />
|
||||
<package id="Microsoft.VisualStudio.Threading" version="14.1.131" targetFramework="net461" />
|
||||
<package id="Microsoft.VisualStudio.Validation" version="14.1.111" targetFramework="net461" />
|
||||
<package id="Microsoft.WindowsAzure.ConfigurationManager" version="3.2.1" targetFramework="net461" />
|
||||
<package id="Newtonsoft.Json" version="8.0.3" targetFramework="net461" />
|
||||
<package id="Owin" version="1.0" targetFramework="net461" />
|
||||
<package id="System.Collections" version="4.0.0" targetFramework="net461" />
|
||||
<package id="System.Collections.Concurrent" version="4.0.0" targetFramework="net461" />
|
||||
<package id="System.Diagnostics.Debug" version="4.0.0" targetFramework="net461" />
|
||||
<package id="System.Diagnostics.Tracing" version="4.0.0" targetFramework="net461" />
|
||||
<package id="System.Dynamic.Runtime" version="4.0.0" targetFramework="net461" />
|
||||
<package id="System.IdentityModel.Tokens.Jwt" version="4.0.2.206221351" targetFramework="net461" />
|
||||
<package id="System.IdentityModel.Tokens.ValidatingIssuerNameRegistry" version="4.5.1" targetFramework="net452" />
|
||||
<package id="System.Linq" version="4.0.0" targetFramework="net461" />
|
||||
<package id="System.Resources.ResourceManager" version="4.0.0" targetFramework="net461" />
|
||||
<package id="System.Runtime" version="4.0.0" targetFramework="net461" />
|
||||
<package id="System.Runtime.Extensions" version="4.0.0" targetFramework="net461" />
|
||||
<package id="System.Threading" version="4.0.0" targetFramework="net461" />
|
||||
<package id="System.Threading.Tasks" version="4.0.0" targetFramework="net461" />
|
||||
<package id="System.Threading.Tasks.Dataflow" version="4.5.25" targetFramework="net461" />
|
||||
</packages>
|
|
@ -0,0 +1,67 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProductVersion>2.8</ProductVersion>
|
||||
<ProjectGuid>4eabd84f-6ad2-48d5-b286-b86758d4f806</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>HueBot</RootNamespace>
|
||||
<AssemblyName>HueBot</AssemblyName>
|
||||
<StartDevelopmentStorage>True</StartDevelopmentStorage>
|
||||
<Name>HueBot</Name>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<!-- Items for the project -->
|
||||
<ItemGroup>
|
||||
<ServiceDefinition Include="ServiceDefinition.csdef" />
|
||||
<ServiceConfiguration Include="ServiceConfiguration.Local.cscfg" />
|
||||
<ServiceConfiguration Include="ServiceConfiguration.Cloud.cscfg" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="WorkerRole\WorkerRole.csproj">
|
||||
<Name>WorkerRole</Name>
|
||||
<Project>{9f593a7b-8935-4bc8-9fbb-c3a9e3bc2a23}</Project>
|
||||
<Private>True</Private>
|
||||
<RoleType>Worker</RoleType>
|
||||
<RoleName>WorkerRole</RoleName>
|
||||
<UpdateDiagnosticsConnectionStringOnPublish>True</UpdateDiagnosticsConnectionStringOnPublish>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Profiles" />
|
||||
<Folder Include="WorkerRoleContent\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<DiagnosticsConfiguration Include="WorkerRoleContent\diagnostics.wadcfgx" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PublishProfile Include="Profiles\agent-uswe-dev-01-skypeProduction.azurePubxml" />
|
||||
<PublishProfile Include="Profiles\huebotProduction.azurePubxml" />
|
||||
<PublishProfile Include="Profiles\huebotProduction1.azurePubxml" />
|
||||
</ItemGroup>
|
||||
<!-- Import the target files for this project template -->
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition=" '$(VisualStudioVersion)' == '' ">10.0</VisualStudioVersion>
|
||||
<CloudExtensionsDir Condition=" '$(CloudExtensionsDir)' == '' ">$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Windows Azure Tools\2.8\</CloudExtensionsDir>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(CloudExtensionsDir)Microsoft.WindowsAzure.targets" />
|
||||
</Project>
|
|
@ -0,0 +1,37 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.25420.1
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{CC5FD16D-436D-48AD-A40C-5A424C6E3E79}") = "HueBot", "HueBot.ccproj", "{4EABD84F-6AD2-48D5-B286-B86758D4F806}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{BD841814-05F3-498D-834D-CC7AFE2A8F1A} = {BD841814-05F3-498D-834D-CC7AFE2A8F1A}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkerRole", "WorkerRole\WorkerRole.csproj", "{9F593A7B-8935-4BC8-9FBB-C3A9E3BC2A23}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrontEnd", "FrontEnd\FrontEnd.csproj", "{BD841814-05F3-498D-834D-CC7AFE2A8F1A}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{4EABD84F-6AD2-48D5-B286-B86758D4F806}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{4EABD84F-6AD2-48D5-B286-B86758D4F806}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{4EABD84F-6AD2-48D5-B286-B86758D4F806}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{4EABD84F-6AD2-48D5-B286-B86758D4F806}.Release|x64.Build.0 = Release|Any CPU
|
||||
{9F593A7B-8935-4BC8-9FBB-C3A9E3BC2A23}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{9F593A7B-8935-4BC8-9FBB-C3A9E3BC2A23}.Debug|x64.Build.0 = Debug|x64
|
||||
{9F593A7B-8935-4BC8-9FBB-C3A9E3BC2A23}.Release|x64.ActiveCfg = Release|x64
|
||||
{9F593A7B-8935-4BC8-9FBB-C3A9E3BC2A23}.Release|x64.Build.0 = Release|x64
|
||||
{BD841814-05F3-498D-834D-CC7AFE2A8F1A}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{BD841814-05F3-498D-834D-CC7AFE2A8F1A}.Debug|x64.Build.0 = Debug|x64
|
||||
{BD841814-05F3-498D-834D-CC7AFE2A8F1A}.Release|x64.ActiveCfg = Release|x64
|
||||
{BD841814-05F3-498D-834D-CC7AFE2A8F1A}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ServiceConfiguration serviceName="HueBot" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration" osFamily="4" osVersion="*" schemaVersion="2015-04.2.6">
|
||||
<Role name="WorkerRole">
|
||||
<Instances count="1" />
|
||||
<ConfigurationSettings>
|
||||
<Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="DefaultEndpointsProtocol=https;AccountName=$storage$;AccountKey=$storageKey$" />
|
||||
<Setting name="ServiceDnsName" value="$ServiceDnsName$" /> <!-- xyz.cloudapp.net-->
|
||||
<Setting name="DefaultCertificate" value="$CertificateThumbprint$" />
|
||||
<Setting name="Skype.Bots.Speech.Subscription" value="$SpeechSubscriptionKey$"/>
|
||||
<Setting name="Microsoft.WindowsAzure.Plugins.RemoteForwarder.Enabled" value="true" />
|
||||
</ConfigurationSettings>
|
||||
<Certificates>
|
||||
<Certificate name="Default" thumbprint="$CertificateThumbprint$" thumbprintAlgorithm="sha1" />
|
||||
</Certificates>
|
||||
</Role>
|
||||
<NetworkConfiguration>
|
||||
<AddressAssignments>
|
||||
<InstanceAddress roleName="WorkerRole">
|
||||
<PublicIPs>
|
||||
<PublicIP name="instancePublicIP" domainNameLabel="pip" />
|
||||
</PublicIPs>
|
||||
</InstanceAddress>
|
||||
</AddressAssignments>
|
||||
</NetworkConfiguration>
|
||||
</ServiceConfiguration>
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ServiceConfiguration serviceName="HueBot" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration" osFamily="4" osVersion="*" schemaVersion="2015-04.2.6">
|
||||
<Role name="WorkerRole">
|
||||
<Instances count="1" />
|
||||
<ConfigurationSettings>
|
||||
<Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="DefaultEndpointsProtocol=https;AccountName=$storage$;AccountKey=$storageKey$" />
|
||||
<Setting name="ServiceDnsName" value="$ServiceDnsName$" /> <!-- xyz.cloudapp.net-->
|
||||
<Setting name="DefaultCertificate" value="$CertificateThumbprint$" />
|
||||
<Setting name="Skype.Bots.Speech.Subscription" value="$SpeechSubscriptionKey$"/>
|
||||
<Setting name="Microsoft.WindowsAzure.Plugins.RemoteForwarder.Enabled" value="true" />
|
||||
</ConfigurationSettings>
|
||||
<Certificates>
|
||||
<Certificate name="Default" thumbprint="$CertificateThumbprint$" thumbprintAlgorithm="sha1" />
|
||||
</Certificates>
|
||||
</Role>
|
||||
</ServiceConfiguration>
|
|
@ -0,0 +1,61 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ServiceDefinition name="HueBot" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition" schemaVersion="2015-04.2.6">
|
||||
<WorkerRole name="WorkerRole" vmsize="Standard_D3_v2">
|
||||
<Runtime executionContext="elevated" />
|
||||
<Startup>
|
||||
<Task commandLine="SetupMediaFoundation.cmd" executionContext="elevated" taskType="simple"/>
|
||||
<Task commandLine="Startup.cmd > Startup.cmd.log" executionContext="elevated" taskType="simple">
|
||||
<Environment>
|
||||
<Variable name="PrivateDefaultCallControlPort">
|
||||
<RoleInstanceValue xpath="/RoleEnvironment/CurrentInstance/Endpoints/Endpoint[@name='DefaultCallControlEndpoint']/@port" />
|
||||
</Variable>
|
||||
<Variable name="PrivateInstanceCallControlPort">
|
||||
<RoleInstanceValue xpath="/RoleEnvironment/CurrentInstance/Endpoints/Endpoint[@name='InstanceCallControlEndpoint']/@port" />
|
||||
</Variable>
|
||||
<Variable name="InstanceIpAddress">
|
||||
<RoleInstanceValue xpath="/RoleEnvironment/CurrentInstance/Endpoints/Endpoint[@name='InstanceMediaControlEndpoint']/@address" />
|
||||
</Variable>
|
||||
<Variable name="DefaultCertificate">
|
||||
<RoleInstanceValue xpath="/RoleEnvironment/CurrentInstance/ConfigurationSettings/ConfigurationSetting[@name='DefaultCertificate']/@value" />
|
||||
</Variable>
|
||||
</Environment>
|
||||
</Task>
|
||||
<Task commandLine="InstallNETFX.cmd" executionContext="elevated" taskType="simple">
|
||||
<Environment>
|
||||
<Variable name="NetFxVersion" value="NDP461" />
|
||||
<Variable name="PathToNETFXInstall">
|
||||
<RoleInstanceValue xpath="/RoleEnvironment/CurrentInstance/LocalResources/LocalResource[@name='NETFXInstall']/@path" />
|
||||
</Variable>
|
||||
</Environment>
|
||||
</Task>
|
||||
</Startup>
|
||||
<ConfigurationSettings>
|
||||
<Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" />
|
||||
<Setting name="ServiceDnsName" />
|
||||
<Setting name="DefaultCertificate" />
|
||||
<Setting name="Skype.Bots.Speech.Subscription" />
|
||||
</ConfigurationSettings>
|
||||
<Endpoints>
|
||||
<InputEndpoint name="DefaultCallControlEndpoint" protocol="tcp" port="443" localPort="9440" />
|
||||
<InstanceInputEndpoint name="InstanceCallControlEndpoint" protocol="tcp" localPort="10100">
|
||||
<AllocatePublicPortFrom>
|
||||
<FixedPortRange max="10199" min="10100" />
|
||||
</AllocatePublicPortFrom>
|
||||
</InstanceInputEndpoint>
|
||||
<InstanceInputEndpoint name="InstanceMediaControlEndpoint" protocol="tcp" localPort="8445">
|
||||
<AllocatePublicPortFrom>
|
||||
<FixedPortRange max="20199" min="20100" />
|
||||
</AllocatePublicPortFrom>
|
||||
</InstanceInputEndpoint>
|
||||
</Endpoints>
|
||||
<LocalResources>
|
||||
<LocalStorage name="NETFXInstall" sizeInMB="1024" cleanOnRoleRecycle="false" />
|
||||
</LocalResources>
|
||||
<Certificates>
|
||||
<Certificate name="Default" storeLocation="LocalMachine" storeName="My" />
|
||||
</Certificates>
|
||||
<Imports>
|
||||
<Import moduleName="RemoteForwarder" />
|
||||
</Imports>
|
||||
</WorkerRole>
|
||||
</ServiceDefinition>
|
|
@ -0,0 +1,312 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Skype.Bots.Media;
|
||||
using Microsoft.WindowsAzure;
|
||||
using Microsoft.WindowsAzure.ServiceRuntime;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using FrontEnd;
|
||||
using FrontEnd.Http;
|
||||
using Microsoft.Azure;
|
||||
using FrontEnd.Logging;
|
||||
using Microsoft.IdentityModel.Protocols;
|
||||
using System.Configuration;
|
||||
|
||||
namespace WorkerRole
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads the configuration from service configuration
|
||||
/// </summary>
|
||||
internal class AzureConfiguration : IConfiguration
|
||||
{
|
||||
#region Fields
|
||||
private const string DefaultEndpointKey = "DefaultCallControlEndpoint";
|
||||
private const string InstanceCallControlEndpointKey = "InstanceCallControlEndpoint";
|
||||
private const string InstanceMediaControlEndpointKey = "InstanceMediaControlEndpoint";
|
||||
private const string ServiceDnsNameKey = "ServiceDnsName";
|
||||
private const string DefaultCertificateKey = "DefaultCertificate";
|
||||
private const string SpeechSubscriptionKey = "Skype.Bots.Speech.Subscription";
|
||||
private const string MicrosoftAppIdKey = "MicrosoftAppId";
|
||||
|
||||
//Prefix of the InstanceId from the RoleEnvironment
|
||||
private const string InstanceIdToken = "in_";
|
||||
|
||||
private static readonly AzureConfiguration s_Configuration = new AzureConfiguration();
|
||||
|
||||
/// <summary>
|
||||
/// DomainNameLabel in NetworkConfiguration in .cscfg <PublicIP name="instancePublicIP" domainNameLabel="pip"/>
|
||||
/// If the below changes, please change in the cscfg as well
|
||||
/// </summary>
|
||||
public const string DomainNameLabel = "pip";
|
||||
|
||||
/// <summary>
|
||||
/// localPort specified in <InputEndpoint name="DefaultCallControlEndpoint" protocol="tcp" port="443" localPort="9440" />
|
||||
/// in .csdef. This is needed for running in emulator. Media debugging in emulator will be supported in future releases.
|
||||
/// </summary>
|
||||
private const int DefaultPort = 9440;
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
public string ServiceDnsName { get; private set; }
|
||||
|
||||
public IEnumerable<Uri> CallControlListeningUrls { get; private set; }
|
||||
|
||||
public Uri CallControlCallbackUrl { get; private set; }
|
||||
|
||||
public Uri NotificationCallbackUrl { get; private set; }
|
||||
|
||||
public MediaPlatformSettings MediaPlatformSettings { get; private set; }
|
||||
|
||||
public string MicrosoftAppId { get; private set; }
|
||||
|
||||
public static AzureConfiguration Instance { get { return s_Configuration; } }
|
||||
|
||||
public string SpeechSubscription { get; private set; }
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
private AzureConfiguration()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize from serviceConfig
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
// Collect config values from Azure config.
|
||||
TraceEndpointInfo();
|
||||
ServiceDnsName = GetString(ServiceDnsNameKey);
|
||||
X509Certificate2 defaultCertificate = GetCertificateFromStore(DefaultCertificateKey);
|
||||
|
||||
RoleInstanceEndpoint instanceCallControlEndpoint = RoleEnvironment.IsEmulated ? null : GetEndpoint(InstanceCallControlEndpointKey);
|
||||
RoleInstanceEndpoint defaultEndpoint = GetEndpoint(DefaultEndpointKey);
|
||||
RoleInstanceEndpoint mediaControlEndpoint = RoleEnvironment.IsEmulated ? null : GetEndpoint(InstanceMediaControlEndpointKey);
|
||||
|
||||
int instanceCallControlInternalPort = RoleEnvironment.IsEmulated ? DefaultPort : instanceCallControlEndpoint.IPEndpoint.Port;
|
||||
string instanceCallControlInternalIpAddress = RoleEnvironment.IsEmulated
|
||||
? IPAddress.Loopback.ToString()
|
||||
: instanceCallControlEndpoint.IPEndpoint.Address.ToString();
|
||||
|
||||
int instanceCallControlPublicPort = RoleEnvironment.IsEmulated ? DefaultPort : instanceCallControlEndpoint.PublicIPEndpoint.Port;
|
||||
int mediaInstanceInternalPort = RoleEnvironment.IsEmulated ? 8445 : mediaControlEndpoint.IPEndpoint.Port;
|
||||
int mediaInstancePublicPort = RoleEnvironment.IsEmulated ? 20100 : mediaControlEndpoint.PublicIPEndpoint.Port;
|
||||
|
||||
string instanceCallControlIpEndpoint = string.Format("{0}:{1}", instanceCallControlInternalIpAddress, instanceCallControlInternalPort);
|
||||
|
||||
MicrosoftAppId = ConfigurationManager.AppSettings[MicrosoftAppIdKey];
|
||||
if (string.IsNullOrEmpty(MicrosoftAppId))
|
||||
{
|
||||
throw new ConfigurationException("MicrosoftAppId", "Key not found or empty value");
|
||||
}
|
||||
|
||||
// Create structured config objects for service.
|
||||
CallControlCallbackUrl = new Uri(string.Format(
|
||||
"https://{0}:{1}/{2}/{3}/",
|
||||
ServiceDnsName,
|
||||
instanceCallControlPublicPort,
|
||||
HttpRouteConstants.CallSignalingRoutePrefix,
|
||||
HttpRouteConstants.OnCallbackRoute));
|
||||
|
||||
NotificationCallbackUrl = new Uri(string.Format(
|
||||
"https://{0}:{1}/{2}/{3}/",
|
||||
ServiceDnsName,
|
||||
instanceCallControlPublicPort,
|
||||
HttpRouteConstants.CallSignalingRoutePrefix,
|
||||
HttpRouteConstants.OnNotificationRoute));
|
||||
|
||||
TraceConfigValue("CallControlCallbackUri", CallControlCallbackUrl);
|
||||
|
||||
List<Uri> controlListenUris = new List<Uri>
|
||||
{
|
||||
new Uri("https://" + instanceCallControlIpEndpoint + "/"),
|
||||
new Uri("https://" + defaultEndpoint.IPEndpoint + "/")
|
||||
};
|
||||
CallControlListeningUrls = controlListenUris;
|
||||
|
||||
foreach (Uri uri in CallControlListeningUrls)
|
||||
{
|
||||
TraceConfigValue("Call control listening Uri", uri);
|
||||
}
|
||||
|
||||
SpeechSubscription = GetString(SpeechSubscriptionKey);
|
||||
|
||||
IPAddress publicInstanceIpAddress = RoleEnvironment.IsEmulated
|
||||
? IPAddress.Loopback
|
||||
: GetInstancePublicIpAddress(ServiceDnsName);
|
||||
|
||||
MediaPlatformSettings = new MediaPlatformSettings()
|
||||
{
|
||||
MediaPlatformInstanceSettings = new MediaPlatformInstanceSettings()
|
||||
{
|
||||
CertificateThumbprint = defaultCertificate.Thumbprint,
|
||||
InstanceInternalPort = mediaInstanceInternalPort,
|
||||
InstancePublicIPAddress = publicInstanceIpAddress,
|
||||
InstancePublicPort = mediaInstancePublicPort,
|
||||
ServiceFqdn = ServiceDnsName
|
||||
},
|
||||
|
||||
ApplicationId = MicrosoftAppId
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose the configuration
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Helper methods
|
||||
private static void TraceEndpointInfo()
|
||||
{
|
||||
string[] endpoints = RoleEnvironment.IsEmulated
|
||||
? new string[] { DefaultEndpointKey }
|
||||
: new string[] { DefaultEndpointKey, InstanceMediaControlEndpointKey };
|
||||
|
||||
foreach (string endpointName in endpoints)
|
||||
{
|
||||
RoleInstanceEndpoint endpoint = GetEndpoint(endpointName);
|
||||
StringBuilder info = new StringBuilder();
|
||||
info.AppendFormat("Internal=https://{0}, ", endpoint.IPEndpoint);
|
||||
string publicInfo = endpoint.PublicIPEndpoint == null ? "-" : endpoint.PublicIPEndpoint.Port.ToString();
|
||||
info.AppendFormat("PublicPort={0}", publicInfo);
|
||||
TraceConfigValue(endpointName, info);
|
||||
}
|
||||
}
|
||||
|
||||
private static void TraceConfigValue(string key, object value)
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, "{0} -> {1}", key, value);
|
||||
}
|
||||
|
||||
private static RoleInstanceEndpoint GetEndpoint(string name)
|
||||
{
|
||||
RoleInstanceEndpoint endpoint;
|
||||
if (!RoleEnvironment.CurrentRoleInstance.InstanceEndpoints.TryGetValue(name, out endpoint))
|
||||
{
|
||||
throw new ConfigurationException(name, "No endpoint with name '{0}' was found.", name);
|
||||
}
|
||||
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
private static string GetString(string key, bool allowEmpty = false)
|
||||
{
|
||||
string s = CloudConfigurationManager.GetSetting(key);
|
||||
|
||||
TraceConfigValue(key, s);
|
||||
|
||||
if (!allowEmpty && string.IsNullOrWhiteSpace(s))
|
||||
{
|
||||
throw new ConfigurationException(key, "The configuration value is null or empty.");
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
private static List<string> GetStringList(string key)
|
||||
{
|
||||
return GetString(key).Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||
}
|
||||
|
||||
private static X509Certificate2 GetCertificateFromStore(string key)
|
||||
{
|
||||
string thumbprint = GetString(key);
|
||||
|
||||
X509Certificate2 cert;
|
||||
|
||||
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
|
||||
store.Open(OpenFlags.ReadOnly);
|
||||
try
|
||||
{
|
||||
X509Certificate2Collection certs = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, validOnly: false);
|
||||
if (certs.Count != 1)
|
||||
{
|
||||
throw new ConfigurationException(key, "No certificate with thumbprint {0} was found in the machine store.", thumbprint);
|
||||
}
|
||||
|
||||
cert = certs[0];
|
||||
}
|
||||
finally
|
||||
{
|
||||
store.Close();
|
||||
}
|
||||
|
||||
return cert;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the PIP for this instance
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private static IPAddress GetInstancePublicIpAddress(string publicFqdn)
|
||||
{
|
||||
int instanceNumber;
|
||||
//get the instanceId for the current instance. It will be of the form XXMediaBotRole_IN_0. Look for IN_ and then extract the number after it
|
||||
//Assumption: in_<instanceNumber> will the be the last in the instanceId
|
||||
string instanceId = RoleEnvironment.CurrentRoleInstance.Id;
|
||||
int instanceIdIndex = instanceId.IndexOf(InstanceIdToken, StringComparison.OrdinalIgnoreCase);
|
||||
if (!Int32.TryParse(instanceId.Substring(instanceIdIndex + InstanceIdToken.Length), out instanceNumber))
|
||||
{
|
||||
Log.Error(new CallerInfo(), LogContext.FrontEnd, "Couldn't extract Instance index from {0}", instanceId);
|
||||
throw new Exception("Couldn't extract Instance index from " + instanceId);
|
||||
}
|
||||
|
||||
//for example: instance0 for fooservice.cloudapp.net will have hostname as pip.0.fooservice.cloudapp.net
|
||||
string instanceHostName = DomainNameLabel + "." + instanceNumber + "." + publicFqdn;
|
||||
IPAddress[] instanceAddresses = Dns.GetHostEntry(instanceHostName).AddressList;
|
||||
if(instanceAddresses.Length == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Could not resolve the PIP hostname. Please make sure that PIP is properly configured for the service");
|
||||
}
|
||||
return instanceAddresses[0];
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exception thrown when the configuration is not correct
|
||||
/// </summary>
|
||||
internal sealed class ConfigurationException : Exception
|
||||
{
|
||||
internal ConfigurationException(string parameter, string message, params object[] args)
|
||||
: base(string.Format(message, args))
|
||||
{
|
||||
Parameter = parameter;
|
||||
}
|
||||
|
||||
public string Parameter { get; private set; }
|
||||
|
||||
public override string Message
|
||||
{
|
||||
get
|
||||
{
|
||||
return string.Format(
|
||||
"Parameter name: {0}\r\n{1}",
|
||||
Parameter,
|
||||
base.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("Parameter name: {0}\r\n{1}", Parameter, base.ToString());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
# Error handling
|
||||
trap
|
||||
{
|
||||
Write-Output "Error Hit: $_"
|
||||
Write-Output "Error File: $($_.InvocationInfo.ScriptName)"
|
||||
Write-Output "Error Line #: $($_.InvocationInfo.ScriptLineNumber)"
|
||||
Write-Output ""
|
||||
Write-Output "Exception: $($_.Exception)"
|
||||
Write-Output ""
|
||||
Write-Output "Exception.InnerException: $($_.Exception.InnerException)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Output "Checking if Media Foundation is installed"
|
||||
if((Get-WindowsFeature Server-Media-Foundation).Installed -eq 0)
|
||||
{
|
||||
Write-Output "Installing Media Foundation."
|
||||
Add-WindowsFeature Server-Media-Foundation
|
||||
|
||||
Write-Output "Rebooting VM for changes to take effect."
|
||||
Restart-Computer
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
REM Code Courtesy https://azure.microsoft.com/en-us/documentation/articles/cloud-services-dotnet-install-dotnet/
|
||||
REM Set the value of netfx to install appropriate .NET Framework.
|
||||
REM ***** To install .NET 4.5.2 set the variable netfx to "NDP452" *****
|
||||
REM ***** To install .NET 4.6 set the variable netfx to "NDP46" *****
|
||||
REM ***** To install .NET 4.6.1 set the variable netfx to "NDP461" *****
|
||||
set netfx="%NetFxVersion%"
|
||||
|
||||
REM ***** Needed to correctly install .NET 4.6.1, otherwise you may see an out of disk space error *****
|
||||
set TMP=%PathToNETFXInstall%
|
||||
set TEMP=%PathToNETFXInstall%
|
||||
|
||||
REM ***** Setup .NET filenames and registry keys *****
|
||||
if %netfx%=="NDP461" goto NDP461
|
||||
if %netfx%=="NDP46" goto NDP46
|
||||
set netfxinstallfile="NDP452-KB2901954-Web.exe"
|
||||
set netfxregkey="0x5cbf5"
|
||||
goto logtimestamp
|
||||
|
||||
:NDP46
|
||||
set netfxinstallfile="NDP46-KB3045560-Web.exe"
|
||||
set netfxregkey="0x60051"
|
||||
goto logtimestamp
|
||||
|
||||
:NDP461
|
||||
set netfxinstallfile="NDP461-KB3102438-Web.exe"
|
||||
set netfxregkey="0x6041f"
|
||||
|
||||
:logtimestamp
|
||||
REM ***** Setup LogFile with timestamp *****
|
||||
set timehour=%time:~0,2%
|
||||
set timestamp=%date:~-4,4%%date:~-10,2%%date:~-7,2%-%timehour: =0%%time:~3,2%
|
||||
md "%PathToNETFXInstall%\log"
|
||||
set startuptasklog="%PathToNETFXInstall%log\startuptasklog-%timestamp%.txt"
|
||||
set netfxinstallerlog="%PathToNETFXInstall%log\NetFXInstallerLog-%timestamp%"
|
||||
|
||||
echo Logfile generated at: %startuptasklog% >> %startuptasklog%
|
||||
echo TMP set to: %TMP% >> %startuptasklog%
|
||||
echo TEMP set to: %TEMP% >> %startuptasklog%
|
||||
|
||||
REM ***** Check if .NET is installed *****
|
||||
echo Checking if .NET (%netfx%) is installed >> %startuptasklog%
|
||||
reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" /v Release | Find %netfxregkey%
|
||||
if %ERRORLEVEL%== 0 goto end
|
||||
|
||||
REM ***** Installing .NET *****
|
||||
echo Installing .NET: start /wait %~dp0%netfxinstallfile% /q /serialdownload /log %netfxinstallerlog% >> %startuptasklog%
|
||||
start /wait %~dp0%netfxinstallfile% /q /serialdownload /log %netfxinstallerlog% >> %startuptasklog% 2>>&1
|
||||
|
||||
:end
|
||||
echo install.cmd completed: %date:~-4,4%%date:~-10,2%%date:~-7,2%-%timehour: =0%%time:~3,2% >> %startuptasklog%
|
Двоичный файл не отображается.
|
@ -0,0 +1,42 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("WorkerRole")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("WorkerRole")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2016")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("9f593a7b-8935-4bc8-9fbb-c3a9e3bc2a23")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
|
@ -0,0 +1,3 @@
|
|||
echo Launching Powershell to install Media Foundation
|
||||
PowerShell -ExecutionPolicy Unrestricted .\InstallMediaFoundation.ps1 >> InstallMediaFoundation.log 2>&1
|
||||
EXIT /B 0
|
|
@ -0,0 +1,131 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.WindowsAzure.ServiceRuntime;
|
||||
using FrontEnd.Logging;
|
||||
using FrontEnd;
|
||||
|
||||
namespace WorkerRole
|
||||
{
|
||||
public class WorkerRole : RoleEntryPoint
|
||||
{
|
||||
private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
|
||||
private readonly ManualResetEvent runCompleteEvent = new ManualResetEvent(false);
|
||||
|
||||
/// <summary>
|
||||
/// Keep the service running until OnStop is called
|
||||
/// </summary>
|
||||
public override void Run()
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, "WorkerRole is running");
|
||||
|
||||
try
|
||||
{
|
||||
this.RunAsync(this.cancellationTokenSource.Token).Wait();
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.runCompleteEvent.Set();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize and start the service when workerrole is started
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override bool OnStart()
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
// Wire up exception handling for unhandled exceptions (bugs).
|
||||
AppDomain.CurrentDomain.UnhandledException += this.OnAppDomainUnhandledException;
|
||||
TaskScheduler.UnobservedTaskException += this.OnUnobservedTaskException;
|
||||
|
||||
// Set the maximum number of concurrent connections
|
||||
ServicePointManager.DefaultConnectionLimit = 12;
|
||||
AzureConfiguration.Instance.Initialize();
|
||||
|
||||
// Create and start the environment-independent service.
|
||||
Service.Instance.Initialize(AzureConfiguration.Instance);
|
||||
Service.Instance.Start();
|
||||
|
||||
bool result = base.OnStart();
|
||||
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, "WorkerRole has been started");
|
||||
|
||||
return result;
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
Log.Error(new CallerInfo(), LogContext.FrontEnd, "Exception on startup: {0}", e.ToString());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleanup when WorkerRole is stopped
|
||||
/// </summary>
|
||||
public override void OnStop()
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, "WorkerRole is stopping");
|
||||
|
||||
this.cancellationTokenSource.Cancel();
|
||||
this.runCompleteEvent.WaitOne();
|
||||
|
||||
base.OnStop();
|
||||
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, "WorkerRole has stopped");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(new CallerInfo(), LogContext.FrontEnd, "Exception on shutdown: {0}", e.ToString());
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
AppDomain.CurrentDomain.UnhandledException -= this.OnAppDomainUnhandledException;
|
||||
TaskScheduler.UnobservedTaskException -= this.OnUnobservedTaskException;
|
||||
Log.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RunAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log UnObservedTaskExceptions
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
|
||||
{
|
||||
Log.Error(new CallerInfo(), LogContext.FrontEnd, "Unobserved task exception: " + e.Exception.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log any unhandled exceptions that are raised in the service
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void OnAppDomainUnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
Log.Error(new CallerInfo(), FrontEnd.Logging.LogContext.FrontEnd, "Unhandled exception: " + e.ExceptionObject.ToString());
|
||||
Log.Flush(); // process may or may not be terminating so flush log just in case.
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,402 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\packages\Bond.CSharp.4.2.1\build\Bond.CSharp.props" Condition="Exists('..\packages\Bond.CSharp.4.2.1\build\Bond.CSharp.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProductVersion>8.0.30703</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{9F593A7B-8935-4BC8-9FBB-C3A9E3BC2A23}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>WorkerRole</RootNamespace>
|
||||
<AssemblyName>WorkerRole</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<RoleType>Worker</RoleType>
|
||||
<TargetFrameworkProfile />
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Autofac, Version=3.5.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Autofac.3.5.2\lib\net40\Autofac.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Bond, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Bond.Core.CSharp.4.2.1\lib\net45\Bond.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Bond.Attributes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Bond.Core.CSharp.4.2.1\lib\net45\Bond.Attributes.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Bond.IO, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Bond.Core.CSharp.4.2.1\lib\net45\Bond.IO.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Bond.JSON, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Bond.Runtime.CSharp.4.2.1\lib\net45\Bond.JSON.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Chronic, Version=0.3.2.0, Culture=neutral, PublicKeyToken=3bd1f1ef638b0d3c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Chronic.Signed.0.3.2\lib\net40\Chronic.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Bing.Messaging, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Bing.Speech.2.0.2\lib\net45\Microsoft.Bing.Messaging.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Bing.Speech, Version=2.0.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Bing.Speech.2.0.2\lib\net45\Microsoft.Bing.Speech.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Bot.Builder, Version=3.5.8.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Bot.Builder.3.5.8.0\lib\net46\Microsoft.Bot.Builder.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Bot.Builder.Autofac, Version=3.5.8.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Bot.Builder.3.5.8.0\lib\net46\Microsoft.Bot.Builder.Autofac.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Bot.Builder.Calling, Version=3.0.4.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Bot.Builder.Calling.3.0.4\lib\net46\Microsoft.Bot.Builder.Calling.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Bot.Builder.RealTimeMediaCalling, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Bot.Builder.RealTimeMediaCalling.1.0.2-alpha\lib\net46\Microsoft.Bot.Builder.RealTimeMediaCalling.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Bot.Connector, Version=3.5.8.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Bot.Builder.3.5.8.0\lib\net46\Microsoft.Bot.Connector.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Data.Edm, Version=5.6.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Data.Edm.5.6.2\lib\net40\Microsoft.Data.Edm.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Data.OData, Version=5.6.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Data.OData.5.6.2\lib\net40\Microsoft.Data.OData.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Data.Services.Client, Version=5.6.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Data.Services.Client.5.6.2\lib\net40\Microsoft.Data.Services.Client.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.IdentityModel.Protocol.Extensions, Version=1.0.2.33, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.IdentityModel.Protocol.Extensions.1.0.2.206221351\lib\net45\Microsoft.IdentityModel.Protocol.Extensions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin.Host.HttpListener, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin.Hosting, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Owin.Hosting.3.0.1\lib\net45\Microsoft.Owin.Hosting.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin.Security, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin.Security.Jwt, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Owin.Security.Jwt.3.0.1\lib\net45\Microsoft.Owin.Security.Jwt.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin.Security.OAuth, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Owin.Security.OAuth.3.0.1\lib\net45\Microsoft.Owin.Security.OAuth.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin.Security.OpenIdConnect, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Owin.Security.OpenIdConnect.3.0.1\lib\net45\Microsoft.Owin.Security.OpenIdConnect.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Rest.ClientRuntime, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Rest.ClientRuntime.2.3.2\lib\net45\Microsoft.Rest.ClientRuntime.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Skype.Bots.Media, Version=1.5.0.1177, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=AMD64">
|
||||
<HintPath>..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\lib\Microsoft.Skype.Bots.Media.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<SpecificVersion>True</SpecificVersion>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.VisualStudio.Threading, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.VisualStudio.Threading.14.1.131\lib\net45\Microsoft.VisualStudio.Threading.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.VisualStudio.Validation, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.VisualStudio.Validation.14.1.111\lib\net45\Microsoft.VisualStudio.Validation.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.WindowsAzure.Configuration, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.1\lib\net40\Microsoft.WindowsAzure.Configuration.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.WindowsAzure.Diagnostics, Version=2.8.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.WindowsAzure.ServiceRuntime, Version=2.7.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.WindowsAzure.Storage, Version=4.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\WindowsAzure.Storage.4.3.0\lib\net40\Microsoft.WindowsAzure.Storage.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.IdentityModel" />
|
||||
<Reference Include="System.IdentityModel.Tokens.Jwt, Version=4.0.20622.1351, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.IdentityModel.Tokens.Jwt.4.0.2.206221351\lib\net45\System.IdentityModel.Tokens.Jwt.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.IdentityModel.Tokens.ValidatingIssuerNameRegistry, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.IdentityModel.Tokens.ValidatingIssuerNameRegistry.4.5.1\lib\net45\System.IdentityModel.Tokens.ValidatingIssuerNameRegistry.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Net" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Net.Http.Formatting, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Net.Http.WebRequest" />
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.Spatial, Version=5.6.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Spatial.5.6.2\lib\net40\System.Spatial.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Threading.Tasks.Dataflow, Version=4.5.25.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Threading.Tasks.Dataflow.4.5.25\lib\dotnet\System.Threading.Tasks.Dataflow.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Web.Http, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Web.Http.Owin, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="AzureConfiguration.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="WorkerRole.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\InstallMPServiceImpCounters.ps1">
|
||||
<Link>skype_media_lib\InstallMPServiceImpCounters.ps1</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\MediaPerf.ini">
|
||||
<Link>skype_media_lib\MediaPerf.ini</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\MediaPlatformStartupScript.bat">
|
||||
<Link>skype_media_lib\MediaPlatformStartupScript.bat</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\TraceFormat\default.tmx">
|
||||
<Link>skype_media_lib\TraceFormat\default.tmx</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\TraceFormat\default_media.tmx">
|
||||
<Link>skype_media_lib\TraceFormat\default_media.tmx</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="app.config" />
|
||||
<None Include="InstallMediaFoundation.ps1">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="InstallNETFX.cmd">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="packages.config">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
<None Include="SetupMediaFoundation.cmd">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="startup.cmd">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\EtlReader.dll">
|
||||
<Link>skype_media_lib\EtlReader.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\MediaPerf.dll">
|
||||
<Link>skype_media_lib\MediaPerf.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\MediaPerf.h">
|
||||
<Link>skype_media_lib\MediaPerf.h</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Microsoft.Applications.Telemetry.dll">
|
||||
<Link>skype_media_lib\Microsoft.Applications.Telemetry.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Microsoft.Applications.Telemetry.Server.dll">
|
||||
<Link>skype_media_lib\Microsoft.Applications.Telemetry.Server.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Microsoft.Bond.dll">
|
||||
<Link>skype_media_lib\Microsoft.Bond.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Microsoft.Bond.Interfaces.dll">
|
||||
<Link>skype_media_lib\Microsoft.Bond.Interfaces.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Microsoft.Rtc.ClsContracts.dll">
|
||||
<Link>skype_media_lib\Microsoft.Rtc.ClsContracts.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Microsoft.Rtc.ClsLite.dll">
|
||||
<Link>skype_media_lib\Microsoft.Rtc.ClsLite.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Microsoft.Rtc.Internal.Media.dll">
|
||||
<Link>skype_media_lib\Microsoft.Rtc.Internal.Media.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Microsoft.Rtc.Internal.Media.MediaApi.dll">
|
||||
<Link>skype_media_lib\Microsoft.Rtc.Internal.Media.MediaApi.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Microsoft.Skype.ECS.Client.dll">
|
||||
<Link>skype_media_lib\Microsoft.Skype.ECS.Client.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\MPAzAppHost.dll">
|
||||
<Link>skype_media_lib\MPAzAppHost.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\MPServiceHostLib.dll">
|
||||
<Link>skype_media_lib\MPServiceHostLib.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\MPServiceImp.dll">
|
||||
<Link>skype_media_lib\MPServiceImp.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\rtmcodecs.dll">
|
||||
<Link>skype_media_lib\rtmcodecs.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\RtmMvrCs.dll">
|
||||
<Link>skype_media_lib\RtmMvrCs.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\rtmpal.dll">
|
||||
<Link>skype_media_lib\rtmpal.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Skype.ECS.Criteria.dll">
|
||||
<Link>skype_media_lib\Skype.ECS.Criteria.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Skype.ECS.Utilities.dll">
|
||||
<Link>skype_media_lib\Skype.ECS.Utilities.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\SkypeRT.dll">
|
||||
<Link>skype_media_lib\SkypeRT.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\TraceFormat\default.xml">
|
||||
<Link>skype_media_lib\TraceFormat\default.xml</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<Content Include="NDP461-KB3102438-Web.exe">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\FrontEnd\FrontEnd.csproj">
|
||||
<Project>{bd841814-05f3-498d-834d-cc7afe2a8f1a}</Project>
|
||||
<Name>FrontEnd</Name>
|
||||
<SpecificVersion>True</SpecificVersion>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
<PropertyGroup>
|
||||
<!-- Setting AutoUnifyAssemblyReferences to false will allow the ResolveAssemblyReferences task to
|
||||
create warnings when detecting version missmatches among references.
|
||||
-->
|
||||
<AutoUnifyAssemblyReferences>false</AutoUnifyAssemblyReferences>
|
||||
</PropertyGroup>
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\packages\Bond.CSharp.4.2.1\build\Bond.CSharp.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Bond.CSharp.4.2.1\build\Bond.CSharp.props'))" />
|
||||
<Error Condition="!Exists('..\packages\Bond.CSharp.4.2.1\build\Bond.CSharp.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Bond.CSharp.4.2.1\build\Bond.CSharp.targets'))" />
|
||||
<Error Condition="!Exists('..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\build\Microsoft.Skype.Bots.Media.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\build\Microsoft.Skype.Bots.Media.targets'))" />
|
||||
</Target>
|
||||
<Import Project="..\packages\Bond.CSharp.4.2.1\build\Bond.CSharp.targets" Condition="Exists('..\packages\Bond.CSharp.4.2.1\build\Bond.CSharp.targets')" />
|
||||
<Import Project="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\build\Microsoft.Skype.Bots.Media.targets" Condition="Exists('..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\build\Microsoft.Skype.Bots.Media.targets')" />
|
||||
</Project>
|
|
@ -0,0 +1,85 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<system.diagnostics>
|
||||
<sources>
|
||||
<source name="FrontEnd" switchValue="Verbose">
|
||||
<listeners>
|
||||
<add name="AzureDiagnostics" />
|
||||
</listeners>
|
||||
</source>
|
||||
<source name="Media" switchValue="Verbose">
|
||||
<listeners>
|
||||
<add name="AzureDiagnostics" />
|
||||
</listeners>
|
||||
</source>
|
||||
<source name="AuthToken" switchValue="Verbose">
|
||||
<listeners>
|
||||
<add name="AzureDiagnostics" />
|
||||
</listeners>
|
||||
</source>
|
||||
</sources>
|
||||
|
||||
<sharedListeners>
|
||||
<add type="Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitorTraceListener, Microsoft.WindowsAzure.Diagnostics, Version=2.8.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" name="AzureDiagnostics">
|
||||
<filter type="" />
|
||||
</add>
|
||||
</sharedListeners>
|
||||
|
||||
<switches>
|
||||
<add name="logLevel" value="4" />
|
||||
</switches>
|
||||
<trace autoflush="false" indentsize="4">
|
||||
<listeners>
|
||||
<add type="Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitorTraceListener, Microsoft.WindowsAzure.Diagnostics, Version=2.8.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" name="AzureDiagnostics">
|
||||
</add>
|
||||
</listeners>
|
||||
</trace>
|
||||
</system.diagnostics>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" /></startup>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.IdentityModel.Tokens.Jwt" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.20622.1351" newVersion="4.0.20622.1351" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.IdentityModel.Logging" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-1.0.0.127" newVersion="1.0.0.127" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.IdentityModel.Protocol.Extensions" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-1.0.2.33" newVersion="1.0.2.33" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Threading.Tasks.Dataflow" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.5.25.0" newVersion="4.5.25.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Skype.ECS.Client" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-1.0.131.0" newVersion="1.0.131.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Applications.Telemetry.Server" publicKeyToken="ffe01d0f25e9b4f1" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-1.1.152.0" newVersion="1.1.152.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Applications.Telemetry" publicKeyToken="ffe01d0f25e9b4f1" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-1.1.152.0" newVersion="1.1.152.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
<appSettings>
|
||||
<!-- update these with your BotId, Microsoft App Id and your Microsoft App Password from your bot registration portal-->
|
||||
<add key="BotId" value="$BotHandle$" />
|
||||
<add key="MicrosoftAppId" value="$MicrosoftAppId$" />
|
||||
<add key="MicrosoftAppPassword" value="$BotSecret$" />
|
||||
</appSettings>
|
||||
</configuration>
|
|
@ -0,0 +1,49 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Autofac" version="3.5.2" targetFramework="net461" />
|
||||
<package id="Bond.Core.CSharp" version="4.2.1" targetFramework="net461" />
|
||||
<package id="Bond.CSharp" version="4.2.1" targetFramework="net461" />
|
||||
<package id="Bond.Runtime.CSharp" version="4.2.1" targetFramework="net461" />
|
||||
<package id="Chronic.Signed" version="0.3.2" targetFramework="net461" />
|
||||
<package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net461" />
|
||||
<package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net461" />
|
||||
<package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net461" />
|
||||
<package id="Microsoft.Bing.Speech" version="2.0.2" targetFramework="net461" />
|
||||
<package id="Microsoft.Bot.Builder" version="3.5.8.0" targetFramework="net461" />
|
||||
<package id="Microsoft.Bot.Builder.Calling" version="3.0.4" targetFramework="net461" />
|
||||
<package id="Microsoft.Bot.Builder.RealTimeMediaCalling" version="1.0.2-alpha" targetFramework="net461" />
|
||||
<package id="Microsoft.Data.Edm" version="5.6.2" targetFramework="net452" />
|
||||
<package id="Microsoft.Data.OData" version="5.6.2" targetFramework="net452" />
|
||||
<package id="Microsoft.Data.Services.Client" version="5.6.2" targetFramework="net452" />
|
||||
<package id="Microsoft.IdentityModel.Protocol.Extensions" version="1.0.2.206221351" targetFramework="net461" />
|
||||
<package id="Microsoft.Owin" version="3.0.1" targetFramework="net461" />
|
||||
<package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net461" />
|
||||
<package id="Microsoft.Owin.Hosting" version="3.0.1" targetFramework="net461" />
|
||||
<package id="Microsoft.Owin.Security" version="3.0.1" targetFramework="net461" />
|
||||
<package id="Microsoft.Owin.Security.Jwt" version="3.0.1" targetFramework="net461" />
|
||||
<package id="Microsoft.Owin.Security.OAuth" version="3.0.1" targetFramework="net461" />
|
||||
<package id="Microsoft.Owin.Security.OpenIdConnect" version="3.0.1" targetFramework="net461" />
|
||||
<package id="Microsoft.Rest.ClientRuntime" version="2.3.2" targetFramework="net461" />
|
||||
<package id="Microsoft.Skype.Bots.Media" version="1.5.0.1177-alpha" targetFramework="net461" />
|
||||
<package id="Microsoft.VisualStudio.Threading" version="14.1.131" targetFramework="net461" />
|
||||
<package id="Microsoft.VisualStudio.Validation" version="14.1.111" targetFramework="net461" />
|
||||
<package id="Microsoft.WindowsAzure.ConfigurationManager" version="3.2.1" targetFramework="net461" />
|
||||
<package id="Newtonsoft.Json" version="8.0.3" targetFramework="net461" />
|
||||
<package id="Owin" version="1.0" targetFramework="net461" />
|
||||
<package id="System.Collections" version="4.0.0" targetFramework="net461" />
|
||||
<package id="System.Collections.Concurrent" version="4.0.0" targetFramework="net461" />
|
||||
<package id="System.Diagnostics.Debug" version="4.0.0" targetFramework="net461" />
|
||||
<package id="System.Diagnostics.Tracing" version="4.0.0" targetFramework="net461" />
|
||||
<package id="System.Dynamic.Runtime" version="4.0.0" targetFramework="net461" />
|
||||
<package id="System.IdentityModel.Tokens.Jwt" version="4.0.2.206221351" targetFramework="net461" />
|
||||
<package id="System.IdentityModel.Tokens.ValidatingIssuerNameRegistry" version="4.5.1" targetFramework="net461" />
|
||||
<package id="System.Linq" version="4.0.0" targetFramework="net461" />
|
||||
<package id="System.Resources.ResourceManager" version="4.0.0" targetFramework="net461" />
|
||||
<package id="System.Runtime" version="4.0.0" targetFramework="net461" />
|
||||
<package id="System.Runtime.Extensions" version="4.0.0" targetFramework="net461" />
|
||||
<package id="System.Spatial" version="5.6.2" targetFramework="net452" />
|
||||
<package id="System.Threading" version="4.0.0" targetFramework="net461" />
|
||||
<package id="System.Threading.Tasks" version="4.0.0" targetFramework="net461" />
|
||||
<package id="System.Threading.Tasks.Dataflow" version="4.5.25" targetFramework="net461" />
|
||||
<package id="WindowsAzure.Storage" version="4.3.0" targetFramework="net452" />
|
||||
</packages>
|
|
@ -0,0 +1,26 @@
|
|||
REM --- Move to this scripts location ---
|
||||
pushd "%~dp0"
|
||||
|
||||
REM --- Print out environment variables for debugging ---
|
||||
set
|
||||
|
||||
REM--- Register media perf dlls ---
|
||||
powershell .\skype_media_lib\MediaPlatformStartupScript.bat
|
||||
|
||||
REM --- Delete existing certificate bindings and URL ACL registrations ---
|
||||
netsh http delete sslcert ipport=%InstanceIpAddress%:%PrivateDefaultCallControlPort%
|
||||
netsh http delete sslcert ipport=%InstanceIpAddress%:%PrivateInstanceCallControlPort%
|
||||
netsh http delete urlacl url=https://%InstanceIpAddress%:%PrivateDefaultCallControlPort%/
|
||||
netsh http delete urlacl url=https://%InstanceIpAddress%:%PrivateInstanceCallControlPort%/
|
||||
|
||||
REM --- Delete new URL ACLs and certificate bindings ---
|
||||
netsh http add urlacl url=https://%InstanceIpAddress%:%PrivateDefaultCallControlPort%/ user="NT AUTHORITY\NETWORK SERVICE"
|
||||
netsh http add urlacl url=https://%InstanceIpAddress%:%PrivateInstanceCallControlPort%/ user="NT AUTHORITY\NETWORK SERVICE"
|
||||
netsh http add sslcert ipport=%InstanceIpAddress%:%PrivateDefaultCallControlPort% clientcertnegotiation=enable "appid={00000000-0000-0000-0000-000000000001}" cert=%DefaultCertificate%
|
||||
netsh http add sslcert ipport=%InstanceIpAddress%:%PrivateInstanceCallControlPort% clientcertnegotiation=enable "appid={00000000-0000-0000-0000-000000000001}" cert=%DefaultCertificate%
|
||||
|
||||
REM --- Disable strong-name validation. This should not be needed once all external binaries are properly signed ---
|
||||
REGEDIT /S %~dp0\DisableStrongNameVerification.reg
|
||||
|
||||
popd
|
||||
exit /b 0
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<DiagnosticsConfiguration xmlns="http://schemas.microsoft.com/ServiceHosting/2010/10/DiagnosticsConfiguration">
|
||||
<PublicConfig xmlns="http://schemas.microsoft.com/ServiceHosting/2010/10/DiagnosticsConfiguration">
|
||||
<WadCfg>
|
||||
<DiagnosticMonitorConfiguration overallQuotaInMB="4096">
|
||||
<DiagnosticInfrastructureLogs scheduledTransferLogLevelFilter="Verbose" />
|
||||
<Directories scheduledTransferPeriod="PT1M">
|
||||
<IISLogs containerName="wad-iis-logfiles" />
|
||||
<FailedRequestLogs containerName="wad-failedrequestlogs" />
|
||||
</Directories>
|
||||
<PerformanceCounters scheduledTransferPeriod="PT1M">
|
||||
<PerformanceCounterConfiguration counterSpecifier="\Memory\Available MBytes" sampleRate="PT3M" />
|
||||
<PerformanceCounterConfiguration counterSpecifier="\Web Service(_Total)\ISAPI Extension Requests/sec" sampleRate="PT3M" />
|
||||
<PerformanceCounterConfiguration counterSpecifier="\Web Service(_Total)\Bytes Total/Sec" sampleRate="PT3M" />
|
||||
<PerformanceCounterConfiguration counterSpecifier="\ASP.NET Applications(__Total__)\Requests/Sec" sampleRate="PT3M" />
|
||||
<PerformanceCounterConfiguration counterSpecifier="\ASP.NET Applications(__Total__)\Errors Total/Sec" sampleRate="PT3M" />
|
||||
<PerformanceCounterConfiguration counterSpecifier="\ASP.NET\Requests Queued" sampleRate="PT3M" />
|
||||
<PerformanceCounterConfiguration counterSpecifier="\ASP.NET\Requests Rejected" sampleRate="PT3M" />
|
||||
<PerformanceCounterConfiguration counterSpecifier="\Processor(_Total)\% Processor Time" sampleRate="PT3M" />
|
||||
</PerformanceCounters>
|
||||
<WindowsEventLog scheduledTransferPeriod="PT1M">
|
||||
<DataSource name="Application!*[System[(Level=1 or Level=2 or Level=3)]]" />
|
||||
<DataSource name="Windows Azure!*[System[(Level=1 or Level=2 or Level=3 or Level=4)]]" />
|
||||
</WindowsEventLog>
|
||||
<CrashDumps dumpType="Full">
|
||||
<CrashDumpConfiguration processName="WaAppAgent.exe" />
|
||||
<CrashDumpConfiguration processName="WaIISHost.exe" />
|
||||
<CrashDumpConfiguration processName="WindowsAzureGuestAgent.exe" />
|
||||
<CrashDumpConfiguration processName="WaWorkerHost.exe" />
|
||||
<CrashDumpConfiguration processName="DiagnosticsAgent.exe" />
|
||||
<CrashDumpConfiguration processName="w3wp.exe" />
|
||||
</CrashDumps>
|
||||
<Logs scheduledTransferPeriod="PT1M" scheduledTransferLogLevelFilter="Verbose" />
|
||||
</DiagnosticMonitorConfiguration>
|
||||
</WadCfg>
|
||||
<StorageAccount />
|
||||
</PublicConfig>
|
||||
<PrivateConfig xmlns="http://schemas.microsoft.com/ServiceHosting/2010/10/DiagnosticsConfiguration">
|
||||
<StorageAccount endpoint="" />
|
||||
</PrivateConfig>
|
||||
<IsEnabled>true</IsEnabled>
|
||||
</DiagnosticsConfiguration>
|
|
@ -0,0 +1,263 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using CorrelationId = FrontEnd.Logging.CorrelationId;
|
||||
using FrontEnd.Logging;
|
||||
using Microsoft.Skype.Bots.Media;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FrontEnd.CallLogic
|
||||
{
|
||||
/// <summary>
|
||||
/// This class handles media related logic for a call.
|
||||
/// </summary>
|
||||
internal class MediaSession : IDisposable
|
||||
{
|
||||
#region Fields
|
||||
/// <summary>
|
||||
/// Msi when there is no dominant speaker
|
||||
/// </summary>
|
||||
public const uint DominantSpeaker_None = DominantSpeakerChangedEventArgs.None;
|
||||
|
||||
/// <summary>
|
||||
/// Unique correlationID of this particular call.
|
||||
/// </summary>
|
||||
private readonly string _correlationId;
|
||||
|
||||
/// <summary>
|
||||
/// The audio socket created for this particular call.
|
||||
/// </summary>
|
||||
private readonly AudioSocket _audioSocket;
|
||||
|
||||
/// <summary>
|
||||
/// The video based screen sharing socket created for this particular call.
|
||||
/// </summary>
|
||||
private readonly VideoSocket _videoSocket;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the call has been disposed
|
||||
/// </summary>
|
||||
private int _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// The time stamp when video image was updated last
|
||||
/// </summary>
|
||||
private DateTime _lastVideoCapturedTimeUtc = DateTime.MinValue;
|
||||
|
||||
/// <summary>
|
||||
/// The time between each video frame capturing
|
||||
/// </summary>
|
||||
private TimeSpan VideoCaptureFrequency = TimeSpan.FromMilliseconds(1000);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
/// <summary>
|
||||
/// The current Video image
|
||||
/// </summary>
|
||||
public Bitmap CurrentVideoImage { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Id of this call.
|
||||
/// </summary>
|
||||
public string Id { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The opaque media configuration object sent back to the Skype platform when answering a call.
|
||||
/// </summary>
|
||||
public JObject MediaConfiguration { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of IRealTimeMediaCall that handles incoming and outgoing requests
|
||||
/// </summary>
|
||||
public readonly RealTimeMediaCall RealTimeMediaCall;
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
/// <summary>
|
||||
/// Create a new instance of the MediaSession.
|
||||
/// </summary>
|
||||
public MediaSession(string id, string correlationId, RealTimeMediaCall call)
|
||||
{
|
||||
_correlationId = correlationId;
|
||||
this.Id = id;
|
||||
RealTimeMediaCall = call;
|
||||
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"[{this.Id}]: Call created");
|
||||
|
||||
try
|
||||
{
|
||||
_audioSocket = new AudioSocket(new AudioSocketSettings
|
||||
{
|
||||
StreamDirections = StreamDirection.Recvonly,
|
||||
SupportedAudioFormat = AudioFormat.Pcm16K, // audio format is currently fixed at PCM 16 KHz.
|
||||
CallId = correlationId
|
||||
});
|
||||
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"[{this.Id}]:Created AudioSocket");
|
||||
|
||||
// video socket
|
||||
_videoSocket = new VideoSocket(new VideoSocketSettings
|
||||
{
|
||||
StreamDirections = StreamDirection.Recvonly,
|
||||
ReceiveColorFormat = VideoColorFormat.NV12,
|
||||
CallId = correlationId
|
||||
});
|
||||
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"[{this.Id}]: Created VideoSocket");
|
||||
|
||||
//audio socket events
|
||||
_audioSocket.DominantSpeakerChanged += OnDominantSpeakerChanged;
|
||||
|
||||
//Video socket events
|
||||
_videoSocket.VideoMediaReceived += OnVideoMediaReceived;
|
||||
|
||||
MediaConfiguration = MediaPlatform.CreateMediaConfiguration(_audioSocket, _videoSocket);
|
||||
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"[{this.Id}]: MediaConfiguration={MediaConfiguration.ToString(Formatting.Indented)}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(new CallerInfo(), LogContext.FrontEnd, "Error in MediaSession creation" + ex.ToString());
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribes from all audio/video send/receive-related events, cancels tasks and disposes sockets
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _disposed, 1, 0) == 0)
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"[{this.Id}]: Disposing Call");
|
||||
|
||||
if (_audioSocket != null)
|
||||
{
|
||||
_audioSocket.DominantSpeakerChanged -= OnDominantSpeakerChanged;
|
||||
_audioSocket.Dispose();
|
||||
}
|
||||
|
||||
if (_videoSocket != null)
|
||||
{
|
||||
_videoSocket.VideoMediaReceived -= OnVideoMediaReceived;
|
||||
_videoSocket.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(new CallerInfo(), LogContext.FrontEnd, $"[{this.Id}]: Ignoring exception in dispose {ex}");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Audio
|
||||
/// <summary>
|
||||
/// Listen for dominant speaker changes in the conference
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void OnDominantSpeakerChanged(object sender, DominantSpeakerChangedEventArgs e)
|
||||
{
|
||||
CorrelationId.SetCurrentId(_correlationId);
|
||||
Log.Info(
|
||||
new CallerInfo(),
|
||||
LogContext.Media,
|
||||
$"[{this.Id}:OnDominantSpeakerChanged(DominantSpeaker={e.CurrentDominantSpeaker})]"
|
||||
);
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await RealTimeMediaCall.Subscribe(e.CurrentDominantSpeaker, true).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(new CallerInfo(), LogContext.FrontEnd, $"[{this.Id}]: Ignoring exception in subscribe {ex}");
|
||||
}
|
||||
});
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Video
|
||||
private void OnVideoMediaReceived(object sender, VideoMediaReceivedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
CorrelationId.SetCurrentId(_correlationId);
|
||||
|
||||
if (DateTime.Now > this._lastVideoCapturedTimeUtc + this.VideoCaptureFrequency)
|
||||
{
|
||||
// Update the last capture timestamp
|
||||
this._lastVideoCapturedTimeUtc = DateTime.Now;
|
||||
|
||||
Log.Info(
|
||||
new CallerInfo(),
|
||||
LogContext.Media,
|
||||
"[{0}]: Capturing image: [VideoMediaReceivedEventArgs(Data=<{1}>, Length={2}, Timestamp={3}, Width={4}, Height={5}, ColorFormat={6}, FrameRate={7})]",
|
||||
this.Id,
|
||||
e.Buffer.Data.ToString(),
|
||||
e.Buffer.Length,
|
||||
e.Buffer.Timestamp,
|
||||
e.Buffer.VideoFormat.Width,
|
||||
e.Buffer.VideoFormat.Height,
|
||||
e.Buffer.VideoFormat.VideoColorFormat,
|
||||
e.Buffer.VideoFormat.FrameRate);
|
||||
|
||||
// Make a copy of the media buffer
|
||||
Stopwatch watch = new Stopwatch();
|
||||
watch.Start();
|
||||
|
||||
byte[] buffer = new byte[e.Buffer.Length];
|
||||
Marshal.Copy(e.Buffer.Data, buffer, 0, (int)e.Buffer.Length);
|
||||
|
||||
VideoMediaBuffer videoRenderMediaBuffer = e.Buffer as VideoMediaBuffer;
|
||||
|
||||
IntPtr ptrToBuffer = Marshal.AllocHGlobal(buffer.Length);
|
||||
Marshal.Copy(buffer, 0, ptrToBuffer, buffer.Length);
|
||||
|
||||
watch.Stop();
|
||||
Log.Info(new CallerInfo(), LogContext.Media, $"{this.Id} Took {watch.ElapsedMilliseconds} ms to copy buffer");
|
||||
|
||||
// Transform to bitmap object
|
||||
Bitmap bmpObject = MediaUtils.TransformNV12ToBmpFaster(buffer, e.Buffer.VideoFormat.Width, e.Buffer.VideoFormat.Height);
|
||||
|
||||
bool sendChatMessage = (CurrentVideoImage == null);
|
||||
Log.Info(new CallerInfo(), LogContext.Media, $"{this.Id} send chat message {sendChatMessage}");
|
||||
|
||||
// 3. Update the bitmap cache
|
||||
CurrentVideoImage = bmpObject;
|
||||
|
||||
if (sendChatMessage)
|
||||
{
|
||||
Task.Run(async () => {
|
||||
try
|
||||
{
|
||||
await RealTimeMediaCall.SendMessageForCall(RealTimeMediaCall);
|
||||
}catch(Exception ex)
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"Exception in SendingChatMessage {ex}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(new CallerInfo(), LogContext.Media, $"{this.Id} Exception in VideoMediaReceived {ex.ToString()}");
|
||||
}
|
||||
|
||||
e.Buffer.Dispose();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using FrontEnd.Logging;
|
||||
|
||||
namespace FrontEnd
|
||||
{
|
||||
/// <summary>
|
||||
/// Media related utility class
|
||||
/// </summary>
|
||||
public class MediaUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// Transform NV12 to bmp image so we can view how is it looks like. Note it's not NV12 to RBG conversion.
|
||||
/// </summary>
|
||||
/// <param name="data">NV12 sample data</param>
|
||||
/// <param name="width">Image width</param>
|
||||
/// <param name="height">Image height</param>
|
||||
public static Bitmap TransformNV12ToBmpFaster(byte[] data, int width = 1280, int height = 720)
|
||||
{
|
||||
int stride = (int)(4 * (((width * (Image.GetPixelFormatSize(PixelFormat.Format32bppRgb) / 8)) + 3) / 4));
|
||||
|
||||
Stopwatch watch = new Stopwatch();
|
||||
watch.Start();
|
||||
|
||||
Bitmap bmp = new Bitmap((int)width, (int)height, PixelFormat.Format32bppRgb);
|
||||
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppRgb);
|
||||
|
||||
unsafe
|
||||
{
|
||||
byte* ptr = (byte*)bmpData.Scan0;
|
||||
int uvStart = (int)width * (int)height;
|
||||
for (int y = 0; y < (int)height; y++)
|
||||
{
|
||||
int currentLine = y * bmpData.Stride;
|
||||
for (int x = 0; x < (int)width; x++)
|
||||
{
|
||||
int vIndex = uvStart + (y >> 1) * (int)width + ((x >> 1) << 1);
|
||||
byte grayscale = data[x + (y * width)];
|
||||
byte blue = (byte)(data[vIndex] + grayscale - 127);
|
||||
byte red = (byte)(data[vIndex + 1] + grayscale - 127);
|
||||
byte green = (byte)(1.704 * grayscale - 0.510 * red - 0.194 * blue);
|
||||
|
||||
ptr[(x * 4) + y * stride] = blue;
|
||||
ptr[(x * 4) + y * stride + 1] = green;
|
||||
ptr[(x * 4) + y * stride + 2] = red;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bmp.UnlockBits(bmpData);
|
||||
|
||||
watch.Stop();
|
||||
Log.Info(new CallerInfo(), LogContext.Media, "Took {0} ms to lock and unlock", watch.ElapsedMilliseconds);
|
||||
|
||||
return bmp;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,370 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FrontEnd.Logging;
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Contracts;
|
||||
using Microsoft.Bot.Builder.Calling.ObjectModel.Misc;
|
||||
using Microsoft.Bot.Builder.RealTimeMediaCalling;
|
||||
using Microsoft.Bot.Builder.RealTimeMediaCalling.Events;
|
||||
using Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Contracts;
|
||||
using Microsoft.Bot.Connector;
|
||||
using FrontEnd.Http;
|
||||
|
||||
namespace FrontEnd.CallLogic
|
||||
{
|
||||
/// <summary>
|
||||
/// This class does all the signaling needed to handle a call.
|
||||
/// </summary>
|
||||
internal class RealTimeMediaCall : IRealTimeMediaCall
|
||||
{
|
||||
/// <summary>
|
||||
/// Container for the current active calls.
|
||||
/// </summary>
|
||||
private static ConcurrentDictionary<string, RealTimeMediaCall> ActiveMediaCalls;
|
||||
|
||||
/// <summary>
|
||||
/// The MediaStreamId of the participant to which the video channel is currently subscribed to
|
||||
/// </summary>
|
||||
private RosterParticipant _subscribedToParticipant;
|
||||
|
||||
/// <summary>
|
||||
/// Roster to get the video msi of the dominant speaker
|
||||
/// </summary>
|
||||
private IEnumerable<RosterParticipant> _participants;
|
||||
|
||||
/// <summary>
|
||||
/// MediaSession that handles media related details
|
||||
/// </summary>
|
||||
public MediaSession MediaSession { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ThreadId corresponding to this call to tie with chat messages for this conversation
|
||||
/// </summary>
|
||||
public string ThreadId;
|
||||
|
||||
/// <summary>
|
||||
/// Service that helps parse incoming requests and provide corresponding events
|
||||
/// </summary>
|
||||
public IRealTimeMediaCallService CallService { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Id generated locally that is unique to each RealTimeMediaCall
|
||||
/// </summary>
|
||||
public readonly string CallId;
|
||||
|
||||
/// <summary>
|
||||
/// CorrelationId that needs to be set in the media platform for correlating logs across services
|
||||
/// </summary>
|
||||
public readonly string CorrelationId;
|
||||
|
||||
static RealTimeMediaCall()
|
||||
{
|
||||
ActiveMediaCalls = new ConcurrentDictionary<string, RealTimeMediaCall>();
|
||||
}
|
||||
|
||||
public RealTimeMediaCall(IRealTimeMediaCallService callService)
|
||||
{
|
||||
if (callService == null)
|
||||
throw new ArgumentNullException(nameof(callService));
|
||||
|
||||
CallService = callService;
|
||||
CorrelationId = callService.CorrelationId;
|
||||
CallId = CorrelationId + ":" + Guid.NewGuid().ToString();
|
||||
|
||||
//Register for the events
|
||||
CallService.OnIncomingCallReceived += OnIncomingCallReceived;
|
||||
CallService.OnAnswerAppHostedMediaCompleted += OnAnswerAppHostedMediaCompleted;
|
||||
CallService.OnCallStateChangeNotification += OnCallStateChangeNotification;
|
||||
CallService.OnRosterUpdateNotification += OnRosterUpdateNotification;
|
||||
CallService.OnCallCleanup += OnCallCleanup;
|
||||
}
|
||||
|
||||
private Task OnIncomingCallReceived(RealTimeMediaIncomingCallEvent incomingCallEvent)
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"[{CallId}] OnIncomingCallReceived");
|
||||
|
||||
//handles the media for this call like creating sockets, registering for events on the socket and sending/receiving media
|
||||
MediaSession = new MediaSession(CallId, CorrelationId, this);
|
||||
incomingCallEvent.RealTimeMediaWorkflow.Actions = new ActionBase[]
|
||||
{
|
||||
new AnswerAppHostedMedia
|
||||
{
|
||||
MediaConfiguration = MediaSession.MediaConfiguration,
|
||||
OperationId = Guid.NewGuid().ToString()
|
||||
}
|
||||
};
|
||||
|
||||
//subscribe for roster changes and call state changes
|
||||
incomingCallEvent.RealTimeMediaWorkflow.NotificationSubscriptions = new NotificationType[] { NotificationType.RosterUpdate, NotificationType.CallStateChange};
|
||||
ThreadId = incomingCallEvent.IncomingCall.ThreadId;
|
||||
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"[{CallId}] Answering the call");
|
||||
|
||||
ActiveMediaCalls.AddOrUpdate(CallId, this, (callId, oldcall) => this);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task OnAnswerAppHostedMediaCompleted(AnswerAppHostedMediaOutcomeEvent answerAppHostedMediaOutcomeEvent)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"[{CallId}] OnAnswerAppHostedMediaCompleted");
|
||||
AnswerAppHostedMediaOutcome answerAppHostedMediaOutcome = answerAppHostedMediaOutcomeEvent.AnswerAppHostedMediaOutcome;
|
||||
if (answerAppHostedMediaOutcome.Outcome == Outcome.Failure)
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"[{CallId}] AnswerAppHostedMedia failed with reason: {answerAppHostedMediaOutcome.FailureReason}");
|
||||
//cleanup internal resources
|
||||
MediaSession.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
answerAppHostedMediaOutcomeEvent.RealTimeMediaWorkflow.NotificationSubscriptions = new NotificationType[] { NotificationType.CallStateChange, NotificationType.RosterUpdate };
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
} catch (Exception ex)
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"[{CallId}] threw {ex.ToString()}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnRosterUpdateNotification(RosterUpdateNotification rosterUpdateNotification)
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"[{CallId}] OnRosterUpdateNotification");
|
||||
_participants = rosterUpdateNotification.Participants;
|
||||
|
||||
uint prevSubscribedMsi = (_subscribedToParticipant == null) ? MediaSession.DominantSpeaker_None
|
||||
: Convert.ToUInt32(_subscribedToParticipant.MediaStreamId);
|
||||
await Subscribe(prevSubscribedMsi, false).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This gets called when the user hangs up the call to the bot
|
||||
/// </summary>
|
||||
/// <param name="callStateChangeNotification"></param>
|
||||
/// <returns></returns>
|
||||
private Task OnCallStateChangeNotification(CallStateChangeNotification callStateChangeNotification)
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"[{CallId}] Received CallStateChangeNotification with AudioVideoCallStateType={callStateChangeNotification.CurrentState.ToString()}");
|
||||
|
||||
if (callStateChangeNotification.CurrentState == CallState.Terminated)
|
||||
{
|
||||
//cleanup the media session that disposes sockets, etc
|
||||
MediaSession.Dispose();
|
||||
RealTimeMediaCall temp;
|
||||
ActiveMediaCalls.TryRemove(CallId, out temp);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When the IRealTimeMediaCallService detects an error and cleans up the call locally
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private Task OnCallCleanup()
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"[{CallId}] Received OnCallCleanup");
|
||||
if (MediaSession != null)
|
||||
{
|
||||
MediaSession.Dispose();
|
||||
}
|
||||
|
||||
RealTimeMediaCall temp;
|
||||
ActiveMediaCalls.TryRemove(CallId, out temp);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to the video stream of a participant. This function is called when dominant speaker notification is received and when roster changes
|
||||
/// When invoked on dominant speaker change, look if the participant is sharing their video. If yes then subscribe else choose the first participant in the list sharing their video
|
||||
/// When invoked on roster change, verify if the previously subscribed-to participant is still in the roster and sending video
|
||||
/// </summary>
|
||||
/// <param name="msi">Msi of dominant speaker or previously subscribed to msi depending on where it is invoked</param>
|
||||
/// <param name="msiOfDominantSpeaker">Gives more detail on the above msi.. Whether it is of dominant speaker or previously subscribed to video msi</param>
|
||||
/// <returns></returns>
|
||||
internal async Task Subscribe(uint msi, bool msiOfDominantSpeaker)
|
||||
{
|
||||
try
|
||||
{
|
||||
RosterParticipant participant;
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"[{CallId}] Received subscribe request for Msi {msi} msiOfDominantSpeaker {msiOfDominantSpeaker}");
|
||||
if(msiOfDominantSpeaker)
|
||||
{
|
||||
participant = GetParticipantWithDominantSpeakerMsi(msi);
|
||||
}
|
||||
else
|
||||
{
|
||||
participant = GetParticipantForRosterChange(msi);
|
||||
}
|
||||
|
||||
if (participant == null)
|
||||
{
|
||||
_subscribedToParticipant = null;
|
||||
return;
|
||||
}
|
||||
|
||||
//if we have already subscribed earlier, skip the subscription
|
||||
if (_subscribedToParticipant != null && _subscribedToParticipant.MediaStreamId == participant.MediaStreamId)
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"[{CallId}] Already subscribed to {participant.Identity}. So skipping subscription");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"[{CallId}] Subscribing to {participant.Identity} with msi {participant.MediaStreamId}");
|
||||
|
||||
//Get subscription details
|
||||
var videoSubscription = new VideoSubscription
|
||||
{
|
||||
ParticipantIdentity = participant.Identity,
|
||||
OperationId = Guid.NewGuid().ToString(),
|
||||
SocketId = 0, //index of the VideoSocket in MediaConfiguration which receives the incoming video stream
|
||||
VideoModality = ModalityType.Video,
|
||||
VideoResolution = ResolutionFormat.Hd1080p
|
||||
};
|
||||
|
||||
await CallService.Subscribe(videoSubscription).ConfigureAwait(false);
|
||||
_subscribedToParticipant = participant;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Log.Error(new CallerInfo(), LogContext.FrontEnd, $"[{CallId}] Subscribe threw exception {ex.ToString()}");
|
||||
_subscribedToParticipant = null;
|
||||
}
|
||||
}
|
||||
|
||||
private RosterParticipant GetParticipantWithDominantSpeakerMsi(uint dominantSpeakerMsi)
|
||||
{
|
||||
RosterParticipant firstParticipant = null;
|
||||
if (_participants == null)
|
||||
{
|
||||
Log.Warning(new CallerInfo(), LogContext.FrontEnd, $"[{CallId}] Did not receive rosterupdate notification yet");
|
||||
return null;
|
||||
}
|
||||
|
||||
string dominantSpeakerIdentity = string.Empty;
|
||||
foreach (RosterParticipant participant in _participants)
|
||||
{
|
||||
if (participant.MediaStreamId == dominantSpeakerMsi.ToString())
|
||||
{
|
||||
//identify
|
||||
if (participant.MediaType == ModalityType.Audio)
|
||||
{
|
||||
dominantSpeakerIdentity = participant.Identity;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (participant.MediaType != ModalityType.Video
|
||||
|| !(participant.MediaStreamDirection == "sendonly" || participant.MediaStreamDirection == "sendrecv")
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//cache the first participant.. just incase dominant speaker is not sending video
|
||||
if (firstParticipant == null)
|
||||
{
|
||||
firstParticipant = participant;
|
||||
if (dominantSpeakerMsi == MediaSession.DominantSpeaker_None)
|
||||
{
|
||||
return firstParticipant;
|
||||
}
|
||||
}
|
||||
|
||||
//The dominant speaker is sending video..
|
||||
if (participant.Identity == dominantSpeakerIdentity)
|
||||
{
|
||||
return participant;
|
||||
}
|
||||
}
|
||||
|
||||
//If dominant speaker is not sending video or if dominant speaker has exited the conference, choose the first participant sending video
|
||||
return firstParticipant;
|
||||
}
|
||||
|
||||
private RosterParticipant GetParticipantForRosterChange(uint msi)
|
||||
{
|
||||
RosterParticipant firstParticipant = null;
|
||||
if (_participants == null)
|
||||
{
|
||||
Log.Warning(new CallerInfo(), LogContext.FrontEnd, $"[{CallId}] Did not receive rosterupdate notification yet");
|
||||
return null;
|
||||
}
|
||||
|
||||
string dominantSpeakerIdentity = string.Empty;
|
||||
foreach (RosterParticipant participant in _participants)
|
||||
{
|
||||
if (participant.MediaStreamId == msi.ToString())
|
||||
{
|
||||
if (participant.MediaType == ModalityType.Video && (participant.MediaStreamDirection == "sendonly" || participant.MediaStreamDirection == "sendrecv"))
|
||||
{
|
||||
return participant;
|
||||
}
|
||||
}
|
||||
|
||||
if (participant.MediaType != ModalityType.Video
|
||||
|| !(participant.MediaStreamDirection == "sendonly" || participant.MediaStreamDirection == "sendrecv")
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (firstParticipant == null)
|
||||
{
|
||||
firstParticipant = participant;
|
||||
}
|
||||
}
|
||||
|
||||
//No dominant speaker or dominant speaker is not sending video or if old dominant speaker has exited the conference, choose a new oe
|
||||
return firstParticipant;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Debug: Get a list of the active mediasessions
|
||||
/// </summary>
|
||||
/// <returns>List of current call identifiers.</returns>
|
||||
public static IList<string> GetActiveRealTimeMediaCalls()
|
||||
{
|
||||
return ActiveMediaCalls.Values.Select(x => x.CallId).ToList();
|
||||
}
|
||||
|
||||
internal static RealTimeMediaCall GetCallForCallId(string callId)
|
||||
{
|
||||
return ActiveMediaCalls.Values.FirstOrDefault(x => (x.CallId == callId));
|
||||
}
|
||||
|
||||
internal static async Task SendUrlForConversationId(string threadId)
|
||||
{
|
||||
RealTimeMediaCall mediaCall = ActiveMediaCalls.Values.FirstOrDefault(x => (x.ThreadId == threadId));
|
||||
if (mediaCall == null)
|
||||
{
|
||||
Log.Warning(new CallerInfo(), LogContext.FrontEnd, $"No active mediacall for {threadId}");
|
||||
return;
|
||||
}
|
||||
await SendMessageForCall(mediaCall);
|
||||
}
|
||||
|
||||
internal static async Task SendMessageForCall(RealTimeMediaCall mediaCall)
|
||||
{
|
||||
string url = $"{Service.Instance.Configuration.AzureInstanceBaseUrl}/{HttpRouteConstants.CallSignalingRoutePrefix}/{HttpRouteConstants.Image}";
|
||||
url = url.Replace("{callid}", mediaCall.CallId);
|
||||
try
|
||||
{
|
||||
await MessageSender.SendMessage(mediaCall.ThreadId, url);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Log.Error(new CallerInfo(), LogContext.FrontEnd, $"[{mediaCall.CallId}] Exception in sending chat {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
using System.Drawing;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using FrontEnd.Http;
|
||||
using FrontEnd.Logging;
|
||||
|
||||
namespace FrontEnd.CallLogic
|
||||
{
|
||||
public static class VideoImageViewer
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the image for a particular call
|
||||
/// </summary>
|
||||
/// <param name="conversationResult"></param>
|
||||
/// <returns></returns>
|
||||
public static HttpResponseMessage GetVideoImageResponse(string callId)
|
||||
{
|
||||
RealTimeMediaCall mediaCall = RealTimeMediaCall.GetCallForCallId(callId);
|
||||
if (mediaCall == null)
|
||||
{
|
||||
return new HttpResponseMessage(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
MediaSession mediaSession = mediaCall.MediaSession;
|
||||
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"[{callId}] Received request to retrieve image for call");
|
||||
Bitmap bitmap = mediaSession.CurrentVideoImage;
|
||||
if (bitmap == null)
|
||||
{
|
||||
return new HttpResponseMessage(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
|
||||
response.Content = new PushStreamContent((targetStream, httpContext, transportContext) =>
|
||||
{
|
||||
using (targetStream)
|
||||
{
|
||||
bitmap.Save(targetStream, System.Drawing.Imaging.ImageFormat.Jpeg);
|
||||
}
|
||||
}, new MediaTypeHeaderValue("image/jpeg"));
|
||||
|
||||
response.Headers.Add("Cache-Control", "private,must-revalidate,post-check=1,pre-check=2,no-cache");
|
||||
|
||||
string url = $"{Service.Instance.Configuration.AzureInstanceBaseUrl}/{HttpRouteConstants.CallSignalingRoutePrefix}/{HttpRouteConstants.Image}";
|
||||
url = url.Replace("{callid}", callId);
|
||||
response.Headers.Add("Refresh", $"3; url={url}");
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,348 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\packages\Bond.CSharp.4.2.1\build\Bond.CSharp.props" Condition="Exists('..\packages\Bond.CSharp.4.2.1\build\Bond.CSharp.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{E0FC03A1-FD3C-43BD-BAA0-1AA418EC6DCB}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>FrontEnd</RootNamespace>
|
||||
<AssemblyName>FrontEnd</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||
<DefineConstants>TRACE;DEBUG</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Autofac, Version=3.5.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Autofac.3.5.2\lib\net40\Autofac.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Bond, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Bond.Core.CSharp.4.2.1\lib\net45\Bond.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Bond.Attributes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Bond.Core.CSharp.4.2.1\lib\net45\Bond.Attributes.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Bond.IO, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Bond.Core.CSharp.4.2.1\lib\net45\Bond.IO.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Bond.JSON, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Bond.Runtime.CSharp.4.2.1\lib\net45\Bond.JSON.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Chronic, Version=0.3.2.0, Culture=neutral, PublicKeyToken=3bd1f1ef638b0d3c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Chronic.Signed.0.3.2\lib\net40\Chronic.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Bot.Builder, Version=3.5.8.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Bot.Builder.3.5.8.0\lib\net46\Microsoft.Bot.Builder.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Bot.Builder.Autofac, Version=3.5.8.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Bot.Builder.3.5.8.0\lib\net46\Microsoft.Bot.Builder.Autofac.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Bot.Builder.Calling, Version=3.0.4.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Bot.Builder.Calling.3.0.4\lib\net46\Microsoft.Bot.Builder.Calling.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Bot.Builder.RealTimeMediaCalling, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Bot.Builder.RealTimeMediaCalling.1.0.2-alpha\lib\net46\Microsoft.Bot.Builder.RealTimeMediaCalling.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Bot.Connector, Version=3.5.8.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Bot.Builder.3.5.8.0\lib\net46\Microsoft.Bot.Connector.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.IdentityModel.Protocol.Extensions, Version=1.0.2.33, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.IdentityModel.Protocol.Extensions.1.0.2.206221351\lib\net45\Microsoft.IdentityModel.Protocol.Extensions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin.Host.HttpListener, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin.Hosting, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Owin.Hosting.3.0.1\lib\net45\Microsoft.Owin.Hosting.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin.Security, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin.Security.OAuth, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Owin.Security.OAuth.3.0.1\lib\net45\Microsoft.Owin.Security.OAuth.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin.Security.OpenIdConnect, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Owin.Security.OpenIdConnect.3.0.1\lib\net45\Microsoft.Owin.Security.OpenIdConnect.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Rest.ClientRuntime, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Rest.ClientRuntime.2.3.2\lib\net45\Microsoft.Rest.ClientRuntime.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Skype.Bots.Media, Version=1.5.0.1177, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=AMD64">
|
||||
<HintPath>..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\lib\Microsoft.Skype.Bots.Media.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<SpecificVersion>True</SpecificVersion>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.VisualStudio.Threading, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.VisualStudio.Threading.14.1.131\lib\net45\Microsoft.VisualStudio.Threading.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.VisualStudio.Validation, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.VisualStudio.Validation.14.1.111\lib\net45\Microsoft.VisualStudio.Validation.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.WindowsAzure.Configuration, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.1\lib\net40\Microsoft.WindowsAzure.Configuration.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.IdentityModel" />
|
||||
<Reference Include="System.IdentityModel.Tokens.Jwt, Version=4.0.20622.1351, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.IdentityModel.Tokens.Jwt.4.0.2.206221351\lib\net45\System.IdentityModel.Tokens.Jwt.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Net" />
|
||||
<Reference Include="System.Net.Http.Formatting, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Net.Http.WebRequest" />
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.Threading.Tasks.Dataflow, Version=4.5.25.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Threading.Tasks.Dataflow.4.5.25\lib\dotnet\System.Threading.Tasks.Dataflow.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Web.Http, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Web.Http.Owin, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="CallLogic\RealTimeMediaCall.cs" />
|
||||
<Compile Include="CallLogic\MediaSession.cs" />
|
||||
<Compile Include="CallLogic\MediaUtils.cs" />
|
||||
<Compile Include="CallLogic\VideoImageViewer.cs" />
|
||||
<Compile Include="Http\CallEndpointStartup.cs" />
|
||||
<Compile Include="Http\RealTimeMediaCallingBotServiceSettings.cs" />
|
||||
<Compile Include="Http\CallController.cs" />
|
||||
<Compile Include="Http\ExceptionLogger.cs" />
|
||||
<Compile Include="Http\HttpRouteConstants.cs" />
|
||||
<Compile Include="Http\LoggingMessageHandler.cs" />
|
||||
<Compile Include="Http\MessagesController.cs" />
|
||||
<Compile Include="Http\MessageSender.cs" />
|
||||
<Compile Include="IConfiguration.cs" />
|
||||
<Compile Include="Logging\CallerInfo.cs" />
|
||||
<Compile Include="Logging\CorrelationId.cs" />
|
||||
<Compile Include="Logging\Log.cs" />
|
||||
<Compile Include="MediaLogic\AudioSendBuffer.cs" />
|
||||
<Compile Include="MediaLogic\VideoSendBuffer.cs" />
|
||||
<Compile Include="Service.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\InstallMPServiceImpCounters.ps1">
|
||||
<Link>skype_media_lib\InstallMPServiceImpCounters.ps1</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\MediaPerf.ini">
|
||||
<Link>skype_media_lib\MediaPerf.ini</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\MediaPlatformStartupScript.bat">
|
||||
<Link>skype_media_lib\MediaPlatformStartupScript.bat</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\TraceFormat\default.tmx">
|
||||
<Link>skype_media_lib\TraceFormat\default.tmx</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\TraceFormat\default_media.tmx">
|
||||
<Link>skype_media_lib\TraceFormat\default_media.tmx</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="app.config" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\EtlReader.dll">
|
||||
<Link>skype_media_lib\EtlReader.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\MediaPerf.dll">
|
||||
<Link>skype_media_lib\MediaPerf.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\MediaPerf.h">
|
||||
<Link>skype_media_lib\MediaPerf.h</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Microsoft.Applications.Telemetry.dll">
|
||||
<Link>skype_media_lib\Microsoft.Applications.Telemetry.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Microsoft.Applications.Telemetry.Server.dll">
|
||||
<Link>skype_media_lib\Microsoft.Applications.Telemetry.Server.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Microsoft.Bond.dll">
|
||||
<Link>skype_media_lib\Microsoft.Bond.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Microsoft.Bond.Interfaces.dll">
|
||||
<Link>skype_media_lib\Microsoft.Bond.Interfaces.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Microsoft.Rtc.ClsContracts.dll">
|
||||
<Link>skype_media_lib\Microsoft.Rtc.ClsContracts.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Microsoft.Rtc.ClsLite.dll">
|
||||
<Link>skype_media_lib\Microsoft.Rtc.ClsLite.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Microsoft.Rtc.Internal.Media.dll">
|
||||
<Link>skype_media_lib\Microsoft.Rtc.Internal.Media.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Microsoft.Rtc.Internal.Media.MediaApi.dll">
|
||||
<Link>skype_media_lib\Microsoft.Rtc.Internal.Media.MediaApi.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Microsoft.Skype.ECS.Client.dll">
|
||||
<Link>skype_media_lib\Microsoft.Skype.ECS.Client.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\MPAzAppHost.dll">
|
||||
<Link>skype_media_lib\MPAzAppHost.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\MPServiceHostLib.dll">
|
||||
<Link>skype_media_lib\MPServiceHostLib.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\MPServiceImp.dll">
|
||||
<Link>skype_media_lib\MPServiceImp.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\rtmcodecs.dll">
|
||||
<Link>skype_media_lib\rtmcodecs.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\RtmMvrCs.dll">
|
||||
<Link>skype_media_lib\RtmMvrCs.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\rtmpal.dll">
|
||||
<Link>skype_media_lib\rtmpal.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Skype.ECS.Criteria.dll">
|
||||
<Link>skype_media_lib\Skype.ECS.Criteria.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\Skype.ECS.Utilities.dll">
|
||||
<Link>skype_media_lib\Skype.ECS.Utilities.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\SkypeRT.dll">
|
||||
<Link>skype_media_lib\SkypeRT.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\src\skype_media_lib\TraceFormat\default.xml">
|
||||
<Link>skype_media_lib\TraceFormat\default.xml</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\packages\Bond.CSharp.4.2.1\build\Bond.CSharp.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Bond.CSharp.4.2.1\build\Bond.CSharp.props'))" />
|
||||
<Error Condition="!Exists('..\packages\Bond.CSharp.4.2.1\build\Bond.CSharp.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Bond.CSharp.4.2.1\build\Bond.CSharp.targets'))" />
|
||||
<Error Condition="!Exists('..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\build\Microsoft.Skype.Bots.Media.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\build\Microsoft.Skype.Bots.Media.targets'))" />
|
||||
</Target>
|
||||
<Import Project="..\packages\Bond.CSharp.4.2.1\build\Bond.CSharp.targets" Condition="Exists('..\packages\Bond.CSharp.4.2.1\build\Bond.CSharp.targets')" />
|
||||
<Import Project="..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\build\Microsoft.Skype.Bots.Media.targets" Condition="Exists('..\packages\Microsoft.Skype.Bots.Media.1.5.0.1177-alpha\build\Microsoft.Skype.Bots.Media.targets')" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
|
@ -0,0 +1,121 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http;
|
||||
using FrontEnd.CallLogic;
|
||||
using FrontEnd.Logging;
|
||||
using Microsoft.Bot.Builder.RealTimeMediaCalling;
|
||||
using Microsoft.Bot.Connector;
|
||||
|
||||
namespace FrontEnd.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// CallContoller is the enty point for handling incoming call signaling HTTP requests from Skype platform.
|
||||
/// </summary>
|
||||
[RoutePrefix(HttpRouteConstants.CallSignalingRoutePrefix)]
|
||||
public class CallController : ApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// Instantiate a CallController with a specific ICallProcessor (e.g. for testing).
|
||||
/// </summary>
|
||||
/// <param name="callProcessor"></param>
|
||||
static CallController()
|
||||
{
|
||||
RealTimeMediaCalling.RegisterRealTimeMediaCallingBot(c => { return new RealTimeMediaCall(c); },
|
||||
new RealTimeMediaCallingBotServiceSettings()
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle an incoming call.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Route(HttpRouteConstants.OnIncomingCallRoute)]
|
||||
[BotAuthentication]
|
||||
public async Task<HttpResponseMessage> OnIncomingCallAsync()
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"Received HTTP {Request.Method}, {Request.RequestUri}");
|
||||
|
||||
var response = await RealTimeMediaCalling.SendAsync(Request, RealTimeMediaCallRequestType.IncomingCall).ConfigureAwait(false);
|
||||
|
||||
// Enforce the connection close to ensure that requests are evenly load balanced so
|
||||
// calls do no stick to one instance of the worker role.
|
||||
response.Headers.ConnectionClose = true;
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle a callback for an existing call.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Route(HttpRouteConstants.OnCallbackRoute)]
|
||||
[BotAuthentication]
|
||||
public async Task<HttpResponseMessage> OnCallbackAsync()
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"Received HTTP {Request.Method}, {Request.RequestUri}");
|
||||
|
||||
//let the RealTimeMediaCalling SDK know of the incoming callback. The SDK deserializes the callback, validates it and calls the appropriate
|
||||
//events on the IRealTimeMediaCall for this request
|
||||
var response = await RealTimeMediaCalling.SendAsync(Request, RealTimeMediaCallRequestType.CallingEvent).ConfigureAwait(false);
|
||||
|
||||
// Enforce the connection close to ensure that requests are evenly load balanced so
|
||||
// calls do no stick to one instance of the worker role.
|
||||
response.Headers.ConnectionClose = true;
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle a notification for an existing call.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Route(HttpRouteConstants.OnNotificationRoute)]
|
||||
[BotAuthentication]
|
||||
public async Task<HttpResponseMessage> OnNotificationAsync()
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"Notification Received HTTP {Request.Method} on {Request.RequestUri}");
|
||||
|
||||
//let the RealTimeMediaCalling SDK know of the incoming notification. The SDK deserializes the callback, validates it and calls the appropriate
|
||||
//events on the IRealTimeMediaCall for this request
|
||||
var response = await RealTimeMediaCalling.SendAsync(Request, RealTimeMediaCallRequestType.NotificationEvent).ConfigureAwait(false);
|
||||
|
||||
// Enforce the connection close to ensure that requests are evenly load balanced so
|
||||
// calls do no stick to one instance of the worker role.
|
||||
response.Headers.ConnectionClose = true;
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the image of screen sharing.
|
||||
/// </summary>
|
||||
/// <param name="callid">Id of the call to retrieve image</param>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
[Route(HttpRouteConstants.Image)]
|
||||
public HttpResponseMessage OnGetImage(string callid)
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"Retrieving image for call id {callid}");
|
||||
|
||||
try
|
||||
{
|
||||
return VideoImageViewer.GetVideoImageResponse(callid);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Warning(new CallerInfo(), LogContext.FrontEnd, $"[OnGetImage] Exception {e.ToString()}");
|
||||
var response = new HttpResponseMessage(HttpStatusCode.InternalServerError);
|
||||
response.Content = new StringContent(e.ToString());
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.ExceptionHandling;
|
||||
using FrontEnd.Logging;
|
||||
using Owin;
|
||||
using Microsoft.Bot.Builder.RealTimeMediaCalling.ObjectModel.Misc;
|
||||
|
||||
namespace FrontEnd.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// Initialize the httpConfiguration for OWIN
|
||||
/// </summary>
|
||||
public class CallEndpointStartup
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration settings like Auth, Routes for OWIN
|
||||
/// </summary>
|
||||
/// <param name="app"></param>
|
||||
|
||||
public void Configuration(IAppBuilder app)
|
||||
{
|
||||
HttpConfiguration httpConfig = new HttpConfiguration();
|
||||
httpConfig.MapHttpAttributeRoutes();
|
||||
httpConfig.MessageHandlers.Add(new LoggingMessageHandler(isIncomingMessageHandler: true, logContext: LogContext.FrontEnd));
|
||||
|
||||
httpConfig.Services.Add(typeof(IExceptionLogger), new ExceptionLogger());
|
||||
httpConfig.Formatters.JsonFormatter.SerializerSettings = RealTimeMediaSerializer.GetSerializerSettings();
|
||||
httpConfig.EnsureInitialized();
|
||||
|
||||
app.UseWebApi(httpConfig);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http.ExceptionHandling;
|
||||
using FrontEnd.Logging;
|
||||
|
||||
namespace FrontEnd.Http
|
||||
{
|
||||
internal class ExceptionLogger : IExceptionLogger
|
||||
{
|
||||
public ExceptionLogger()
|
||||
{
|
||||
}
|
||||
|
||||
public Task LogAsync(ExceptionLoggerContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
Log.Error(new CallerInfo(), LogContext.FrontEnd, "Exception processing HTTP request. {0}", context.Exception.ToString());
|
||||
return Task.FromResult<object>(null);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
namespace FrontEnd.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// HTTP route constants for routing requests to CallController methods.
|
||||
/// </summary>
|
||||
public static class HttpRouteConstants
|
||||
{
|
||||
/// <summary>
|
||||
/// Route prefix for all incoming requests.
|
||||
/// </summary>
|
||||
public const string CallSignalingRoutePrefix = "api/calling";
|
||||
|
||||
/// <summary>
|
||||
/// Route for incoming calls.
|
||||
/// </summary>
|
||||
public const string OnIncomingCallRoute = "call";
|
||||
|
||||
/// <summary>
|
||||
/// Route for incoming calls.
|
||||
/// </summary>
|
||||
public const string OnIncomingMessageRoute = "";
|
||||
|
||||
/// <summary>
|
||||
/// Route for existing call callbacks.
|
||||
/// </summary>
|
||||
public const string OnCallbackRoute = "callback";
|
||||
|
||||
/// <summary>
|
||||
/// Route for existing call notifications.
|
||||
/// </summary>
|
||||
public const string OnNotificationRoute = "notification";
|
||||
|
||||
/// <summary>
|
||||
/// Route for getting all calls
|
||||
/// </summary>
|
||||
public const string Calls = "calls";
|
||||
|
||||
/// <summary>
|
||||
/// Route for getting Image for a call
|
||||
/// </summary>
|
||||
public const string Image = "image/{callid}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,339 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
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.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FrontEnd.Logging;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace FrontEnd.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class to log HTTP requests and responses and to set the CorrelationID based on the X-Microsoft-Skype-Chain-ID header
|
||||
/// value of incoming HTTP requests from Skype platform.
|
||||
/// </summary>
|
||||
internal class LoggingMessageHandler : DelegatingHandler
|
||||
{
|
||||
public const string CidHeaderName = "X-Microsoft-Skype-Chain-ID";
|
||||
|
||||
private readonly bool isIncomingMessageHandler;
|
||||
private readonly LogContext logContext;
|
||||
private string[] urlIgnorers;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new LoggingMessageHandler.
|
||||
/// </summary>
|
||||
public LoggingMessageHandler(bool isIncomingMessageHandler, LogContext logContext, string[] urlIgnorers = null)
|
||||
{
|
||||
this.isIncomingMessageHandler = isIncomingMessageHandler;
|
||||
this.logContext = logContext;
|
||||
this.urlIgnorers = urlIgnorers;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log the request and response.
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
string requestCid;
|
||||
string responseCid;
|
||||
|
||||
if (this.isIncomingMessageHandler)
|
||||
{
|
||||
requestCid = AdoptCorrelationId(request.Headers);
|
||||
}
|
||||
else
|
||||
{
|
||||
requestCid = SetCorrelationId(request.Headers);
|
||||
}
|
||||
|
||||
bool ignore =
|
||||
this.urlIgnorers != null &&
|
||||
this.urlIgnorers.Any(ignorer => request.RequestUri.ToString().IndexOf(ignorer, StringComparison.OrdinalIgnoreCase) >= 0);
|
||||
|
||||
if (ignore)
|
||||
{
|
||||
return await SendAndLogAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
string localMessageId = Guid.NewGuid().ToString();
|
||||
string requestUriText = request.RequestUri.ToString();
|
||||
string requestHeadersText = GetHeadersText(request.Headers);
|
||||
|
||||
if (request.Content != null)
|
||||
{
|
||||
requestHeadersText =
|
||||
String.Join(
|
||||
Environment.NewLine,
|
||||
requestHeadersText,
|
||||
GetHeadersText(request.Content.Headers));
|
||||
}
|
||||
|
||||
string requestBodyText = await GetBodyText(request.Content).ConfigureAwait(false);
|
||||
|
||||
Log.Info(new CallerInfo(), logContext, "|| correlationId={0} || local.msgid={1} ||{2}{3}:: {4} {5}{6}{7}{8}{9}{10}$$END$$",
|
||||
requestCid, localMessageId,
|
||||
Environment.NewLine,
|
||||
this.isIncomingMessageHandler ? "Incoming" : "Outgoing",
|
||||
request.Method.ToString(),
|
||||
requestUriText,
|
||||
Environment.NewLine,
|
||||
requestHeadersText,
|
||||
Environment.NewLine,
|
||||
requestBodyText,
|
||||
Environment.NewLine);
|
||||
|
||||
Stopwatch stopwatch = Stopwatch.StartNew();
|
||||
HttpResponseMessage response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
Log.Info(
|
||||
new CallerInfo(),
|
||||
logContext,
|
||||
"{0} HTTP request with Local id={1} took {2}ms.",
|
||||
this.isIncomingMessageHandler ? "Incoming" : "Outgoing",
|
||||
localMessageId,
|
||||
stopwatch.ElapsedMilliseconds);
|
||||
|
||||
if (this.isIncomingMessageHandler)
|
||||
{
|
||||
responseCid = SetCorrelationId(response.Headers);
|
||||
}
|
||||
else
|
||||
{
|
||||
responseCid = AdoptCorrelationId(response.Headers);
|
||||
}
|
||||
|
||||
this.WarnIfDifferent(requestCid, responseCid);
|
||||
|
||||
HttpStatusCode statusCode = response.StatusCode;
|
||||
|
||||
string responseUriText = request.RequestUri.ToString();
|
||||
string responseHeadersText = GetHeadersText(response.Headers);
|
||||
|
||||
if (response.Content != null)
|
||||
{
|
||||
responseHeadersText =
|
||||
String.Join(
|
||||
Environment.NewLine,
|
||||
responseHeadersText,
|
||||
GetHeadersText(response.Content.Headers));
|
||||
}
|
||||
|
||||
string responseBodyText = await GetBodyText(response.Content).ConfigureAwait(false);
|
||||
|
||||
Log.Info(new CallerInfo(), logContext, "|| correlationId={0} || statuscode={1} || local.msgid={2} ||{3}Response to {4}:: {5} {6}{7}{8} {9}{10}{11}{12}{13}{14}$$END$$",
|
||||
CorrelationId.GetCurrentId(), statusCode, localMessageId,
|
||||
Environment.NewLine,
|
||||
this.isIncomingMessageHandler ? "incoming" : "outgoing",
|
||||
request.Method.ToString(),
|
||||
responseUriText,
|
||||
Environment.NewLine,
|
||||
((int)response.StatusCode).ToString(),
|
||||
response.StatusCode.ToString(),
|
||||
Environment.NewLine,
|
||||
responseHeadersText,
|
||||
Environment.NewLine,
|
||||
responseBodyText,
|
||||
Environment.NewLine);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage> SendAndLogAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(new CallerInfo(), LogContext.FrontEnd, "Exception occurred when calling SendAsync: {0}", e.ToString());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void WarnIfDifferent(string requestCid, string responseCid)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(requestCid) || string.IsNullOrWhiteSpace(responseCid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.Equals(requestCid, responseCid))
|
||||
{
|
||||
Log.Warning(
|
||||
new CallerInfo(), LogContext.FrontEnd,
|
||||
"The correlationId of the {0} request, {1}, is different from the {2} response, {3}.",
|
||||
this.isIncomingMessageHandler ? "incoming" : "outgoing",
|
||||
requestCid,
|
||||
this.isIncomingMessageHandler ? "outgoing" : "outgoing",
|
||||
responseCid);
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetHeadersText(HttpHeaders headers)
|
||||
{
|
||||
if (headers == null || !headers.Any())
|
||||
{
|
||||
return String.Empty;
|
||||
}
|
||||
|
||||
List<string> headerTexts = new List<string>();
|
||||
|
||||
foreach (KeyValuePair<string, IEnumerable<string>> h in headers)
|
||||
{
|
||||
headerTexts.Add(GetHeaderText(h));
|
||||
}
|
||||
|
||||
return String.Join(Environment.NewLine, headerTexts);
|
||||
}
|
||||
|
||||
private static string GetHeaderText(KeyValuePair<string, IEnumerable<string>> header)
|
||||
{
|
||||
return String.Format("{0}: {1}", header.Key, String.Join(",", header.Value));
|
||||
}
|
||||
|
||||
private static string AdoptCorrelationId(HttpHeaders headers)
|
||||
{
|
||||
string correlationId = null;
|
||||
IEnumerable<string> correlationIdHeaderValues;
|
||||
if (headers.TryGetValues(CidHeaderName, out correlationIdHeaderValues))
|
||||
{
|
||||
correlationId = correlationIdHeaderValues.FirstOrDefault();
|
||||
CorrelationId.SetCurrentId(correlationId);
|
||||
}
|
||||
|
||||
return correlationId;
|
||||
}
|
||||
|
||||
private static string SetCorrelationId(HttpHeaders headers)
|
||||
{
|
||||
string correlationId = CorrelationId.GetCurrentId();
|
||||
if (!string.IsNullOrWhiteSpace(correlationId))
|
||||
{
|
||||
headers.Add(CidHeaderName, correlationId);
|
||||
}
|
||||
|
||||
return correlationId;
|
||||
}
|
||||
|
||||
public static async Task<string> GetBodyText(HttpContent content)
|
||||
{
|
||||
if (content == null)
|
||||
{
|
||||
return "(empty body)";
|
||||
}
|
||||
|
||||
if (content.IsMimeMultipartContent())
|
||||
{
|
||||
Stream stream = await content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
|
||||
if (!stream.CanSeek)
|
||||
{
|
||||
return "(cannot log body because HTTP stream cannot seek)";
|
||||
}
|
||||
|
||||
StringBuilder multipartBodyBuilder = new StringBuilder();
|
||||
MultipartMemoryStreamProvider streamProvider = new MultipartMemoryStreamProvider();
|
||||
await content.ReadAsMultipartAsync<MultipartMemoryStreamProvider>(streamProvider, (int)stream.Length).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var multipartContent in streamProvider.Contents)
|
||||
{
|
||||
multipartBodyBuilder.AppendLine("-- beginning of multipart content --");
|
||||
|
||||
// Headers
|
||||
string headerText = GetHeadersText(multipartContent.Headers);
|
||||
multipartBodyBuilder.AppendLine(headerText);
|
||||
|
||||
// Body of message
|
||||
string multipartBody = await multipartContent.ReadAsStringAsync().ConfigureAwait(false);
|
||||
string formattedJsonBody;
|
||||
|
||||
if (TryFormatJsonBody(multipartBody, out formattedJsonBody))
|
||||
{
|
||||
multipartBody = formattedJsonBody;
|
||||
}
|
||||
|
||||
if (String.IsNullOrWhiteSpace(multipartBody))
|
||||
{
|
||||
multipartBodyBuilder.AppendLine("(empty body)");
|
||||
}
|
||||
else
|
||||
{
|
||||
multipartBodyBuilder.AppendLine(multipartBody);
|
||||
}
|
||||
|
||||
multipartBodyBuilder.AppendLine("-- end of multipart content --");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Reset the stream position so consumers of this class can re-read the multipart content.
|
||||
stream.Position = 0;
|
||||
}
|
||||
|
||||
return multipartBodyBuilder.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
string body = await content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
|
||||
string formattedJsonBody;
|
||||
if (TryFormatJsonBody(body, out formattedJsonBody))
|
||||
{
|
||||
body = formattedJsonBody;
|
||||
}
|
||||
|
||||
if (String.IsNullOrWhiteSpace(body))
|
||||
{
|
||||
return "(empty body)";
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryFormatJsonBody(string body, out string jsonBody)
|
||||
{
|
||||
jsonBody = null;
|
||||
|
||||
if (String.IsNullOrWhiteSpace(body))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
object parsedObject = JsonConvert.DeserializeObject(body);
|
||||
|
||||
if (parsedObject == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
jsonBody = JsonConvert.SerializeObject(parsedObject, Formatting.Indented);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using FrontEnd.Logging;
|
||||
using Microsoft.Bot.Connector;
|
||||
|
||||
namespace FrontEnd.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper to send chat messages
|
||||
/// </summary>
|
||||
internal class MessageSender
|
||||
{
|
||||
/// <summary>
|
||||
/// Send chat message on a conversation thread with the url as the text message
|
||||
/// </summary>
|
||||
/// <param name="threadId">ThreadId for the conversation that the chat message needs to be sent</param>
|
||||
/// <param name="urlText">Url that needs to be send in the chat message</param>
|
||||
/// <returns></returns>
|
||||
public static async Task SendMessage(string threadId, string urlText)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (MessagesController.MessagesServiceUrl == null)
|
||||
{
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"Bot has not received a chat message before. So it cannot send a message back");
|
||||
return;
|
||||
}
|
||||
|
||||
var connector = new ConnectorClient(new Uri(MessagesController.MessagesServiceUrl));
|
||||
IMessageActivity newMessage = Activity.CreateMessageActivity();
|
||||
newMessage.Type = ActivityTypes.Message;
|
||||
newMessage.From = MessagesController.BotAccount;
|
||||
newMessage.Conversation = new ConversationAccount(true, threadId);
|
||||
newMessage.Text = "[Click here for video shots from the conference](" + urlText + ")";
|
||||
await connector.Conversations.SendToConversationAsync((Activity)newMessage);
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"sent message");
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Log.Error(new CallerInfo(), LogContext.FrontEnd, $"{ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http;
|
||||
using FrontEnd.CallLogic;
|
||||
using FrontEnd.Logging;
|
||||
using Microsoft.Bot.Connector;
|
||||
|
||||
namespace FrontEnd.Http
|
||||
{
|
||||
[BotAuthentication]
|
||||
[RoutePrefix("api/messages")]
|
||||
public class MessagesController : ApiController
|
||||
{
|
||||
public static string MessagesServiceUrl;
|
||||
public static ChannelAccount BotAccount;
|
||||
|
||||
/// <summary>
|
||||
/// POST: api/Messages
|
||||
/// receive a message from a user and send replies
|
||||
/// </summary>
|
||||
/// <param name="activity"></param>
|
||||
[HttpPost]
|
||||
[Route(HttpRouteConstants.OnIncomingMessageRoute)]
|
||||
public virtual async Task<HttpResponseMessage> Post([FromBody] Activity activity)
|
||||
{
|
||||
//cache the serviceUrl and bot account
|
||||
MessagesServiceUrl = activity.ServiceUrl;
|
||||
BotAccount = activity.Recipient;
|
||||
Log.Info(new CallerInfo(), LogContext.FrontEnd, $"Received chat message.. checking if there is an active media call for this thread");
|
||||
await RealTimeMediaCall.SendUrlForConversationId(activity.Conversation.Id);
|
||||
|
||||
return new HttpResponseMessage(System.Net.HttpStatusCode.Accepted);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
using System;
|
||||
using Microsoft.Bot.Builder.RealTimeMediaCalling;
|
||||
|
||||
namespace FrontEnd.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// Service settings to configure the RealTimeMediaCalling
|
||||
/// </summary>
|
||||
public class RealTimeMediaCallingBotServiceSettings : IRealTimeMediaCallServiceSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// The url where the callbacks for the calls to this bot needs to be sent.
|
||||
/// For example "https://testservice.azurewebsites.net/api/calling/callback"
|
||||
/// </summary>
|
||||
public Uri CallbackUrl { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The url where the notifications for the calls to this bot needs to be sent.
|
||||
/// For example "https://testservice.azurewebsites.net/api/calling/notification"
|
||||
/// </summary>
|
||||
public Uri NotificationUrl { get; private set; }
|
||||
|
||||
public RealTimeMediaCallingBotServiceSettings()
|
||||
{
|
||||
CallbackUrl = Service.Instance.Configuration.CallControlCallbackUrl;
|
||||
NotificationUrl = Service.Instance.Configuration.NotificationCallbackUrl;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Skype.Bots.Media;
|
||||
|
||||
namespace FrontEnd
|
||||
{
|
||||
/// <summary>
|
||||
/// IConfiguration contains the static configuration information the application needs
|
||||
/// to run such as the urls it needs to listen on, credentials to communicate with
|
||||
/// Bing translator, settings for media.platform, etc.
|
||||
///
|
||||
/// The concrete implemenation AzureConfiguration gets the configuration from Azure. However,
|
||||
/// other concrete classes could be created to allow the application to run outside of Azure
|
||||
/// for testing.
|
||||
/// </summary>
|
||||
public interface IConfiguration : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Dns name for this service
|
||||
/// </summary>
|
||||
string ServiceDnsName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// List of HTTP urls the app should listen on for incoming call
|
||||
/// signaling requests from Skype Platform.
|
||||
/// </summary>
|
||||
IEnumerable<Uri> CallControlListeningUrls { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The base callback URL for this instance. To ensure that all requests
|
||||
/// for a given call go to the same instance, this Url is unique to each
|
||||
/// instance by way of its instance input endpoint port.
|
||||
/// </summary>
|
||||
Uri CallControlCallbackUrl { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The template for call notifications like call state change notifications.
|
||||
/// To ensure that all requests for a given call go to the same instance, this Url
|
||||
/// is unique to each instance by way of its instance input endpoint port.
|
||||
/// </summary>
|
||||
Uri NotificationCallbackUrl { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Base url for this particular azure instance
|
||||
/// </summary>
|
||||
Uri AzureInstanceBaseUrl { get; }
|
||||
|
||||
/// <summary>
|
||||
/// MicrosoftAppId generated at the time of registration of the bot
|
||||
/// </summary>
|
||||
string MicrosoftAppId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Settings for the bot media platform
|
||||
/// </summary>
|
||||
MediaPlatformSettings MediaPlatformSettings { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace FrontEnd.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Class that encapsulates the caller's (creator's) information. This is helpful to provide more context in log statements.
|
||||
/// </summary>
|
||||
public class CallerInfo
|
||||
{
|
||||
private static ConcurrentDictionary<int, string> toStringCache = new ConcurrentDictionary<int, string>();
|
||||
|
||||
/// <summary>
|
||||
/// The name of the method or property of the caller
|
||||
/// </summary>
|
||||
public string MemberName { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The full path of the source file of the caller
|
||||
/// </summary>
|
||||
public string FilePath { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The line number of the source file of the caller
|
||||
/// </summary>
|
||||
public int LineNumber { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the CallerInfo class
|
||||
/// </summary>
|
||||
public CallerInfo(
|
||||
[CallerMemberName] string memberName = "",
|
||||
[CallerFilePath] string filePath = "",
|
||||
[CallerLineNumber] int lineNumber = 0
|
||||
)
|
||||
{
|
||||
this.MemberName = memberName;
|
||||
this.FilePath = filePath;
|
||||
this.LineNumber = lineNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the hashcode for this instance
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return MemberName.GetHashCode() ^ FilePath.GetHashCode() ^ LineNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String representation of the caller's info
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return toStringCache.GetOrAdd(this.GetHashCode(), hc => String.Format(
|
||||
"{0},{1}({2})",
|
||||
this.MemberName,
|
||||
Path.GetFileName(this.FilePath),
|
||||
this.LineNumber
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
using System;
|
||||
using System.Runtime.Remoting.Messaging;
|
||||
|
||||
namespace FrontEnd.Logging
|
||||
{
|
||||
internal class CorrelationId
|
||||
{
|
||||
private class Holder : MarshalByRefObject
|
||||
{
|
||||
public string Id;
|
||||
}
|
||||
|
||||
internal const string LogicalDataName = "FrontEnd.Logging.CorrelationId";
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current correlation ID. This is necessary to call in event handler callbacks because the event producer
|
||||
/// may not be aware of the call id.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
public static void SetCurrentId(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Holder holder = CallContext.LogicalGetData(LogicalDataName) as Holder;
|
||||
if (holder == null)
|
||||
{
|
||||
CallContext.LogicalSetData(LogicalDataName, new Holder { Id = value });
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
holder.Id = value;
|
||||
}
|
||||
catch (AppDomainUnloadedException)
|
||||
{
|
||||
CallContext.LogicalSetData(LogicalDataName, new Holder { Id = value });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current correlation id.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetCurrentId()
|
||||
{
|
||||
Holder holder = CallContext.LogicalGetData(LogicalDataName) as Holder;
|
||||
if (holder != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
return holder.Id;
|
||||
}
|
||||
catch (AppDomainUnloadedException)
|
||||
{
|
||||
CallContext.FreeNamedDataSlot(LogicalDataName);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace FrontEnd.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Different contexts for which log statements are produced. Each of these contexts
|
||||
/// has a corresponding TraceSource entry in the WorkerRole's app.config file.
|
||||
/// </summary>
|
||||
public enum LogContext
|
||||
{
|
||||
AuthToken,
|
||||
FrontEnd,
|
||||
Media
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper class for logging. This class provides a common mechanism for logging throughout the application.
|
||||
/// </summary>
|
||||
public static class Log
|
||||
{
|
||||
private static readonly Dictionary<LogContext, TraceSource> traceSources = new Dictionary<LogContext, TraceSource>();
|
||||
|
||||
static Log()
|
||||
{
|
||||
foreach (LogContext context in Enum.GetValues(typeof(LogContext)))
|
||||
{
|
||||
traceSources[context] = new TraceSource(context.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if Verbose method is on
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsVerboseOn(LogContext context)
|
||||
{
|
||||
TraceSource traceSource = traceSources[context];
|
||||
return traceSource.Switch.Level >= SourceLevels.Verbose || traceSource.Switch.Level == SourceLevels.All;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verbose logging of the message
|
||||
/// </summary>
|
||||
/// <param name="callerInfo"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="format"></param>
|
||||
/// <param name="args"></param>
|
||||
public static void Verbose(CallerInfo callerInfo, LogContext context, string format, params object[] args)
|
||||
{
|
||||
Log.Write(TraceEventType.Verbose, callerInfo, context, format, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Info level logging of the message
|
||||
/// </summary>
|
||||
/// <param name="callerInfo"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="format"></param>
|
||||
/// <param name="args"></param>
|
||||
public static void Info(CallerInfo callerInfo, LogContext context, string format, params object[] args)
|
||||
{
|
||||
Log.Write(TraceEventType.Information, callerInfo, context, format, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Warning level logging of the message
|
||||
/// </summary>
|
||||
/// <param name="callerInfo"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="format"></param>
|
||||
/// <param name="args"></param>
|
||||
public static void Warning(CallerInfo callerInfo, LogContext context, string format, params object[] args)
|
||||
{
|
||||
Log.Write(TraceEventType.Warning, callerInfo, context, format, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Error level logging of the message
|
||||
/// </summary>
|
||||
/// <param name="callerInfo"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="format"></param>
|
||||
/// <param name="args"></param>
|
||||
public static void Error(CallerInfo callerInfo, LogContext context, string format, params object[] args)
|
||||
{
|
||||
Log.Write(TraceEventType.Error, callerInfo, context, format, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flush the log trace sources
|
||||
/// </summary>
|
||||
public static void Flush()
|
||||
{
|
||||
foreach (TraceSource traceSource in traceSources.Values)
|
||||
{
|
||||
traceSource.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
private static void Write(TraceEventType level, CallerInfo callerInfo, LogContext context, string format, params object[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
string correlationId = CorrelationId.GetCurrentId() ?? "-";
|
||||
string callerInfoString = (callerInfo == null) ? "-" : callerInfo.ToString();
|
||||
string tracePrefix = "[" + correlationId + " " + callerInfoString + "] ";
|
||||
if (args.Length == 0)
|
||||
{
|
||||
traceSources[context].TraceEvent(level, 0, tracePrefix + format);
|
||||
}
|
||||
else
|
||||
{
|
||||
traceSources[context].TraceEvent(level, 0, string.Format(tracePrefix + format, args));
|
||||
}
|
||||
|
||||
}catch(Exception ex)
|
||||
{
|
||||
Trace.TraceError("Error in Log.cs" + ex.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/**************************************************
|
||||
* *
|
||||
* © Microsoft Corporation. All rights reserved. *
|
||||
* *
|
||||
**************************************************/
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using FrontEnd.Logging;
|
||||
using Microsoft.Skype.Bots.Media;
|
||||
|
||||
namespace FrontEnd.Media
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an Audio Buffer for Send and also implements Dispose
|
||||
/// </summary>
|
||||
class AudioSendBuffer : AudioMediaBuffer
|
||||
{
|
||||
private bool m_disposed;
|
||||
|
||||
[DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
|
||||
public static extern void CopyMemory(IntPtr dest, IntPtr src, uint count);
|
||||
|
||||
public AudioSendBuffer(AudioMediaBuffer mediaBuffer, AudioFormat format, ulong timeStamp)
|
||||
{
|
||||
IntPtr unmanagedBuffer = Marshal.AllocHGlobal((int)mediaBuffer.Length);
|
||||
CopyMemory(unmanagedBuffer, mediaBuffer.Data, (uint)mediaBuffer.Length);
|
||||
|
||||
Data = unmanagedBuffer;
|
||||
Length = mediaBuffer.Length;
|
||||
AudioFormat = format;
|
||||
Timestamp = (long)timeStamp;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!m_disposed)
|
||||
{
|
||||
Marshal.FreeHGlobal(Data);
|
||||
}
|
||||
|
||||
m_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче