mirror of
https://github.com/PepperDash/Essentials.git
synced 2026-02-12 11:15:08 +00:00
Major update to remove eRoutingSignalType.AudioVideo in favor of bitmasked values and adding UsbOutput and UsbInput types. Updated all affected routing ports and ExecuteSwitch method calls. Need to review and test routing to ensure bitwise operators are all correct.
1679 lines
61 KiB
C#
1679 lines
61 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using Crestron.SimplSharp;
|
|
using Crestron.SimplSharpPro.CrestronThread;
|
|
using Newtonsoft.Json;
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
using PepperDash.Core;
|
|
using PepperDash.Essentials.Core;
|
|
using PepperDash.Essentials.Core.Config;
|
|
using PepperDash.Essentials.Core.Routing;
|
|
using PepperDash.Essentials.Devices.Common.Cameras;
|
|
using PepperDash.Essentials.Devices.Common.Codec;
|
|
using PepperDash.Essentials.Devices.Common.Occupancy;
|
|
using PepperDash.Essentials.Devices.Common.VideoCodec;
|
|
|
|
namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
|
|
{
|
|
public class ZoomRoom : VideoCodecBase, IHasCodecSelfView, IHasDirectory, ICommunicationMonitor, IRouting, IHasScheduleAwareness, IHasCodecCameras
|
|
{
|
|
public CommunicationGather PortGather { get; private set; }
|
|
|
|
public StatusMonitorBase CommunicationMonitor { get; private set; }
|
|
|
|
private CrestronQueue<string> ReceiveQueue;
|
|
|
|
private Thread ReceiveThread;
|
|
|
|
string Delimiter = "\x0D\x0A";
|
|
|
|
private ZoomRoomSyncState SyncState;
|
|
|
|
public ZoomRoomStatus Status { get; private set; }
|
|
|
|
public ZoomRoomConfiguration Configuration { get; private set; }
|
|
|
|
private StringBuilder JsonMessage;
|
|
|
|
private bool JsonFeedbackMessageIsIncoming;
|
|
private uint JsonCurlyBraceCounter = 0;
|
|
|
|
public bool CommDebuggingIsOn;
|
|
|
|
CTimer LoginMessageReceivedTimer;
|
|
CTimer RetryConnectionTimer;
|
|
|
|
/// <summary>
|
|
/// Gets and returns the scaled volume of the codec
|
|
/// </summary>
|
|
protected override Func<int> VolumeLevelFeedbackFunc
|
|
{
|
|
get
|
|
{
|
|
return () => CrestronEnvironment.ScaleWithLimits(Configuration.Audio.Output.Volume, 100, 0, 65535, 0);
|
|
}
|
|
}
|
|
|
|
protected override Func<bool> PrivacyModeIsOnFeedbackFunc
|
|
{
|
|
get
|
|
{
|
|
return () => Configuration.Call.Microphone.Mute;
|
|
}
|
|
}
|
|
|
|
protected override Func<bool> StandbyIsOnFeedbackFunc
|
|
{
|
|
get
|
|
{
|
|
return () => false;
|
|
}
|
|
}
|
|
|
|
protected override Func<string> SharingSourceFeedbackFunc
|
|
{
|
|
get
|
|
{
|
|
return () => Status.Sharing.dispState;
|
|
}
|
|
}
|
|
|
|
protected override Func<bool> SharingContentIsOnFeedbackFunc
|
|
{
|
|
get
|
|
{
|
|
return () => Status.Call.Sharing.IsSharing;
|
|
}
|
|
}
|
|
|
|
protected Func<bool> FarEndIsSharingContentFeedbackFunc
|
|
{
|
|
get
|
|
{
|
|
return () => false;
|
|
}
|
|
}
|
|
|
|
protected override Func<bool> MuteFeedbackFunc
|
|
{
|
|
get
|
|
{
|
|
return () => Configuration.Audio.Output.Volume == 0;
|
|
}
|
|
}
|
|
|
|
//protected Func<bool> RoomIsOccupiedFeedbackFunc
|
|
//{
|
|
// get
|
|
// {
|
|
// return () => false;
|
|
// }
|
|
//}
|
|
|
|
//protected Func<int> PeopleCountFeedbackFunc
|
|
//{
|
|
// get
|
|
// {
|
|
// return () => 0;
|
|
// }
|
|
//}
|
|
|
|
protected Func<bool> SelfViewIsOnFeedbackFunc
|
|
{
|
|
get
|
|
{
|
|
return () => !Configuration.Video.HideConfSelfVideo;
|
|
}
|
|
}
|
|
|
|
protected Func<string> SelfviewPipPositionFeedbackFunc
|
|
{
|
|
get
|
|
{
|
|
return () => "";
|
|
}
|
|
}
|
|
|
|
protected Func<string> LocalLayoutFeedbackFunc
|
|
{
|
|
get
|
|
{
|
|
return () => "";
|
|
}
|
|
}
|
|
|
|
protected Func<bool> LocalLayoutIsProminentFeedbackFunc
|
|
{
|
|
get
|
|
{
|
|
return () => false;
|
|
}
|
|
}
|
|
|
|
|
|
public RoutingInputPort CodecOsdIn { get; private set; }
|
|
public RoutingOutputPort Output1 { get; private set; }
|
|
|
|
uint DefaultMeetingDurationMin = 30;
|
|
|
|
int PreviousVolumeLevel = 0;
|
|
|
|
public ZoomRoom(DeviceConfig config, IBasicCommunication comm)
|
|
: base(config)
|
|
{
|
|
var props = JsonConvert.DeserializeObject<ZoomRoomPropertiesConfig>(config.Properties.ToString());
|
|
|
|
// The queue that will collect the repsonses in the order they are received
|
|
ReceiveQueue = new CrestronQueue<string>(25);
|
|
|
|
// The thread responsible for dequeuing and processing the messages
|
|
ReceiveThread = new Thread((o) => ProcessQueue(), null);
|
|
|
|
Communication = comm;
|
|
|
|
if (props.CommunicationMonitorProperties != null)
|
|
{
|
|
CommunicationMonitor = new GenericCommunicationMonitor(this, Communication, props.CommunicationMonitorProperties);
|
|
}
|
|
else
|
|
{
|
|
CommunicationMonitor = new GenericCommunicationMonitor(this, Communication, 30000, 120000, 300000, "zStatus SystemUnit\r");
|
|
}
|
|
|
|
DeviceManager.AddDevice(CommunicationMonitor);
|
|
|
|
Status = new ZoomRoomStatus();
|
|
|
|
Configuration = new ZoomRoomConfiguration();
|
|
|
|
CodecInfo = new ZoomRoomInfo(Status, Configuration);
|
|
|
|
SyncState = new ZoomRoomSyncState(Key + "--Sync", this);
|
|
|
|
SyncState.InitialSyncCompleted += new EventHandler<EventArgs>(SyncState_InitialSyncCompleted);
|
|
|
|
PhonebookSyncState = new CodecPhonebookSyncState(Key + "--PhonebookSync");
|
|
|
|
PortGather = new CommunicationGather(Communication, "\x0A");
|
|
PortGather.IncludeDelimiter = true;
|
|
PortGather.LineReceived += this.Port_LineReceived;
|
|
|
|
CodecOsdIn = new RoutingInputPort(RoutingPortNames.CodecOsd, eRoutingSignalType.Audio | eRoutingSignalType.Video,
|
|
eRoutingPortConnectionType.Hdmi, new Action(StopSharing), this);
|
|
|
|
Output1 = new RoutingOutputPort(RoutingPortNames.AnyVideoOut, eRoutingSignalType.Audio | eRoutingSignalType.Video,
|
|
eRoutingPortConnectionType.Hdmi, null, this);
|
|
|
|
SelfviewIsOnFeedback = new BoolFeedback(SelfViewIsOnFeedbackFunc);
|
|
|
|
CodecSchedule = new CodecScheduleAwareness();
|
|
|
|
SetUpFeedbackActions();
|
|
|
|
Cameras = new List<CameraBase>();
|
|
|
|
SetUpDirectory();
|
|
}
|
|
|
|
void SyncState_InitialSyncCompleted(object sender, EventArgs e)
|
|
{
|
|
SetUpRouting();
|
|
|
|
SetIsReady();
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Subscribes to the PropertyChanged events on the state objects and fires the corresponding feedbacks.
|
|
/// </summary>
|
|
void SetUpFeedbackActions()
|
|
{
|
|
Configuration.Audio.Output.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(
|
|
(o, a) =>
|
|
{
|
|
if (a.PropertyName == "Volume")
|
|
{
|
|
VolumeLevelFeedback.FireUpdate();
|
|
MuteFeedback.FireUpdate();
|
|
}
|
|
});
|
|
|
|
Configuration.Call.Microphone.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(
|
|
(o, a) =>
|
|
{
|
|
if (a.PropertyName == "Mute")
|
|
{
|
|
PrivacyModeIsOnFeedback.FireUpdate();
|
|
}
|
|
});
|
|
|
|
Configuration.Video.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(
|
|
(o, a) =>
|
|
{
|
|
if (a.PropertyName == "HideConfSelfVideo")
|
|
{
|
|
SelfviewIsOnFeedback.FireUpdate();
|
|
}
|
|
});
|
|
Configuration.Video.Camera.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(
|
|
(o, a) =>
|
|
{
|
|
if (a.PropertyName == "SelectedId")
|
|
{
|
|
SelectCamera(Configuration.Video.Camera.SelectedId); // this will in turn fire the affected feedbacks
|
|
}
|
|
});
|
|
|
|
Status.Call.Sharing.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(
|
|
(o, a) =>
|
|
{
|
|
if (a.PropertyName == "State")
|
|
{
|
|
SharingContentIsOnFeedback.FireUpdate();
|
|
}
|
|
});
|
|
|
|
Status.Sharing.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(
|
|
(o, a) =>
|
|
{
|
|
if (a.PropertyName == "dispState")
|
|
{
|
|
SharingSourceFeedback.FireUpdate();
|
|
}
|
|
else if (a.PropertyName == "password")
|
|
{
|
|
//TODO: Fire Sharing Password Update
|
|
}
|
|
});
|
|
}
|
|
|
|
void SetUpDirectory()
|
|
{
|
|
DirectoryRoot = new CodecDirectory();
|
|
|
|
DirectoryBrowseHistory = new List<CodecDirectory>();
|
|
|
|
CurrentDirectoryResultIsNotDirectoryRoot = new BoolFeedback(() => DirectoryBrowseHistory.Count > 0);
|
|
|
|
CurrentDirectoryResultIsNotDirectoryRoot.FireUpdate();
|
|
}
|
|
|
|
void SetUpRouting()
|
|
{
|
|
// Set up input ports
|
|
CreateOsdSource();
|
|
InputPorts.Add(CodecOsdIn);
|
|
|
|
// Set up output ports
|
|
OutputPorts.Add(Output1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates the fake OSD source, and connects it's AudioVideo output to the CodecOsdIn input
|
|
/// to enable routing
|
|
/// </summary>
|
|
void CreateOsdSource()
|
|
{
|
|
OsdSource = new DummyRoutingInputsDevice(Key + "[osd]");
|
|
DeviceManager.AddDevice(OsdSource);
|
|
var tl = new TieLine(OsdSource.AudioVideoOutputPort, CodecOsdIn);
|
|
TieLineCollection.Default.Add(tl);
|
|
|
|
//foreach(var input in Status.Video.
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts the HTTP feedback server and syncronizes state of codec
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public override bool CustomActivate()
|
|
{
|
|
CrestronConsole.AddNewConsoleCommand(SetCommDebug, "SetCodecCommDebug", "0 for Off, 1 for on", ConsoleAccessLevelEnum.AccessOperator);
|
|
CrestronConsole.AddNewConsoleCommand((s) => SendText("zCommand Phonebook List Offset: 0 Limit: 512"), "GetZoomRoomContacts", "Triggers a refresh of the codec phonebook", ConsoleAccessLevelEnum.AccessOperator);
|
|
CrestronConsole.AddNewConsoleCommand((s) => GetBookings(), "GetZoomRoomBookings", "Triggers a refresh of the booking data for today", ConsoleAccessLevelEnum.AccessOperator);
|
|
|
|
var socket = Communication as ISocketStatus;
|
|
if (socket != null)
|
|
{
|
|
socket.ConnectionChange += new EventHandler<GenericSocketStatusChageEventArgs>(socket_ConnectionChange);
|
|
}
|
|
|
|
// TODO: Turn this off when done initial development
|
|
CommDebuggingIsOn = true;
|
|
|
|
Communication.Connect();
|
|
|
|
CommunicationMonitor.Start();
|
|
|
|
return base.CustomActivate();
|
|
}
|
|
|
|
public void SetCommDebug(string s)
|
|
{
|
|
if (s == "1")
|
|
{
|
|
CommDebuggingIsOn = true;
|
|
Debug.Console(0, this, "Comm Debug Enabled.");
|
|
}
|
|
else
|
|
{
|
|
CommDebuggingIsOn = false;
|
|
Debug.Console(0, this, "Comm Debug Disabled.");
|
|
}
|
|
}
|
|
|
|
void socket_ConnectionChange(object sender, GenericSocketStatusChageEventArgs e)
|
|
{
|
|
Debug.Console(1, this, "Socket status change {0}", e.Client.ClientStatus);
|
|
if (e.Client.IsConnected)
|
|
{
|
|
|
|
}
|
|
else
|
|
{
|
|
SyncState.CodecDisconnected();
|
|
PhonebookSyncState.CodecDisconnected();
|
|
}
|
|
}
|
|
|
|
public void SendText(string command)
|
|
{
|
|
if (CommDebuggingIsOn)
|
|
Debug.Console(1, this, "Sending: '{0}'", command);
|
|
|
|
Communication.SendText(command + Delimiter);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gathers responses and enqueues them.
|
|
/// </summary>
|
|
/// <param name="dev"></param>
|
|
/// <param name="args"></param>
|
|
void Port_LineReceived(object dev, GenericCommMethodReceiveTextArgs args)
|
|
{
|
|
//if (CommDebuggingIsOn)
|
|
// Debug.Console(1, this, "Gathered: '{0}'", args.Text);
|
|
|
|
ReceiveQueue.Enqueue(args.Text);
|
|
|
|
// If the receive thread has for some reason stopped, this will restart it
|
|
if (ReceiveThread.ThreadState != Thread.eThreadStates.ThreadRunning)
|
|
ReceiveThread.Start();
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Runs in it's own thread to dequeue messages in the order they were received to be processed
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
object ProcessQueue()
|
|
{
|
|
try
|
|
{
|
|
while (true)
|
|
{
|
|
var message = ReceiveQueue.Dequeue();
|
|
|
|
ProcessMessage(message);
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.Console(1, this, "Error Processing Queue: {0}", e);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Queues the initial queries to be sent upon connection
|
|
/// </summary>
|
|
void SetUpSyncQueries()
|
|
{
|
|
// zStatus
|
|
SyncState.AddQueryToQueue("zStatus Call Status");
|
|
SyncState.AddQueryToQueue("zStatus Audio Input Line");
|
|
SyncState.AddQueryToQueue("zStatus Audio Output Line");
|
|
SyncState.AddQueryToQueue("zStatus Video Camera Line");
|
|
SyncState.AddQueryToQueue("zStatus Video Optimizable");
|
|
SyncState.AddQueryToQueue("zStatus Capabilities");
|
|
SyncState.AddQueryToQueue("zStatus Sharing");
|
|
SyncState.AddQueryToQueue("zStatus CameraShare");
|
|
SyncState.AddQueryToQueue("zStatus Call Layout");
|
|
SyncState.AddQueryToQueue("zStatus Call ClosedCaption Available");
|
|
SyncState.AddQueryToQueue("zStatus NumberOfScreens");
|
|
|
|
// zConfiguration
|
|
|
|
SyncState.AddQueryToQueue("zConfiguration Call Sharing optimize_video_sharing");
|
|
SyncState.AddQueryToQueue("zConfiguration Call Microphone Mute");
|
|
SyncState.AddQueryToQueue("zConfiguration Call Camera Mute");
|
|
SyncState.AddQueryToQueue("zConfiguration Audio Input SelectedId");
|
|
SyncState.AddQueryToQueue("zConfiguration Audio Input is_sap_disabled");
|
|
SyncState.AddQueryToQueue("zConfiguration Audio Input reduce_reverb");
|
|
SyncState.AddQueryToQueue("zConfiguration Audio Input volume");
|
|
SyncState.AddQueryToQueue("zConfiguration Audio Output selectedId");
|
|
SyncState.AddQueryToQueue("zConfiguration Audio Output volume");
|
|
SyncState.AddQueryToQueue("zConfiguration Video hide_conf_self_video");
|
|
SyncState.AddQueryToQueue("zConfiguration Video Camera selectedId");
|
|
SyncState.AddQueryToQueue("zConfiguration Video Camera Mirror");
|
|
SyncState.AddQueryToQueue("zConfiguration Client appVersion");
|
|
SyncState.AddQueryToQueue("zConfiguration Client deviceSystem");
|
|
SyncState.AddQueryToQueue("zConfiguration Call Layout ShareThumb");
|
|
SyncState.AddQueryToQueue("zConfiguration Call Layout Style");
|
|
SyncState.AddQueryToQueue("zConfiguration Call Layout Size");
|
|
SyncState.AddQueryToQueue("zConfiguration Call Layout Position");
|
|
SyncState.AddQueryToQueue("zConfiguration Call Lock Enable");
|
|
SyncState.AddQueryToQueue("zConfiguration Call MuteUserOnEntry Enable");
|
|
SyncState.AddQueryToQueue("zConfiguration Call ClosedCaption FontSize ");
|
|
SyncState.AddQueryToQueue("zConfiguration Call ClosedCaption Visible");
|
|
|
|
// zCommand
|
|
|
|
SyncState.AddQueryToQueue("zCommand Phonebook List Offset: 0 Limit: 512");
|
|
SyncState.AddQueryToQueue("zCommand Bookings List");
|
|
|
|
|
|
SyncState.StartSync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processes messages as they are dequeued
|
|
/// </summary>
|
|
/// <param name="message"></param>
|
|
void ProcessMessage(string message)
|
|
{
|
|
// Counts the curly braces
|
|
if(message.Contains('{'))
|
|
JsonCurlyBraceCounter++;
|
|
|
|
if (message.Contains('}'))
|
|
JsonCurlyBraceCounter--;
|
|
|
|
Debug.Console(2, this, "JSON Curly Brace Count: {0}", JsonCurlyBraceCounter);
|
|
|
|
if (!JsonFeedbackMessageIsIncoming && message.Trim('\x20') == "{" + Delimiter) // Check for the beginning of a new JSON message
|
|
{
|
|
JsonFeedbackMessageIsIncoming = true;
|
|
JsonCurlyBraceCounter = 1; // reset the counter for each new message
|
|
|
|
JsonMessage = new StringBuilder();
|
|
|
|
JsonMessage.Append(message);
|
|
|
|
if (CommDebuggingIsOn)
|
|
Debug.Console(2, this, "Incoming JSON message...");
|
|
|
|
return;
|
|
}
|
|
else if (JsonFeedbackMessageIsIncoming && message.Trim('\x20') == "}" + Delimiter) // Check for the end of a JSON message
|
|
{
|
|
JsonMessage.Append(message);
|
|
|
|
if(JsonCurlyBraceCounter == 0)
|
|
{
|
|
JsonFeedbackMessageIsIncoming = false;
|
|
|
|
if (CommDebuggingIsOn)
|
|
Debug.Console(2, this, "Complete JSON Received:\n{0}", JsonMessage.ToString());
|
|
|
|
// Forward the complete message to be deserialized
|
|
DeserializeResponse(JsonMessage.ToString());
|
|
}
|
|
|
|
//JsonMessage = new StringBuilder();
|
|
return;
|
|
}
|
|
|
|
// NOTE: This must happen after the above conditions have been checked
|
|
// Append subsequent partial JSON fragments to the string builder
|
|
if (JsonFeedbackMessageIsIncoming)
|
|
{
|
|
JsonMessage.Append(message);
|
|
|
|
//Debug.Console(1, this, "Building JSON:\n{0}", JsonMessage.ToString());
|
|
return;
|
|
}
|
|
|
|
if (CommDebuggingIsOn)
|
|
Debug.Console(1, this, "Non-JSON response: '{0}'", message);
|
|
|
|
JsonCurlyBraceCounter = 0; // reset on non-JSON response
|
|
|
|
if (!SyncState.InitialSyncComplete)
|
|
{
|
|
switch (message.Trim().ToLower()) // remove the whitespace
|
|
{
|
|
case "*r login successful":
|
|
{
|
|
SyncState.LoginMessageReceived();
|
|
|
|
// Fire up a thread to send the intial commands.
|
|
CrestronInvoke.BeginInvoke((o) =>
|
|
{
|
|
Thread.Sleep(100);
|
|
// disable echo of commands
|
|
SendText("echo off");
|
|
Thread.Sleep(100);
|
|
// set feedback exclusions
|
|
SendText("zFeedback Register Op: ex Path: /Event/InfoResult/info/callin_country_list");
|
|
Thread.Sleep(100);
|
|
SendText("zFeedback Register Op: ex Path: /Event/InfoResult/info/callout_country_list");
|
|
Thread.Sleep(100);
|
|
// switch to json format
|
|
SendText("format json");
|
|
});
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deserializes a JSON formatted response
|
|
/// </summary>
|
|
/// <param name="response"></param>
|
|
void DeserializeResponse(string response)
|
|
{
|
|
try
|
|
{
|
|
var trimmedResponse = response.Trim();
|
|
|
|
if (trimmedResponse.Length <= 0)
|
|
return;
|
|
|
|
var message = JObject.Parse(trimmedResponse);
|
|
|
|
eZoomRoomResponseType eType = (eZoomRoomResponseType)Enum.Parse(typeof(eZoomRoomResponseType), message["type"].Value<string>(), true);
|
|
|
|
var topKey = message["topKey"].Value<string>();
|
|
|
|
var responseObj = message[topKey];
|
|
|
|
Debug.Console(1, "{0} Response Received. topKey: '{1}'\n{2}", eType, topKey, responseObj.ToString());
|
|
|
|
switch (eType)
|
|
{
|
|
case eZoomRoomResponseType.zConfiguration:
|
|
{
|
|
switch (topKey.ToLower())
|
|
{
|
|
case "call":
|
|
{
|
|
JsonConvert.PopulateObject(responseObj.ToString(), Configuration.Call);
|
|
|
|
break;
|
|
}
|
|
case "audio":
|
|
{
|
|
JsonConvert.PopulateObject(responseObj.ToString(), Configuration.Audio);
|
|
|
|
break;
|
|
}
|
|
case "video":
|
|
{
|
|
JsonConvert.PopulateObject(responseObj.ToString(), Configuration.Video);
|
|
|
|
break;
|
|
}
|
|
case "client":
|
|
{
|
|
JsonConvert.PopulateObject(responseObj.ToString(), Configuration.Client);
|
|
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case eZoomRoomResponseType.zCommand:
|
|
{
|
|
switch (topKey.ToLower())
|
|
{
|
|
case "phonebooklistresult":
|
|
{
|
|
JsonConvert.PopulateObject(responseObj.ToString(), Status.Phonebook);
|
|
|
|
if(!PhonebookSyncState.InitialSyncComplete)
|
|
{
|
|
PhonebookSyncState.InitialPhonebookFoldersReceived();
|
|
PhonebookSyncState.PhonebookRootEntriesReceived();
|
|
PhonebookSyncState.SetPhonebookHasFolders(false);
|
|
PhonebookSyncState.SetNumberOfContacts(Status.Phonebook.Contacts.Count);
|
|
}
|
|
|
|
var directoryResults = new CodecDirectory();
|
|
|
|
directoryResults = zStatus.Phonebook.ConvertZoomContactsToGeneric(Status.Phonebook.Contacts);
|
|
|
|
DirectoryRoot = directoryResults;
|
|
|
|
OnDirectoryResultReturned(directoryResults);
|
|
|
|
break;
|
|
}
|
|
case "listparticipantsresult":
|
|
{
|
|
Debug.Console(1, this, "JTokenType: {0}", responseObj.Type);
|
|
|
|
if (responseObj.Type == JTokenType.Array)
|
|
{
|
|
// if the type is array this must be the complete list
|
|
Status.Call.Participants = JsonConvert.DeserializeObject<List<zCommand.ListParticipant>>(responseObj.ToString());
|
|
}
|
|
else if (responseObj.Type == JTokenType.Object)
|
|
{
|
|
// this is a single participant event notification
|
|
|
|
var participant = JsonConvert.DeserializeObject<zCommand.ListParticipant>(responseObj.ToString());
|
|
|
|
if (participant != null)
|
|
{
|
|
if (participant.Event == "ZRCUserChangedEventLeftMeeting" || participant.Event == "ZRCUserChangedEventUserInfoUpdated")
|
|
{
|
|
var existingParticipant = Status.Call.Participants.FirstOrDefault(p => p.UserId.Equals(participant.UserId));
|
|
|
|
if (existingParticipant != null)
|
|
{
|
|
if (participant.Event == "ZRCUserChangedEventLeftMeeting")
|
|
{
|
|
// Remove participant
|
|
Status.Call.Participants.Remove(existingParticipant);
|
|
}
|
|
else if (participant.Event == "ZRCUserChangedEventUserInfoUpdated")
|
|
{
|
|
// Update participant
|
|
JsonConvert.PopulateObject(responseObj.ToString(), existingParticipant);
|
|
}
|
|
}
|
|
}
|
|
else if(participant.Event == "ZRCUserChangedEventJoinedMeeting")
|
|
{
|
|
Status.Call.Participants.Add(participant);
|
|
}
|
|
}
|
|
}
|
|
|
|
PrintCurrentCallParticipants();
|
|
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case eZoomRoomResponseType.zEvent:
|
|
{
|
|
switch (topKey.ToLower())
|
|
{
|
|
case "phonebook":
|
|
{
|
|
if (responseObj["Updated Contact"] != null)
|
|
{
|
|
var updatedContact = JsonConvert.DeserializeObject<zStatus.Contact>(responseObj["Updated Contact"].ToString());
|
|
|
|
var existingContact = Status.Phonebook.Contacts.FirstOrDefault(c => c.Jid.Equals(updatedContact.Jid));
|
|
|
|
if (existingContact != null)
|
|
{
|
|
// Update existing contact
|
|
JsonConvert.PopulateObject(responseObj["Updated Contact"].ToString(), existingContact);
|
|
}
|
|
}
|
|
else if (responseObj["Added Contact"] != null)
|
|
{
|
|
var newContact = JsonConvert.DeserializeObject<zStatus.Contact>(responseObj["Updated Contact"].ToString());
|
|
|
|
// Add a new contact
|
|
Status.Phonebook.Contacts.Add(newContact);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case "bookingslistresult":
|
|
{
|
|
if (!SyncState.InitialSyncComplete)
|
|
SyncState.LastQueryResponseReceived();
|
|
|
|
var codecBookings = new List<zCommand.BookingsListResult>();
|
|
|
|
codecBookings = JsonConvert.DeserializeObject < List<zCommand.BookingsListResult>>(responseObj.ToString());
|
|
|
|
if (codecBookings != null && codecBookings.Count > 0)
|
|
{
|
|
CodecSchedule.Meetings = zCommand.GetGenericMeetingsFromBookingResult(codecBookings);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case "bookings":
|
|
{
|
|
// Bookings have been updated, trigger a query to retreive the new bookings
|
|
if (responseObj["Updated"] != null)
|
|
GetBookings();
|
|
|
|
break;
|
|
}
|
|
case "sharingstate":
|
|
{
|
|
JsonConvert.PopulateObject(responseObj.ToString(), Status.Call.Sharing);
|
|
|
|
break;
|
|
}
|
|
case "incomingcallindication":
|
|
{
|
|
var incomingCall = JsonConvert.DeserializeObject<zEvent.IncomingCallIndication>(responseObj.ToString());
|
|
|
|
if (incomingCall != null)
|
|
{
|
|
var newCall = new CodecActiveCallItem();
|
|
|
|
newCall.Direction = eCodecCallDirection.Incoming;
|
|
newCall.Status = eCodecCallStatus.Ringing;
|
|
newCall.Type = eCodecCallType.Unknown;
|
|
newCall.Name = incomingCall.callerName;
|
|
newCall.Id = incomingCall.callerJID;
|
|
|
|
ActiveCalls.Add(newCall);
|
|
|
|
OnCallStatusChange(newCall);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case "treatedincomingcallindication":
|
|
{
|
|
var incomingCall = JsonConvert.DeserializeObject<zEvent.IncomingCallIndication>(responseObj.ToString());
|
|
|
|
if (incomingCall != null)
|
|
{
|
|
var existingCall = ActiveCalls.FirstOrDefault(c => c.Id.Equals(incomingCall.callerJID));
|
|
|
|
if (existingCall != null)
|
|
{
|
|
if (!incomingCall.accepted)
|
|
{
|
|
existingCall.Status = eCodecCallStatus.Disconnected;
|
|
}
|
|
else
|
|
{
|
|
existingCall.Status = eCodecCallStatus.Connecting;
|
|
}
|
|
|
|
OnCallStatusChange(existingCall);
|
|
}
|
|
|
|
UpdateCallStatus();
|
|
}
|
|
|
|
break;
|
|
}
|
|
case "calldisconnect":
|
|
{
|
|
var disconnectEvent = JsonConvert.DeserializeObject<zEvent.CallDisconnect>(responseObj.ToString());
|
|
|
|
if (disconnectEvent.Successful)
|
|
{
|
|
if (ActiveCalls.Count > 0)
|
|
{
|
|
var activeCall = ActiveCalls.FirstOrDefault(c => c.IsActiveCall);
|
|
|
|
if (activeCall != null)
|
|
{
|
|
activeCall.Status = eCodecCallStatus.Disconnected;
|
|
|
|
OnCallStatusChange(activeCall);
|
|
}
|
|
}
|
|
}
|
|
|
|
UpdateCallStatus();
|
|
break;
|
|
}
|
|
case "callconnecterror":
|
|
{
|
|
UpdateCallStatus();
|
|
break;
|
|
}
|
|
case "videounmuterequest":
|
|
{
|
|
// TODO: notify room of a request to unmute video
|
|
break;
|
|
}
|
|
case "meetingneedspassword":
|
|
{
|
|
// TODO: notify user to enter a password
|
|
break;
|
|
}
|
|
case "needwaitforhost":
|
|
{
|
|
var needWait = JsonConvert.DeserializeObject<zEvent.NeedWaitForHost>(responseObj.ToString());
|
|
|
|
if (needWait.Wait)
|
|
{
|
|
// TODO: notify user to wait for host
|
|
}
|
|
|
|
break;
|
|
}
|
|
case "openvideofailforhoststop":
|
|
{
|
|
// TODO: notify user that host has disabled unmuting video
|
|
break;
|
|
}
|
|
case "updatedcallrecordinfo":
|
|
{
|
|
JsonConvert.PopulateObject(responseObj.ToString(), Status.Call.CallRecordInfo);
|
|
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case eZoomRoomResponseType.zStatus:
|
|
{
|
|
switch (topKey.ToLower())
|
|
{
|
|
case "login":
|
|
{
|
|
SyncState.LoginMessageReceived();
|
|
|
|
if (!SyncState.InitialQueryMessagesWereSent)
|
|
SetUpSyncQueries();
|
|
|
|
JsonConvert.PopulateObject(responseObj.ToString(), Status.Login);
|
|
|
|
break;
|
|
}
|
|
case "systemunit":
|
|
{
|
|
JsonConvert.PopulateObject(responseObj.ToString(), Status.SystemUnit);
|
|
|
|
break;
|
|
}
|
|
case "call":
|
|
{
|
|
JsonConvert.PopulateObject(responseObj.ToString(), Status.Call);
|
|
|
|
UpdateCallStatus();
|
|
|
|
break;
|
|
}
|
|
case "capabilities":
|
|
{
|
|
JsonConvert.PopulateObject(responseObj.ToString(), Status.Capabilities);
|
|
break;
|
|
}
|
|
case "sharing":
|
|
{
|
|
JsonConvert.PopulateObject(responseObj.ToString(), Status.Sharing);
|
|
|
|
break;
|
|
}
|
|
case "numberofscreens":
|
|
{
|
|
JsonConvert.PopulateObject(responseObj.ToString(), Status.NumberOfScreens);
|
|
break;
|
|
}
|
|
case "video":
|
|
{
|
|
JsonConvert.PopulateObject(responseObj.ToString(), Status.Video);
|
|
break;
|
|
}
|
|
case "camerashare":
|
|
{
|
|
JsonConvert.PopulateObject(responseObj.ToString(), Status.CameraShare);
|
|
break;
|
|
}
|
|
case "layout":
|
|
{
|
|
JsonConvert.PopulateObject(responseObj.ToString(), Status.Layout);
|
|
break;
|
|
}
|
|
case "audio input line":
|
|
{
|
|
JsonConvert.PopulateObject(responseObj.ToString(), Status.AudioInputs);
|
|
break;
|
|
}
|
|
case "audio output line":
|
|
{
|
|
JsonConvert.PopulateObject(responseObj.ToString(), Status.AudioOuputs);
|
|
break;
|
|
}
|
|
case "video camera line":
|
|
{
|
|
JsonConvert.PopulateObject(responseObj.ToString(), Status.Cameras);
|
|
|
|
if(!SyncState.CamerasHaveBeenSetUp)
|
|
SetUpCameras();
|
|
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
Debug.Console(1, "Unknown Response Type:");
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.Console(1, this, "Error Deserializing feedback: {0}", ex);
|
|
}
|
|
}
|
|
|
|
public void PrintCurrentCallParticipants()
|
|
{
|
|
if (Debug.Level > 0)
|
|
{
|
|
Debug.Console(1, this, "****************************Call Participants***************************");
|
|
foreach (var participant in Status.Call.Participants)
|
|
{
|
|
Debug.Console(1, this, "Name: {0} Audio: {1} IsHost: {2}", participant.UserName, participant.AudioStatusState, participant.IsHost);
|
|
}
|
|
Debug.Console(1, this, "************************************************************************");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves bookings list
|
|
/// </summary>
|
|
void GetBookings()
|
|
{
|
|
SendText("zCommand Bookings List");
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Updates the current call status
|
|
/// </summary>
|
|
void UpdateCallStatus()
|
|
{
|
|
zStatus.eCallStatus callStatus;
|
|
|
|
if (Status.Call != null)
|
|
{
|
|
callStatus = Status.Call.Status;
|
|
|
|
// If not currently in a meeting, intialize the call object
|
|
if (callStatus != zStatus.eCallStatus.IN_MEETING || callStatus != zStatus.eCallStatus.CONNECTING_MEETING)
|
|
{
|
|
Status.Call = new zStatus.Call();
|
|
Status.Call.Status = callStatus; // set the status after initializing the object
|
|
}
|
|
|
|
if (ActiveCalls.Count == 0)
|
|
{
|
|
if(callStatus == zStatus.eCallStatus.CONNECTING_MEETING)
|
|
{
|
|
var newCall = new CodecActiveCallItem();
|
|
|
|
newCall.Status = eCodecCallStatus.Connecting;
|
|
|
|
ActiveCalls.Add(newCall);
|
|
|
|
OnCallStatusChange(newCall);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var existingCall = ActiveCalls.FirstOrDefault(c => !c.Status.Equals(eCodecCallStatus.Ringing));
|
|
|
|
if (callStatus == zStatus.eCallStatus.IN_MEETING)
|
|
{
|
|
existingCall.Status = eCodecCallStatus.Connected;
|
|
}
|
|
else if (callStatus == zStatus.eCallStatus.NOT_IN_MEETING)
|
|
{
|
|
existingCall.Status = eCodecCallStatus.Disconnected;
|
|
}
|
|
|
|
OnCallStatusChange(existingCall);
|
|
}
|
|
|
|
}
|
|
|
|
Debug.Console(1, this, "****************************Active Calls*********************************");
|
|
|
|
// Clean up any disconnected calls left in the list
|
|
for (int i = 0; i < ActiveCalls.Count; i++)
|
|
{
|
|
var call = ActiveCalls[i];
|
|
|
|
Debug.Console(1, this,
|
|
@"Name: {0}
|
|
ID: {1}
|
|
IsActive: {2}
|
|
Status: {3}
|
|
Direction: {4}", call.Name, call.Id, call.IsActiveCall, call.Status, call.Direction);
|
|
|
|
if (!call.IsActiveCall)
|
|
{
|
|
Debug.Console(1, this, "******Removing Inactive Call: {0}******", call.Name);
|
|
ActiveCalls.Remove(call);
|
|
}
|
|
}
|
|
|
|
Debug.Console(1, this, "**************************************************************************");
|
|
|
|
}
|
|
|
|
public override void StartSharing()
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stops sharing the current presentation
|
|
/// </summary>
|
|
public override void StopSharing()
|
|
{
|
|
SendText("zCommand Call Sharing Disconnect");
|
|
}
|
|
|
|
public override void PrivacyModeOn()
|
|
{
|
|
SendText("zConfiguration Call Microphone Mute: on");
|
|
}
|
|
|
|
public override void PrivacyModeOff()
|
|
{
|
|
SendText("zConfiguration Call Microphone Mute: off");
|
|
}
|
|
|
|
public override void PrivacyModeToggle()
|
|
{
|
|
if (PrivacyModeIsOnFeedback.BoolValue)
|
|
PrivacyModeOff();
|
|
else
|
|
PrivacyModeOn();
|
|
}
|
|
|
|
public override void MuteOff()
|
|
{
|
|
SetVolume((ushort)PreviousVolumeLevel);
|
|
}
|
|
|
|
public override void MuteOn()
|
|
{
|
|
PreviousVolumeLevel = Configuration.Audio.Output.Volume; // Store the previous level for recall
|
|
|
|
SetVolume(0);
|
|
}
|
|
|
|
public override void MuteToggle()
|
|
{
|
|
if (MuteFeedback.BoolValue)
|
|
MuteOff();
|
|
else
|
|
MuteOn();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Increments the voluem
|
|
/// </summary>
|
|
/// <param name="pressRelease"></param>
|
|
public override void VolumeUp(bool pressRelease)
|
|
{
|
|
// TODO: Implment volume increment that calls SetVolume()
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decrements the volume
|
|
/// </summary>
|
|
/// <param name="pressRelease"></param>
|
|
public override void VolumeDown(bool pressRelease)
|
|
{
|
|
// TODO: Implment volume decrement that calls SetVolume()
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scales the level and sets the codec to the specified level within its range
|
|
/// </summary>
|
|
/// <param name="level">level from slider (0-65535 range)</param>
|
|
public override void SetVolume(ushort level)
|
|
{
|
|
var scaledLevel = CrestronEnvironment.ScaleWithLimits(level, 65535, 0, 100, 0);
|
|
SendText(string.Format("zConfiguration Audio Output volume: {0}", scaledLevel));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recalls the default volume on the codec
|
|
/// </summary>
|
|
public void VolumeSetToDefault()
|
|
{
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public override void StandbyActivate()
|
|
{
|
|
// No corresponding function on device
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public override void StandbyDeactivate()
|
|
{
|
|
// No corresponding function on device
|
|
}
|
|
|
|
public override void ExecuteSwitch(object selector)
|
|
{
|
|
(selector as Action)();
|
|
}
|
|
|
|
public void ExecuteSwitch(object inputSelector, object outputSelector, eRoutingSignalType signalType)
|
|
{
|
|
ExecuteSwitch(inputSelector);
|
|
}
|
|
|
|
public override void AcceptCall(CodecActiveCallItem call)
|
|
{
|
|
var incomingCall = ActiveCalls.FirstOrDefault(c => c.Status.Equals(eCodecCallStatus.Ringing) && c.Direction.Equals(eCodecCallDirection.Incoming));
|
|
SendText(string.Format("zCommand Call Accept callerJID: {0}", incomingCall.Id));
|
|
}
|
|
|
|
public override void RejectCall(CodecActiveCallItem call)
|
|
{
|
|
var incomingCall = ActiveCalls.FirstOrDefault(c => c.Status.Equals(eCodecCallStatus.Ringing) && c.Direction.Equals(eCodecCallDirection.Incoming));
|
|
SendText(string.Format("zCommand Call Reject callerJID: {0}", incomingCall.Id));
|
|
}
|
|
|
|
public override void Dial(Meeting meeting)
|
|
{
|
|
SendText(string.Format("zCommand Dial Start meetingNumber: {0}", meeting.Id));
|
|
}
|
|
|
|
public override void Dial(string number)
|
|
{
|
|
SendText(string.Format("zCommand Dial Join meetingNumber: {0}", number));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invites a contact to either a new meeting (if not already in a meeting) or the current meeting.
|
|
/// Currently only invites a single user
|
|
/// </summary>
|
|
/// <param name="contact"></param>
|
|
public override void Dial(IInvitableContact contact)
|
|
{
|
|
var ic = contact as zStatus.ZoomDirectoryContact;
|
|
|
|
if (ic != null)
|
|
{
|
|
Debug.Console(1, this, "Attempting to Dial (Invite): {0}", ic.Name);
|
|
|
|
if (!IsInCall)
|
|
SendText(string.Format("zCommand Invite Duration: {0} user: {1}", DefaultMeetingDurationMin, ic.ContactId));
|
|
else
|
|
SendText(string.Format("zCommand Call invite user: {0}", ic.ContactId));
|
|
}
|
|
}
|
|
|
|
public override void EndCall(CodecActiveCallItem call)
|
|
{
|
|
SendText("zCommand Call Disconnect");
|
|
}
|
|
|
|
public override void EndAllCalls()
|
|
{
|
|
SendText("zCommand Call Disconnect");
|
|
}
|
|
|
|
public override void SendDtmf(string s)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
|
|
#region IHasCodecSelfView Members
|
|
|
|
public BoolFeedback SelfviewIsOnFeedback { get; private set; }
|
|
|
|
public void SelfViewModeOn()
|
|
{
|
|
SendText("zConfiguration Video hide_conf_self_video: off");
|
|
}
|
|
|
|
public void SelfViewModeOff()
|
|
{
|
|
SendText("zConfiguration Video hide_conf_self_video: on");
|
|
}
|
|
|
|
public void SelfViewModeToggle()
|
|
{
|
|
if (SelfviewIsOnFeedback.BoolValue)
|
|
SelfViewModeOff();
|
|
else
|
|
SelfViewModeOn();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IHasDirectory Members
|
|
|
|
public event EventHandler<DirectoryEventArgs> DirectoryResultReturned;
|
|
|
|
/// Call when directory results are updated
|
|
/// </summary>
|
|
/// <param name="result"></param>
|
|
void OnDirectoryResultReturned(CodecDirectory result)
|
|
{
|
|
CurrentDirectoryResultIsNotDirectoryRoot.FireUpdate();
|
|
|
|
// This will return the latest results to all UIs. Multiple indendent UI Directory browsing will require a different methodology
|
|
var handler = DirectoryResultReturned;
|
|
if (handler != null)
|
|
{
|
|
handler(this, new DirectoryEventArgs()
|
|
{
|
|
Directory = result,
|
|
DirectoryIsOnRoot = !CurrentDirectoryResultIsNotDirectoryRoot.BoolValue
|
|
});
|
|
}
|
|
|
|
//PrintDirectory(result);
|
|
}
|
|
|
|
public CodecDirectory DirectoryRoot { get; private set; }
|
|
|
|
public CodecDirectory CurrentDirectoryResult
|
|
{
|
|
get
|
|
{
|
|
if (DirectoryBrowseHistory.Count > 0)
|
|
return DirectoryBrowseHistory[DirectoryBrowseHistory.Count - 1];
|
|
else
|
|
return DirectoryRoot;
|
|
}
|
|
}
|
|
|
|
public CodecPhonebookSyncState PhonebookSyncState { get; private set; }
|
|
|
|
public void SearchDirectory(string searchString)
|
|
{
|
|
var directoryResults = new CodecDirectory();
|
|
|
|
directoryResults.AddContactsToDirectory(DirectoryRoot.CurrentDirectoryResults.FindAll(c => c.Name.IndexOf(searchString, 0, StringComparison.OrdinalIgnoreCase) > -1));
|
|
|
|
DirectoryBrowseHistory.Add(directoryResults);
|
|
|
|
OnDirectoryResultReturned(directoryResults);
|
|
}
|
|
|
|
public void GetDirectoryFolderContents(string folderId)
|
|
{
|
|
var directoryResults = new CodecDirectory();
|
|
|
|
directoryResults.ResultsFolderId = folderId;
|
|
directoryResults.AddContactsToDirectory(DirectoryRoot.CurrentDirectoryResults.FindAll(c => c.FolderId.Equals(folderId)));
|
|
|
|
DirectoryBrowseHistory.Add(directoryResults);
|
|
|
|
OnDirectoryResultReturned(directoryResults);
|
|
}
|
|
|
|
public void SetCurrentDirectoryToRoot()
|
|
{
|
|
DirectoryBrowseHistory.Clear();
|
|
|
|
OnDirectoryResultReturned(DirectoryRoot);
|
|
}
|
|
|
|
public void GetDirectoryParentFolderContents()
|
|
{
|
|
var currentDirectory = new CodecDirectory();
|
|
|
|
if (DirectoryBrowseHistory.Count > 0)
|
|
{
|
|
var lastItemIndex = DirectoryBrowseHistory.Count - 1;
|
|
var parentDirectoryContents = DirectoryBrowseHistory[lastItemIndex];
|
|
|
|
DirectoryBrowseHistory.Remove(DirectoryBrowseHistory[lastItemIndex]);
|
|
|
|
currentDirectory = parentDirectoryContents;
|
|
|
|
}
|
|
else
|
|
{
|
|
currentDirectory = DirectoryRoot;
|
|
}
|
|
|
|
OnDirectoryResultReturned(currentDirectory);
|
|
}
|
|
|
|
public BoolFeedback CurrentDirectoryResultIsNotDirectoryRoot { get; private set; }
|
|
|
|
public List<CodecDirectory> DirectoryBrowseHistory { get; private set; }
|
|
|
|
#endregion
|
|
|
|
#region IHasScheduleAwareness Members
|
|
|
|
public CodecScheduleAwareness CodecSchedule { get; private set; }
|
|
|
|
public void GetSchedule()
|
|
{
|
|
GetBookings();
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Builds the cameras List by using the Zoom Room zStatus.Cameras data. Could later be modified to build from config data
|
|
/// </summary>
|
|
void SetUpCameras()
|
|
{
|
|
SelectedCameraFeedback = new StringFeedback(() => Configuration.Video.Camera.SelectedId);
|
|
|
|
ControllingFarEndCameraFeedback = new BoolFeedback(() => SelectedCamera is IAmFarEndCamera);
|
|
|
|
foreach (var cam in Status.Cameras)
|
|
{
|
|
var camera = new ZoomRoomCamera(cam.id, cam.Name, this);
|
|
|
|
Cameras.Add(camera);
|
|
|
|
if (cam.Selected)
|
|
SelectedCamera = camera;
|
|
}
|
|
|
|
if (IsInCall)
|
|
UpdateFarEndCameras();
|
|
|
|
SyncState.CamerasSetUp();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dynamically creates far end cameras for call participants who have far end control enabled.
|
|
/// </summary>
|
|
void UpdateFarEndCameras()
|
|
{
|
|
// TODO: set up far end cameras for the current call
|
|
}
|
|
|
|
#region IHasCameras Members
|
|
|
|
public event EventHandler<CameraSelectedEventArgs> CameraSelected;
|
|
|
|
public List<CameraBase> Cameras { get; private set; }
|
|
|
|
private CameraBase _selectedCamera;
|
|
|
|
public CameraBase SelectedCamera
|
|
{
|
|
get
|
|
{
|
|
return _selectedCamera;
|
|
}
|
|
private set
|
|
{
|
|
_selectedCamera = value;
|
|
SelectedCameraFeedback.FireUpdate();
|
|
ControllingFarEndCameraFeedback.FireUpdate();
|
|
|
|
var handler = CameraSelected;
|
|
if (handler != null)
|
|
{
|
|
handler(this, new CameraSelectedEventArgs(SelectedCamera));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public StringFeedback SelectedCameraFeedback { get; private set; }
|
|
|
|
public void SelectCamera(string key)
|
|
{
|
|
if(Cameras != null)
|
|
{
|
|
var camera = Cameras.FirstOrDefault(c => c.Key.IndexOf(key, StringComparison.OrdinalIgnoreCase) > -1);
|
|
if (camera != null)
|
|
{
|
|
Debug.Console(1, this, "Selected Camera with key: '{0}'", camera.Key);
|
|
SelectedCamera = camera;
|
|
}
|
|
else
|
|
Debug.Console(1, this, "Unable to select camera with key: '{0}'", key);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IHasFarEndCameraControl Members
|
|
|
|
public CameraBase FarEndCamera { get; private set; }
|
|
|
|
public BoolFeedback ControllingFarEndCameraFeedback { get; private set; }
|
|
|
|
#endregion
|
|
}
|
|
|
|
/// <summary>
|
|
/// Zoom Room specific info object
|
|
/// </summary>
|
|
public class ZoomRoomInfo : VideoCodecInfo
|
|
{
|
|
public ZoomRoomStatus Status { get; private set; }
|
|
public ZoomRoomConfiguration Configuration { get; private set; }
|
|
|
|
public override bool AutoAnswerEnabled
|
|
{
|
|
get
|
|
{
|
|
return Status.SystemUnit.RoomInfo.AutoAnswerIsEnabled;
|
|
}
|
|
}
|
|
|
|
public override string E164Alias
|
|
{
|
|
get
|
|
{
|
|
if (!string.IsNullOrEmpty(Status.SystemUnit.MeetingNumber))
|
|
return Status.SystemUnit.MeetingNumber;
|
|
else
|
|
return string.Empty;
|
|
}
|
|
}
|
|
|
|
public override string H323Id
|
|
{
|
|
get
|
|
{
|
|
if (!string.IsNullOrEmpty(Status.Call.Info.meeting_list_item.third_party.h323_address))
|
|
return Status.Call.Info.meeting_list_item.third_party.h323_address;
|
|
else
|
|
return string.Empty;
|
|
}
|
|
}
|
|
|
|
public override string IpAddress
|
|
{
|
|
get
|
|
{
|
|
if (!string.IsNullOrEmpty(Status.SystemUnit.RoomInfo.AccountEmail))
|
|
return Status.SystemUnit.RoomInfo.AccountEmail;
|
|
else
|
|
return string.Empty;
|
|
}
|
|
}
|
|
|
|
public override bool MultiSiteOptionIsEnabled
|
|
{
|
|
get { return true; }
|
|
}
|
|
|
|
public override string SipPhoneNumber
|
|
{
|
|
get
|
|
{
|
|
if (!string.IsNullOrEmpty(Status.Call.Info.dialIn))
|
|
return Status.Call.Info.dialIn;
|
|
else
|
|
return string.Empty;
|
|
}
|
|
}
|
|
|
|
public override string SipUri
|
|
{
|
|
get
|
|
{
|
|
if (!string.IsNullOrEmpty(Status.Call.Info.meeting_list_item.third_party.sip_address))
|
|
return Status.Call.Info.meeting_list_item.third_party.sip_address;
|
|
else
|
|
return string.Empty;
|
|
}
|
|
}
|
|
|
|
public ZoomRoomInfo(ZoomRoomStatus status, ZoomRoomConfiguration configuration)
|
|
{
|
|
Status = status;
|
|
Configuration = configuration;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tracks the initial sycnronization state when establishing a new connection
|
|
/// </summary>
|
|
public class ZoomRoomSyncState : IKeyed
|
|
{
|
|
bool _InitialSyncComplete;
|
|
|
|
public event EventHandler<EventArgs> InitialSyncCompleted;
|
|
|
|
private CrestronQueue<string> SyncQueries;
|
|
|
|
private ZoomRoom Parent;
|
|
|
|
public string Key { get; private set; }
|
|
|
|
public bool InitialSyncComplete
|
|
{
|
|
get { return _InitialSyncComplete; }
|
|
private set
|
|
{
|
|
if (value == true)
|
|
{
|
|
var handler = InitialSyncCompleted;
|
|
if (handler != null)
|
|
handler(this, new EventArgs());
|
|
}
|
|
_InitialSyncComplete = value;
|
|
}
|
|
}
|
|
|
|
public bool LoginMessageWasReceived { get; private set; }
|
|
|
|
public bool InitialQueryMessagesWereSent { get; private set; }
|
|
|
|
public bool LastQueryResponseWasReceived { get; private set; }
|
|
|
|
public bool CamerasHaveBeenSetUp { get; private set;}
|
|
|
|
public ZoomRoomSyncState(string key, ZoomRoom parent)
|
|
{
|
|
Parent = parent;
|
|
Key = key;
|
|
SyncQueries = new CrestronQueue<string>(50);
|
|
CodecDisconnected();
|
|
}
|
|
|
|
public void StartSync()
|
|
{
|
|
DequeueQueries();
|
|
}
|
|
|
|
void DequeueQueries()
|
|
{
|
|
while (!SyncQueries.IsEmpty)
|
|
{
|
|
var query = SyncQueries.Dequeue();
|
|
|
|
Parent.SendText(query);
|
|
}
|
|
|
|
InitialQueryMessagesSent();
|
|
}
|
|
|
|
public void AddQueryToQueue(string query)
|
|
{
|
|
SyncQueries.Enqueue(query);
|
|
}
|
|
|
|
public void LoginMessageReceived()
|
|
{
|
|
LoginMessageWasReceived = true;
|
|
Debug.Console(1, this, "Login Message Received.");
|
|
CheckSyncStatus();
|
|
}
|
|
|
|
public void InitialQueryMessagesSent()
|
|
{
|
|
InitialQueryMessagesWereSent = true;
|
|
Debug.Console(1, this, "Query Messages Sent.");
|
|
CheckSyncStatus();
|
|
}
|
|
|
|
public void LastQueryResponseReceived()
|
|
{
|
|
LastQueryResponseWasReceived = true;
|
|
Debug.Console(1, this, "Last Query Response Received.");
|
|
CheckSyncStatus();
|
|
}
|
|
|
|
public void CamerasSetUp()
|
|
{
|
|
CamerasHaveBeenSetUp = true;
|
|
Debug.Console(1, this, "Cameras Set Up.");
|
|
CheckSyncStatus();
|
|
}
|
|
|
|
public void CodecDisconnected()
|
|
{
|
|
SyncQueries.Clear();
|
|
LoginMessageWasReceived = false;
|
|
InitialQueryMessagesWereSent = false;
|
|
LastQueryResponseWasReceived = false;
|
|
CamerasHaveBeenSetUp = false;
|
|
InitialSyncComplete = false;
|
|
}
|
|
|
|
void CheckSyncStatus()
|
|
{
|
|
if (LoginMessageWasReceived && InitialQueryMessagesWereSent && LastQueryResponseWasReceived && CamerasHaveBeenSetUp)
|
|
{
|
|
InitialSyncComplete = true;
|
|
Debug.Console(1, this, "Initial Codec Sync Complete!");
|
|
}
|
|
else
|
|
InitialSyncComplete = false;
|
|
}
|
|
}
|
|
} |