diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/MockVC/MockVC.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/MockVC/MockVC.cs index 4dcbee11..d728eef4 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/MockVC/MockVC.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/MockVC/MockVC.cs @@ -14,6 +14,7 @@ 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.VideoCodec.Interfaces; using Serilog.Events; namespace PepperDash.Essentials.Devices.Common.VideoCodec @@ -21,7 +22,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec /// /// Represents a MockVC /// - public class MockVC : VideoCodecBase, IHasCallHistory, IHasScheduleAwareness, IHasCallFavorites, IHasDirectory, IHasCodecCameras, IHasCameraAutoMode, IHasCodecRoomPresets, IRoutingSinkWithFeedback + public class MockVC : VideoCodecBase, IHasCallHistory, IHasScheduleAwareness, IHasCallFavorites, IHasDirectory, + IHasCodecCameras, IHasCameraAutoMode, IHasCodecRoomPresets, IRoutingSinkWithFeedback, IHasParticipants { /// /// Gets or sets the PropertiesConfig @@ -45,6 +47,9 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec /// public RoutingOutputPort HdmiOut1 { get; private set; } + /// + /// Gets or sets the HdmiOut2 + /// public RoutingOutputPort HdmiOut2 { get; private set; } /// @@ -52,8 +57,10 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec /// public CodecCallFavorites CallFavorites { get; private set; } + private readonly CodecParticipants _participants; + /// - /// + /// Initializes a new instance of the class. /// public MockVC(DeviceConfig config) : base(config) @@ -71,6 +78,23 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec DirectoryBrowseHistory = new List(); + _participants = new CodecParticipants(); + + // Populate a fake participants list whenever a call connects, and clear it once all + // calls have ended, so IHasParticipants consumers (e.g. the roster UI) have something + // to display without needing a real far-end connection. + CallStatusChange += (o, a) => + { + if (a.CallItem.Status == eCodecCallStatus.Connected) + { + PopulateFakeParticipants(); + } + else if (a.CallItem.Status == eCodecCallStatus.Disconnected && ActiveCalls.Count == 0) + { + Participants.CurrentParticipants = 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); @@ -209,6 +233,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec } + /// public override void Dial(string number, string password) { Debug.LogMessage(LogEventLevel.Debug, this, "Dial: {0} with password: {1}", number, password); @@ -652,6 +677,65 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec #endregion + #region IHasParticipants Members + + /// + /// Gets the collection of call participants + /// + public CodecParticipants Participants + { + get { return _participants; } + } + + /// + /// Builds a fake set of participants and assigns it to , + /// simulating a codec that has joined a call with other people already present. + /// + private void PopulateFakeParticipants() + { + Participants.CurrentParticipants = new List + { + new Participant { UserId = 1, Name = "Myself", IsHost = true, IsMyself = true, CanMuteAudio = true, CanUnmuteVideo = true }, + new Participant { UserId = 2, Name = "Priya Chandran", CanMuteAudio = true, CanUnmuteVideo = true }, + new Participant { UserId = 3, Name = "Miles Anderson", CanMuteAudio = true, CanUnmuteVideo = true, AudioMuteFb = true }, + new Participant { UserId = 4, Name = "Owen Bradshaw", CanMuteAudio = true, CanUnmuteVideo = true, HandIsRaisedFb = true }, + }; + } + + /// + public void RemoveParticipant(int userId) + { + Debug.LogMessage(LogEventLevel.Debug, this, "RemoveParticipant: {0}", userId); + + var remaining = Participants.CurrentParticipants.Where(p => p.UserId != userId).ToList(); + + Participants.CurrentParticipants = remaining; + } + + /// + public void SetParticipantAsHost(int userId) + { + Debug.LogMessage(LogEventLevel.Debug, this, "SetParticipantAsHost: {0}", userId); + + foreach (var p in Participants.CurrentParticipants) + { + p.IsHost = p.UserId == userId; + } + + // Re-assign to trigger ParticipantsListHasChanged, since the mutations above happened + // on the existing list/items in place. + Participants.CurrentParticipants = Participants.CurrentParticipants; + } + + /// + public void AdmitParticipantFromWaitingRoom(int userId) + { + // No waiting room is simulated for this mock codec. + Debug.LogMessage(LogEventLevel.Debug, this, "AdmitParticipantFromWaitingRoom: {0} (no-op, mock has no waiting room)", userId); + } + + #endregion + void SetupCameras() { SupportsCameraAutoMode = true; @@ -884,20 +968,42 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec #region IRoutingSinkWithFeedback Members + /// + /// Gets the currently routed input port for this sink + /// public RoutingInputPort CurrentInputPort { get; private set; } + /// + /// Event fired when the currently routed input changes + /// public event InputChangedEventHandler InputChanged; #endregion #region ICurrentSources Members + /// + /// Gets the currently routed source devices, keyed by signal type + /// public Dictionary CurrentSources { get; private set; } = new Dictionary(); + /// + /// Gets the keys of the currently routed source devices, keyed by signal type + /// public Dictionary CurrentSourceKeys { get; private set; } = new Dictionary(); + /// + /// Event fired when the currently routed source for a signal type changes + /// public event EventHandler CurrentSourcesChanged; + /// + /// Sets the currently routed source device for the given signal type, updating + /// and and firing + /// + /// + /// The signal type being routed + /// The source device now routed to this signal type, or null if cleared public void SetCurrentSource(eRoutingSignalType signalType, IRoutingSource sourceDevice) { CurrentSources.TryGetValue(signalType, out var previousSource); diff --git a/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs b/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs index 6e7a1f5b..bc50316c 100644 --- a/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs +++ b/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs @@ -1273,9 +1273,13 @@ namespace PepperDash.Essentials.WebSocketServer Debug.LogMessage(LogEventLevel.Verbose, "Requesting Image: {0}", this, path); - var imageBasePath = Global.DirectorySeparator + "html" + Global.DirectorySeparator + "logo" + Global.DirectorySeparator; + // Served from the program's own slot-specific folder (e.g. /user/program2/logo/) via + // the Mobile Control direct server, rather than the processor's CWS /html/ webserver. + var imageBasePath = Global.FilePathPrefix + "logo" + Global.DirectorySeparator; - var image = path.Split('/').Last(); + // Path.GetFileName strips any directory components (including URL-decoded traversal + // sequences like "..") so only a bare filename within imageBasePath can ever be read. + var image = Path.GetFileName(Uri.UnescapeDataString(path.Split('?')[0])); var filePath = imageBasePath + image;