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;