using System; using System.Collections.Generic; using System.Linq; using Crestron.SimplSharp; using Crestron.SimplSharpPro.DeviceSupport; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using PepperDash.Core; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Routing; using PepperDash.Essentials.Devices.Common.Cameras; using PepperDash.Essentials.Devices.Common.Codec; using Serilog.Events; namespace PepperDash.Essentials.Devices.Common.VideoCodec { /// /// Represents a MockVC /// public class MockVC : VideoCodecBase, IRoutingSource, IHasCallHistory, IHasScheduleAwareness, IHasCallFavorites, IHasDirectory, IHasCodecCameras, IHasCameraAutoMode, IHasCodecRoomPresets { /// /// Gets or sets the PropertiesConfig /// public MockVcPropertiesConfig PropertiesConfig; /// /// Gets or sets the CodecOsdIn /// public RoutingInputPort CodecOsdIn { get; private set; } /// /// Gets or sets the HdmiIn1 /// public RoutingInputPort HdmiIn1 { get; private set; } /// /// Gets or sets the HdmiIn2 /// public RoutingInputPort HdmiIn2 { get; private set; } /// /// Gets or sets the HdmiOut /// public RoutingOutputPort HdmiOut { get; private set; } /// /// Gets or sets the CallFavorites /// public CodecCallFavorites CallFavorites { get; private set; } /// /// /// public MockVC(DeviceConfig config) : base(config) { PropertiesConfig = JsonConvert.DeserializeObject(config.Properties.ToString()); CodecInfo = new MockCodecInfo(); // Get favoritesw if (PropertiesConfig.Favorites != null) { CallFavorites = new CodecCallFavorites(); CallFavorites.Favorites = PropertiesConfig.Favorites; } DirectoryBrowseHistory = new List(); // Debug helpers MuteFeedback.OutputChange += (o, a) => Debug.LogMessage(LogEventLevel.Debug, this, "Mute={0}", _IsMuted); PrivacyModeIsOnFeedback.OutputChange += (o, a) => Debug.LogMessage(LogEventLevel.Debug, this, "Privacy={0}", _PrivacyModeIsOn); SharingSourceFeedback.OutputChange += (o, a) => Debug.LogMessage(LogEventLevel.Debug, this, "SharingSource={0}", _SharingSource); VolumeLevelFeedback.OutputChange += (o, a) => Debug.LogMessage(LogEventLevel.Debug, this, "Volume={0}", _VolumeLevel); CurrentDirectoryResultIsNotDirectoryRoot = new BoolFeedback("currentDirectoryResultIsNotDirectoryRoot", () => DirectoryBrowseHistory.Count > 0); CurrentDirectoryResultIsNotDirectoryRoot.FireUpdate(); CodecOsdIn = new RoutingInputPort(RoutingPortNames.CodecOsd, eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, 0, this); InputPorts.Add(CodecOsdIn); HdmiIn1 = new RoutingInputPort(RoutingPortNames.HdmiIn1, eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, 1, this); InputPorts.Add(HdmiIn1); HdmiIn2 = new RoutingInputPort(RoutingPortNames.HdmiIn2, eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, 2, this); InputPorts.Add(HdmiIn2); HdmiOut = new RoutingOutputPort(RoutingPortNames.HdmiOut, eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, null, this); OutputPorts.Add(HdmiOut); CallHistory = new CodecCallHistory(); for (int i = 0; i < 10; i++) { var call = new CodecCallHistory.CallHistoryEntry(); call.Name = "Call " + i; call.Number = i + "@call.com"; CallHistory.RecentCalls.Add(call); } // eventually fire history event here SetupCameras(); CreateOsdSource(); SetIsReady(); } /// protected override Func MuteFeedbackFunc { get { return () => _IsMuted; } } bool _IsMuted; /// protected override Func PrivacyModeIsOnFeedbackFunc { get { return () => _PrivacyModeIsOn; } } bool _PrivacyModeIsOn; /// protected override Func SharingSourceFeedbackFunc { get { return () => _SharingSource; } } string _SharingSource; /// protected override Func SharingContentIsOnFeedbackFunc { get { return () => _SharingIsOn; } } bool _SharingIsOn; /// protected override Func VolumeLevelFeedbackFunc { get { return () => _VolumeLevel; } } int _VolumeLevel; /// protected override Func StandbyIsOnFeedbackFunc { get { return () => _StandbyIsOn; } } bool _StandbyIsOn; /// /// Creates the fake OSD source, and connects it's AudioVideo output to the CodecOsdIn input /// to enable routing /// private 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. } /// public override void Dial(string number) { Debug.LogMessage(LogEventLevel.Debug, this, "Dial: {0}", number); var call = new CodecActiveCallItem() { Name = number, Number = number, Id = number, Status = eCodecCallStatus.Dialing, Direction = eCodecCallDirection.Outgoing, Type = eCodecCallType.Video }; ActiveCalls.Add(call); OnCallStatusChange(call); //ActiveCallCountFeedback.FireUpdate(); // Simulate 2-second ring, then connecting, then connected new CTimer(o => { call.Type = eCodecCallType.Video; SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connecting, call); new CTimer(oo => SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connected, call), 1000); }, 2000); } /// public override void Dial(Meeting meeting) { Debug.LogMessage(LogEventLevel.Debug, this, "Dial Meeting: {0}", meeting.Id); var call = new CodecActiveCallItem() { Name = meeting.Title, Number = meeting.Id, Id = meeting.Id, Status = eCodecCallStatus.Dialing, Direction = eCodecCallDirection.Outgoing, Type = eCodecCallType.Video }; ActiveCalls.Add(call); OnCallStatusChange(call); //ActiveCallCountFeedback.FireUpdate(); // Simulate 2-second ring, then connecting, then connected new CTimer(o => { call.Type = eCodecCallType.Video; SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connecting, call); new CTimer(oo => SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connected, call), 1000); }, 2000); } /// public override void EndCall(CodecActiveCallItem call) { Debug.LogMessage(LogEventLevel.Debug, this, "EndCall"); ActiveCalls.Remove(call); SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Disconnected, call); //ActiveCallCountFeedback.FireUpdate(); } /// public override void EndAllCalls() { Debug.LogMessage(LogEventLevel.Debug, this, "EndAllCalls"); for (int i = ActiveCalls.Count - 1; i >= 0; i--) { var call = ActiveCalls[i]; ActiveCalls.Remove(call); SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Disconnected, call); } //ActiveCallCountFeedback.FireUpdate(); } /// public override void AcceptCall(CodecActiveCallItem call) { Debug.LogMessage(LogEventLevel.Debug, this, "AcceptCall"); SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connecting, call); new CTimer(o => SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connected, call), 1000); // should already be in active list } /// public override void RejectCall(CodecActiveCallItem call) { Debug.LogMessage(LogEventLevel.Debug, this, "RejectCall"); ActiveCalls.Remove(call); SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Disconnected, call); //ActiveCallCountFeedback.FireUpdate(); } /// public override void SendDtmf(string s) { Debug.LogMessage(LogEventLevel.Debug, this, "SendDTMF: {0}", s); } /// public override void StartSharing() { _SharingIsOn = true; SharingContentIsOnFeedback.FireUpdate(); } /// public override void StopSharing() { _SharingIsOn = false; SharingContentIsOnFeedback.FireUpdate(); } /// public override void StandbyActivate() { _StandbyIsOn = true; } /// public override void StandbyDeactivate() { _StandbyIsOn = false; } /// public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) { throw new NotImplementedException(); } /// public override void ExecuteSwitch(object selector) { Debug.LogMessage(LogEventLevel.Debug, this, "ExecuteSwitch: {0}", selector); _SharingSource = selector.ToString(); } /// public override void MuteOff() { _IsMuted = false; MuteFeedback.FireUpdate(); } /// public override void MuteOn() { _IsMuted = true; MuteFeedback.FireUpdate(); } /// public override void MuteToggle() { _IsMuted = !_IsMuted; MuteFeedback.FireUpdate(); } /// public override void SetVolume(ushort level) { _VolumeLevel = level; VolumeLevelFeedback.FireUpdate(); } /// public override void VolumeDown(bool pressRelease) { } /// public override void VolumeUp(bool pressRelease) { } /// public override void PrivacyModeOn() { Debug.LogMessage(LogEventLevel.Debug, this, "PrivacyMuteOn"); if (_PrivacyModeIsOn) return; _PrivacyModeIsOn = true; PrivacyModeIsOnFeedback.FireUpdate(); } /// public override void PrivacyModeOff() { Debug.LogMessage(LogEventLevel.Debug, this, "PrivacyMuteOff"); if (!_PrivacyModeIsOn) return; _PrivacyModeIsOn = false; PrivacyModeIsOnFeedback.FireUpdate(); } /// public override void PrivacyModeToggle() { _PrivacyModeIsOn = !_PrivacyModeIsOn; Debug.LogMessage(LogEventLevel.Debug, this, "PrivacyMuteToggle: {0}", _PrivacyModeIsOn); PrivacyModeIsOnFeedback.FireUpdate(); } //******************************************************** // SIMULATION METHODS /// /// TestIncomingVideoCall method /// /// public void TestIncomingVideoCall(string url) { Debug.LogMessage(LogEventLevel.Debug, this, "TestIncomingVideoCall from {0}", url); var call = new CodecActiveCallItem() { Name = url, Id = url, Number = url, Type = eCodecCallType.Video, Direction = eCodecCallDirection.Incoming }; ActiveCalls.Add(call); SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Ringing, call); //OnCallStatusChange(eCodecCallStatus.Unknown, eCodecCallStatus.Ringing, call); } /// /// TestIncomingAudioCall method /// /// public void TestIncomingAudioCall(string url) { Debug.LogMessage(LogEventLevel.Debug, this, "TestIncomingAudioCall from {0}", url); var call = new CodecActiveCallItem() { Name = url, Id = url, Number = url, Type = eCodecCallType.Audio, Direction = eCodecCallDirection.Incoming }; ActiveCalls.Add(call); SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Ringing, call); //OnCallStatusChange(eCodecCallStatus.Unknown, eCodecCallStatus.Ringing, call); } /// /// TestFarEndHangup method /// public void TestFarEndHangup() { Debug.LogMessage(LogEventLevel.Debug, this, "TestFarEndHangup"); } #region IHasCallHistory Members /// /// CallHistory property /// public CodecCallHistory CallHistory { get; private set; } /// /// RemoveCallHistoryEntry method /// public void RemoveCallHistoryEntry(CodecCallHistory.CallHistoryEntry entry) { } #endregion #region IHasScheduleAwareness Members /// /// GetSchedule method /// public void GetSchedule() { } /// /// CodecSchedule property /// public CodecScheduleAwareness CodecSchedule { get { // if the last meeting has past, generate a new list if (_CodecSchedule == null || _CodecSchedule.Meetings.Count == 0 || _CodecSchedule.Meetings[_CodecSchedule.Meetings.Count - 1].StartTime < DateTime.Now) { _CodecSchedule = new CodecScheduleAwareness(1000); for (int i = 0; i < 5; i++) { var m = new Meeting(); m.MinutesBeforeMeeting = 5; m.Id = i.ToString(); m.Organizer = "Employee " + 1; m.StartTime = DateTime.Now.AddMinutes(5).AddHours(i); m.EndTime = DateTime.Now.AddHours(i).AddMinutes(50); m.Title = "Meeting " + i; m.Calls.Add(new Call() { Number = i + "meeting@fake.com" }); _CodecSchedule.Meetings.Add(m); } } return _CodecSchedule; } } CodecScheduleAwareness _CodecSchedule; #endregion #region IHasDirectory Members /// /// DirectoryResultReturned event. Fired when the directory result changes /// public event EventHandler DirectoryResultReturned; /// /// DirectoryRoot property. The root of the directory /// public CodecDirectory DirectoryRoot { get { return MockVideoCodecDirectory.DirectoryRoot; } } /// /// CurrentDirectoryResult property. The current directory result /// public CodecDirectory CurrentDirectoryResult { get { if (DirectoryBrowseHistory.Count > 0) return DirectoryBrowseHistory[DirectoryBrowseHistory.Count - 1]; else return DirectoryRoot; } } /// /// PhonebookSyncState property. The current state of the phonebook synchronization /// public CodecPhonebookSyncState PhonebookSyncState { get { var syncState = new CodecPhonebookSyncState(Key + "PhonebookSync"); syncState.InitialPhonebookFoldersReceived(); syncState.PhonebookRootEntriesReceived(); syncState.SetPhonebookHasFolders(true); syncState.SetNumberOfContacts(0); // just need to call this method for the sync to complete return syncState; } } /// /// Search the directory for contacts that contain the search string /// /// The search string to use public void SearchDirectory(string searchString) { var searchResults = new CodecDirectory(); searchResults.ResultsFolderId = "searchResult"; // Search mock directory for contacts that contain the search string, ignoring case List matches = MockVideoCodecDirectory.CompleteDirectory.CurrentDirectoryResults.FindAll( s => s is DirectoryContact && s.Name.ToLower().Contains(searchString.ToLower())); if (matches != null) { searchResults.AddContactsToDirectory(matches); DirectoryBrowseHistory.Add(searchResults); } OnDirectoryResultReturned(searchResults); } /// /// Get the contents of the specified folder /// /// The ID of the folder to get the contents of public void GetDirectoryFolderContents(string folderId) { var folderDirectory = new CodecDirectory(); if (folderId == MockVideoCodecDirectory.eFolderId.UnitedStates.ToString()) folderDirectory = MockVideoCodecDirectory.UnitedStatesFolderContents; else if (folderId == MockVideoCodecDirectory.eFolderId.Canada.ToString()) folderDirectory = MockVideoCodecDirectory.CanadaFolderContents; else if (folderId == MockVideoCodecDirectory.eFolderId.NewYork.ToString()) folderDirectory = MockVideoCodecDirectory.NewYorkFolderContents; else if (folderId == MockVideoCodecDirectory.eFolderId.Boston.ToString()) folderDirectory = MockVideoCodecDirectory.BostonFolderContents; else if (folderId == MockVideoCodecDirectory.eFolderId.SanFrancisco.ToString()) folderDirectory = MockVideoCodecDirectory.SanFranciscoFolderContents; else if (folderId == MockVideoCodecDirectory.eFolderId.Denver.ToString()) folderDirectory = MockVideoCodecDirectory.DenverFolderContents; else if (folderId == MockVideoCodecDirectory.eFolderId.Austin.ToString()) folderDirectory = MockVideoCodecDirectory.AustinFolderContents; else if (folderId == MockVideoCodecDirectory.eFolderId.Calgary.ToString()) folderDirectory = MockVideoCodecDirectory.CalgaryFolderContents; DirectoryBrowseHistory.Add(folderDirectory); OnDirectoryResultReturned(folderDirectory); } /// /// Set the current directory to the root /// public void SetCurrentDirectoryToRoot() { DirectoryBrowseHistory.Clear(); OnDirectoryResultReturned(DirectoryRoot); } /// /// Get the contents of the parent folder /// 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); } /// /// Gets or sets the CurrentDirectoryResultIsNotDirectoryRoot /// public BoolFeedback CurrentDirectoryResultIsNotDirectoryRoot { get; private set; } /// /// Gets or sets the DirectoryBrowseHistory /// public List DirectoryBrowseHistory { get; private set; } /// /// OnDirectoryResultReturned method /// public void OnDirectoryResultReturned(CodecDirectory result) { CurrentDirectoryResultIsNotDirectoryRoot.FireUpdate(); DirectoryResultReturned?.Invoke(this, new DirectoryEventArgs() { Directory = result, DirectoryIsOnRoot = !CurrentDirectoryResultIsNotDirectoryRoot.BoolValue }); } #endregion void SetupCameras() { SupportsCameraAutoMode = true; SupportsCameraOff = false; Cameras = new List(); var internalCamera = new MockVCCamera(Key + "-camera1", "Near End", this); Cameras.Add(internalCamera); var farEndCamera = new MockFarEndVCCamera(Key + "-cameraFar", "Far End", this); Cameras.Add(farEndCamera); SelectedCameraFeedback = new StringFeedback("selectedCamera", () => SelectedCamera.Key); ControllingFarEndCameraFeedback = new BoolFeedback("controllingFarEndCamera", () => SelectedCamera is IAmFarEndCamera); CameraAutoModeIsOnFeedback = new BoolFeedback("cameraAutoModeIsOn", () => _CameraAutoModeIsOn); SupportsCameraAutoMode = true; CameraAutoModeIsOnFeedback.FireUpdate(); DeviceManager.AddDevice(internalCamera); DeviceManager.AddDevice(farEndCamera); NearEndPresets = new List(15); // Fix the capacity to emulate Cisco if (PropertiesConfig.Presets != null && PropertiesConfig.Presets.Count > 0) { NearEndPresets = PropertiesConfig.Presets; } else { for (int i = 1; i <= NearEndPresets.Capacity; i++) { var label = string.Format("Near End Preset {0}", i); NearEndPresets.Add(new CodecRoomPreset(i, label, true, false)); } } FarEndRoomPresets = new List(15); // Fix the capacity to emulate Cisco // Add the far end presets for (int i = 1; i <= FarEndRoomPresets.Capacity; i++) { var label = string.Format("Far End Preset {0}", i); FarEndRoomPresets.Add(new CodecRoomPreset(i, label, true, false)); } SelectedCamera = internalCamera; ; // call the method to select the camera and ensure the feedbacks get updated. } #region IHasCameras Members /// /// CameraSelected event. Fired when a camera is selected /// public event EventHandler CameraSelected; /// /// Gets the list of cameras associated with this codec /// public List Cameras { get; private set; } private CameraBase _selectedCamera; /// /// Returns the selected camera /// public CameraBase SelectedCamera { get { return _selectedCamera; } private set { _selectedCamera = value; SelectedCameraFeedback.FireUpdate(); ControllingFarEndCameraFeedback.FireUpdate(); CameraSelected?.Invoke(this, new CameraSelectedEventArgs(SelectedCamera)); } } /// /// Gets or sets the SelectedCameraFeedback /// public StringFeedback SelectedCameraFeedback { get; private set; } /// /// SelectCamera method /// public void SelectCamera(string key) { var camera = Cameras.FirstOrDefault(c => c.Key.ToLower().IndexOf(key.ToLower()) > -1); if (camera != null) { Debug.LogMessage(LogEventLevel.Verbose, this, "Selected Camera with key: '{0}'", camera.Key); SelectedCamera = camera; } else Debug.LogMessage(LogEventLevel.Verbose, this, "Unable to select camera with key: '{0}'", key); } #endregion #region IHasFarEndCameraControl Members /// /// Gets or sets the FarEndCamera /// public CameraBase FarEndCamera { get; private set; } /// /// Gets or sets the ControllingFarEndCameraFeedback /// public BoolFeedback ControllingFarEndCameraFeedback { get; private set; } #endregion #region IHasCameraAutoMode Members private bool _CameraAutoModeIsOn; /// /// CameraAutoModeOn method /// public void CameraAutoModeOn() { _CameraAutoModeIsOn = true; CameraAutoModeIsOnFeedback.FireUpdate(); } /// /// CameraAutoModeOff method /// public void CameraAutoModeOff() { _CameraAutoModeIsOn = false; CameraAutoModeIsOnFeedback.FireUpdate(); } /// /// CameraAutoModeToggle method /// public void CameraAutoModeToggle() { if (_CameraAutoModeIsOn) _CameraAutoModeIsOn = false; else _CameraAutoModeIsOn = true; CameraAutoModeIsOnFeedback.FireUpdate(); } /// /// Gets or sets the CameraAutoModeIsOnFeedback /// public BoolFeedback CameraAutoModeIsOnFeedback { get; private set; } #endregion #region IHasCameraPresets Members /// /// CodecRoomPresetsListHasChanged event. Fired when the presets list changes /// public event EventHandler CodecRoomPresetsListHasChanged; /// /// Gets or sets the NearEndPresets /// public List NearEndPresets { get; private set; } /// /// Gets or sets the FarEndRoomPresets /// public List FarEndRoomPresets { get; private set; } /// /// CodecRoomPresetSelect method /// public void CodecRoomPresetSelect(int preset) { if (SelectedCamera is IAmFarEndCamera) { Debug.LogMessage(LogEventLevel.Debug, this, "Selecting Far End Preset: {0}", preset); } else { Debug.LogMessage(LogEventLevel.Debug, this, "Selecting Near End Preset: {0}", preset); } } /// /// CodecRoomPresetStore method /// public void CodecRoomPresetStore(int preset, string description) { var editPreset = NearEndPresets.FirstOrDefault(p => p.ID.Equals(preset)); if (editPreset != null) { editPreset.Defined = true; editPreset.Description = description; } else NearEndPresets.Add(new CodecRoomPreset(preset, description, true, true)); CodecRoomPresetsListHasChanged?.Invoke(this, new EventArgs()); // Update the config SetConfig(Config); } /// /// SelectFarEndPreset method /// public void SelectFarEndPreset(int i) { Debug.LogMessage(LogEventLevel.Debug, this, "Selecting Far End Preset: {0}", i); } #endregion /// protected override void CustomSetConfig(DeviceConfig config) { PropertiesConfig.Presets = NearEndPresets; Config.Properties = JToken.FromObject(PropertiesConfig); ConfigWriter.UpdateDeviceConfig(config); } } /// /// Represents a MockCodecInfo /// public class MockCodecInfo : VideoCodecInfo { /// public override bool MultiSiteOptionIsEnabled { get { return true; } } /// public override string E164Alias { get { return "someE164alias"; } } /// public override string H323Id { get { return "someH323Id"; } } /// public override string IpAddress { get { return "xxx.xxx.xxx.xxx"; } } /// public override string SipPhoneNumber { get { return "333-444-5555"; } } /// public override string SipUri { get { return "mock@someurl.com"; } } /// public override bool AutoAnswerEnabled { get { return _AutoAnswerEnabled; } } bool _AutoAnswerEnabled; /// /// SetAutoAnswer method /// public void SetAutoAnswer(bool value) { _AutoAnswerEnabled = value; } } /// /// Represents a MockVCFactory /// public class MockVCFactory : EssentialsDeviceFactory { /// public MockVCFactory() { TypeNames = new List() { "mockvc" }; } /// public override EssentialsDevice BuildDevice(DeviceConfig dc) { Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new MockVC Device"); return new VideoCodec.MockVC(dc); } } }