Compare commits

..

13 Commits

Author SHA1 Message Date
Jason DeVito
116abbf962 Updates ZoomRoomJoinMap.cs with join numbers. Updated VideoCodecControllerJoinMap.cs to organize the joins in a logical pattern. Update VideoCodecBase to add the maxAnalogs to the logic calculating xsig indexes and offsets. 2021-05-11 08:34:16 -05:00
Neil Dorin
604f4ca22d #698 Adds join objects for participants audio/video mute and pin toggle 2021-05-10 12:58:07 -06:00
Neil Dorin
2d7ad8ba2a #698 Updates to add participant hand raised and pin/unpin 2021-05-07 18:07:25 -06:00
Neil Dorin
e4a3933743 Mostly coded. Needs join numbers for ZoomRoomJoinMap values 2021-05-06 16:54:41 -06:00
Neil Dorin
d6445861f5 Adds IHasZoomRoomLayouts interface. Update zStatus.Layout class to extend NotifiableObject 2021-05-05 21:55:28 -06:00
Jason T Alborough
03b076c8eb Merge pull request #696 from PepperDash/feature/dm-audio-routing-fix
Fixes DM Audio Routing
2021-04-30 15:28:08 -04:00
Andrew Welker
7c58221acc Merge branch 'development' into feature/dm-audio-routing-fix 2021-04-30 13:12:18 -06:00
Andrew Welker
14f7c27b33 Merge pull request #695 from PepperDash/bugfix/discovery-routing-endless-loop
Bugfix/discovery routing endless loop
2021-04-30 13:12:10 -06:00
Jason Alborough
9ea65883b7 Fixes DM Audio Routing 2021-04-30 14:58:39 -04:00
Alex Johnson
9d0020d999 Merge branch 'development' into bugfix/discovery-routing-endless-loop 2021-04-29 13:52:36 -04:00
Alex Johnson
fb44a3b93c Resolves looping in IRouting by adding the device to the tracking list before iterating down further. Adds debug statement to print when this condition occurs - "Skipping input <device> on <router-device>, this was already checked" 2021-04-29 13:44:47 -04:00
Andrew Welker
6b7c5c01f8 Merge pull request #693 from PepperDash/hotfix/console-response-crlf-fixes
Hotfix/console response crlf fixes
2021-04-28 21:58:44 -06:00
Andrew Welker
44c171c8f4 Merge pull request #692 from PepperDash/hotfix/console-response-crlf-fixes
Changes cr to crlf for some console command responses
2021-04-28 09:54:24 -06:00
10 changed files with 4602 additions and 3240 deletions

View File

@@ -123,25 +123,34 @@ namespace PepperDash.Essentials.Core
// No direct tie? Run back out on the inputs' attached devices...
// Only the ones that are routing devices
var attachedMidpoints = destDevInputTies.Where(t => t.SourcePort.ParentDevice is IRoutingInputsOutputs);
//Create a list for tracking already checked devices to avoid loops, if it doesn't already exist from previous iteration
if (alreadyCheckedDevices == null)
alreadyCheckedDevices = new List<IRoutingInputsOutputs>();
alreadyCheckedDevices.Add(destination as IRoutingInputsOutputs);
foreach (var inputTieToTry in attachedMidpoints)
{
Debug.Console(2, destination, "Trying to find route on {0}", inputTieToTry.SourcePort.ParentDevice.Key);
var upstreamDeviceOutputPort = inputTieToTry.SourcePort;
var upstreamRoutingDevice = upstreamDeviceOutputPort.ParentDevice as IRoutingInputsOutputs;
Debug.Console(2, destination, "Trying to find route on {0}", upstreamRoutingDevice.Key);
// Check if this previous device has already been walked
if (!(alreadyCheckedDevices != null && alreadyCheckedDevices.Contains(upstreamRoutingDevice)))
{
// haven't seen this device yet. Do it. Pass the output port to the next
// level to enable switching on success
var upstreamRoutingSuccess = upstreamRoutingDevice.GetRouteToSource(source, upstreamDeviceOutputPort,
alreadyCheckedDevices, signalType, cycle, routeTable);
if (upstreamRoutingSuccess)
{
Debug.Console(2, destination, "Upstream device route found");
goodInputPort = inputTieToTry.DestinationPort;
break; // Stop looping the inputs in this cycle
}
}
if (alreadyCheckedDevices.Contains(upstreamRoutingDevice))
{
Debug.Console(2, destination, "Skipping input {0} on {1}, this was already checked", upstreamRoutingDevice.Key, destination.Key);
continue;
}
// haven't seen this device yet. Do it. Pass the output port to the next
// level to enable switching on success
var upstreamRoutingSuccess = upstreamRoutingDevice.GetRouteToSource(source, upstreamDeviceOutputPort,
alreadyCheckedDevices, signalType, cycle, routeTable);
if (upstreamRoutingSuccess)
{
Debug.Console(2, destination, "Upstream device route found");
goodInputPort = inputTieToTry.DestinationPort;
break; // Stop looping the inputs in this cycle
}
}
}
@@ -163,10 +172,6 @@ namespace PepperDash.Essentials.Core
//Debug.Console(2, destination, "Exiting cycle {0}", cycle);
return true;
}
if(alreadyCheckedDevices == null)
alreadyCheckedDevices = new List<IRoutingInputsOutputs>();
alreadyCheckedDevices.Add(destination as IRoutingInputsOutputs);
Debug.Console(2, destination, "No route found to {0}", source.Key);
return false;

View File

@@ -176,6 +176,7 @@
<Compile Include="VideoCodec\ZoomRoom\ResponseObjects.cs" />
<Compile Include="VideoCodec\ZoomRoom\ZoomRoom.cs" />
<Compile Include="VideoCodec\ZoomRoom\ZoomRoomCamera.cs" />
<Compile Include="VideoCodec\ZoomRoom\ZoomRoomJoinMap.cs" />
<Compile Include="VideoCodec\ZoomRoom\ZoomRoomPropertiesConfig.cs" />
<None Include="Properties\ControlSystem.cfg" />
</ItemGroup>

View File

@@ -19,4 +19,32 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
void LocalLayoutToggleSingleProminent();
void MinMaxLayoutToggle();
}
/// <summary>
/// Defines the requirements for Zoom Room layout control
/// </summary>
public interface IHasZoomRoomLayouts : IHasCodecLayouts
{
event EventHandler<LayoutInfoChangedEventArgs> AvailableLayoutsChanged;
BoolFeedback LayoutViewIsOnFirstPageFeedback { get; } // TODO: #697 [ ] Consider modifying to report button visibility in func
BoolFeedback LayoutViewIsOnLastPageFeedback { get; } // TODO: #697 [ ] Consider modifying to report button visibility in func
BoolFeedback CanSwapContentWithThumbnailFeedback { get; }
BoolFeedback ContentSwappedWithThumbnailFeedback { get; }
ZoomRoom.zConfiguration.eLayoutStyle LastSelectedLayout { get; }
ZoomRoom.zConfiguration.eLayoutStyle AvailableLayouts { get; }
void GetAvailableLayouts(); // Mot sure this is necessary if we're already subscribed to zStatus Call Layout
void SetLayout(ZoomRoom.zConfiguration.eLayoutStyle layoutStyle);
void SwapContentWithThumbnail();
void LayoutTurnNextPage();
void LayoutTurnPreviousPage();
}
public class LayoutInfoChangedEventArgs : EventArgs
{
public ZoomRoom.zConfiguration.eLayoutStyle AvailableLayouts { get; set; }
}
}

View File

@@ -1,13 +1,20 @@
using System;
using System.Collections.Generic;
using PepperDash.Essentials.Core;
namespace PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces
{
/// <summary>
/// Describes a device that has call participants
/// </summary>
public interface IHasParticipants
{
CodecParticipants Participants { get; }
}
/// <summary>
/// Describes the ability to mute and unmute a participant's video in a meeting
/// </summary>
public interface IHasParticipantVideoMute:IHasParticipants
{
void MuteVideoForParticipant(int userId);
@@ -15,13 +22,29 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces
void ToggleVideoForParticipant(int userId);
}
public interface IHasParticipantAudioMute:IHasParticipantVideoMute
/// <summary>
/// Describes the ability to mute and unmute a participant's audio in a meeting
/// </summary>
public interface IHasParticipantAudioMute : IHasParticipantVideoMute
{
void MuteAudioForParticipant(int userId);
void UnmuteAudioForParticipant(int userId);
void ToggleAudioForParticipant(int userId);
}
/// <summary>
/// Describes the ability to pin and unpin a participant in a meeting
/// </summary>
public interface IHasParticipantPinUnpin : IHasParticipants
{
IntFeedback NumberOfScreensFeedback { get; }
int ScreenIndexToPinUserTo { get; }
void PinParticipant(int userId, int screenIndex);
void UnPinParticipant(int userId);
void ToggleParticipantPinState(int userId, int screenIndex);
}
public class CodecParticipants
{
private List<Participant> _currentParticipants;
@@ -31,11 +54,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces
set
{
_currentParticipants = value;
var handler = ParticipantsListHasChanged;
if(handler == null) return;
handler(this, new EventArgs());
OnParticipantsChanged();
}
}
@@ -45,15 +64,31 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces
{
_currentParticipants = new List<Participant>();
}
public void OnParticipantsChanged()
{
var handler = ParticipantsListHasChanged;
if (handler == null) return;
handler(this, new EventArgs());
}
}
/// <summary>
/// Represents a call participant
/// </summary>
public class Participant
{
public int UserId { get; set; }
public bool IsHost { get; set; }
public string Name { get; set; }
public bool CanMuteVideo { get; set; }
public bool CanUnmuteVideo { get; set; }
public bool VideoMuteFb { get; set; }
public bool AudioMuteFb { get; set; }
public bool HandIsRaisedFb { get; set; }
public bool IsPinnedFb { get; set; }
public int ScreenIndexIsPinnedToFb { get; set; }
}
}

View File

@@ -271,6 +271,14 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
public abstract void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge);
/// <summary>
/// Use this method when using a plain VideoCodecControllerJoinMap
/// </summary>
/// <param name="codec"></param>
/// <param name="trilist"></param>
/// <param name="joinStart"></param>
/// <param name="joinMapKey"></param>
/// <param name="bridge"></param>
protected void LinkVideoCodecToApi(VideoCodecBase codec, BasicTriList trilist, uint joinStart, string joinMapKey,
EiscApiAdvanced bridge)
{
@@ -288,10 +296,19 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
bridge.AddJoinMap(Key, joinMap);
}
LinkVideoCodecToApi(codec, trilist, joinMap);
}
/// <summary>
/// Use this method when you need to pass in a join map that extends VideoCodecControllerJoinMap
/// </summary>
/// <param name="codec"></param>
/// <param name="trilist"></param>
/// <param name="joinMap"></param>
protected void LinkVideoCodecToApi(VideoCodecBase codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap)
{
Debug.Console(1, this, "Linking to Trilist {0}", trilist.ID.ToString("X"));
LinkVideoCodecDtmfToApi(trilist, joinMap);
LinkVideoCodecCallControlsToApi(trilist, joinMap);
@@ -530,16 +547,22 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
trilist.SetUshort(joinMap.ParticipantCount.JoinNumber, (ushort)codec.Participants.CurrentParticipants.Count);
};
// TODO: #698 [ ] Figure out how to decode xsig data and trigger actions based on values from SIMPL
// add method here to decode the xsig info and trigger actions
//trilist.SetStringSigAction(joinMap.CurrentParticipants.JoinNumber, DecodeParticipantsXSig);
}
private string UpdateParticipantsXSig(List<Participant> currentParticipants)
{
const int maxParticipants = 50;
const int maxDigitals = 5;
const int maxDigitals = 7;
const int maxStrings = 1;
const int offset = maxDigitals + maxStrings;
var digitalIndex = maxStrings * maxParticipants; //15
const int maxAnalogs = 1;
const int offset = maxDigitals + maxStrings + maxAnalogs;
var digitalIndex = maxStrings + maxAnalogs * maxParticipants; //15
var stringIndex = 0;
var analogIndex = 0;
var meetingIndex = 0;
var tokenArray = new XSigToken[maxParticipants * offset];
@@ -554,34 +577,88 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
tokenArray[digitalIndex + 2] = new XSigDigitalToken(digitalIndex + 3, participant.CanMuteVideo);
tokenArray[digitalIndex + 3] = new XSigDigitalToken(digitalIndex + 4, participant.CanUnmuteVideo);
tokenArray[digitalIndex + 4] = new XSigDigitalToken(digitalIndex + 5, participant.IsHost);
tokenArray[digitalIndex + 5] = new XSigDigitalToken(digitalIndex + 6, participant.HandIsRaisedFb);
tokenArray[digitalIndex + 6] = new XSigDigitalToken(digitalIndex + 7, participant.IsPinnedFb);
//serials
tokenArray[stringIndex] = new XSigSerialToken(stringIndex + 1, participant.Name);
//analogs
tokenArray[analogIndex] = new XSigAnalogToken(analogIndex + 1, (ushort)participant.ScreenIndexIsPinnedToFb);
digitalIndex += maxDigitals;
meetingIndex += offset;
stringIndex += maxStrings;
analogIndex += maxAnalogs;
}
while (meetingIndex < maxParticipants * offset)
{
//digitals
tokenArray[digitalIndex] = new XSigDigitalToken(digitalIndex + 1, false);
tokenArray[digitalIndex + 1] = new XSigDigitalToken(digitalIndex + 2, false);
tokenArray[digitalIndex + 2] = new XSigDigitalToken(digitalIndex + 3, false);
tokenArray[digitalIndex + 3] = new XSigDigitalToken(digitalIndex + 4, false);
tokenArray[digitalIndex + 4] = new XSigDigitalToken(digitalIndex + 5, false);
tokenArray[digitalIndex + 5] = new XSigDigitalToken(digitalIndex + 6, false);
tokenArray[digitalIndex + 6] = new XSigDigitalToken(digitalIndex + 7, false);
//serials
tokenArray[stringIndex] = new XSigSerialToken(stringIndex + 1, String.Empty);
//analogs
tokenArray[analogIndex] = new XSigAnalogToken(analogIndex + 1, 0);
digitalIndex += maxDigitals;
meetingIndex += offset;
stringIndex += maxStrings;
analogIndex += maxAnalogs;
}
return GetXSigString(tokenArray);
}
// TODO: #698 [ ] Figure out how to decode xsig data and trigger actions based on values from SIMPL
private void DecodeParticipantsXSig(string xsigData)
{
if (string.IsNullOrEmpty(xsigData)) return;
var bytes = Encoding.GetEncoding(XSigEncoding).GetBytes(xsigData);
using (var xsigStream = new XSigTokenStreamReader(new MemoryStream(bytes)))
{
XSigToken token = null;
while ((token = xsigStream.ReadXSigToken()) != null)
{
switch (token.TokenType)
{
case XSigTokenType.Digital:
{
var data = token as XSigDigitalToken;
if (data == null) return;
Debug.Console(1, this, "xSig Data TokenType: {0} | Index: {1} | Value: {2}",
data.TokenType, data.Index, data.Value.ToString());
break;
}
case XSigTokenType.Analog:
{
var data = token as XSigAnalogToken;
if (data == null) return;
Debug.Console(1, this, "xSig Data TokenType: {0} | Index: {1} | Value: {2}",
data.TokenType, data.Index, data.Value);
break;
}
case XSigTokenType.Serial:
{
var data = token as XSigSerialToken;
if (data == null) return;
Debug.Console(1, this, "xSig Data TokenType: {0} | Index: {1} | Value: {2}", data.TokenType, data.Index,
data.Value);
break;
}
}
}
}
}
private void LinkVideoCodecContentSharingToApi(BasicTriList trilist, VideoCodecControllerJoinMap joinMap)
{
SharingContentIsOnFeedback.LinkInputSig(trilist.BooleanInput[joinMap.SourceShareStart.JoinNumber]);
@@ -847,7 +924,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
Debug.Console(1, this, "Call Direction: {0}", args.CallItem.Direction);
Debug.Console(1, this, "Call is incoming: {0}", args.CallItem.Direction == eCodecCallDirection.Incoming);
trilist.SetBool(joinMap.IncomingCall.JoinNumber, args.CallItem.Direction == eCodecCallDirection.Incoming && args.CallItem.Status == eCodecCallStatus.Ringing);
trilist.SetBool(joinMap.IncomingCall.JoinNumber, args.CallItem.Direction == eCodecCallDirection.Incoming && args.CallItem.Status == eCodecCallStatus.Ringing);
if (args.CallItem.Direction == eCodecCallDirection.Incoming)
{

View File

@@ -480,12 +480,28 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
public string wifiName { get; set; }
}
public class NumberOfScreens
public class NumberOfScreens : NotifiableObject
{
private int _numOfScreens;
[JsonProperty("NumberOfCECScreens")]
public int NumOfCECScreens { get; set; }
[JsonProperty("NumberOfScreens")]
public int NumOfScreens { get; set; }
public int NumOfScreens
{
get
{
return _numOfScreens;
}
set
{
if (value != _numOfScreens)
{
_numOfScreens = value;
NotifyPropertyChanged("NumberOfScreens");
}
}
}
}
/// <summary>
@@ -551,18 +567,136 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
}
public class Layout
public class Layout : NotifiableObject
{
// backer variables
private bool _can_Switch_Speaker_View;
private bool _can_Switch_Wall_View;
private bool _can_Switch_Share_On_All_Screens;
private bool _is_In_First_Page;
private bool _is_In_Last_Page;
private string _video_type;
public bool can_Adjust_Floating_Video { get; set; }
public bool can_Switch_Floating_Share_Content { get; set; }
public bool can_Switch_Share_On_All_Screens { get; set; }
public bool can_Switch_Speaker_View { get; set; }
public bool can_Switch_Wall_View { get; set; }
public bool is_In_First_Page { get; set; }
public bool is_In_Last_Page { get; set; }
/// <summary>
/// [on/off] // Set to On if it is possible to invoke zConfiguration Call Layout Style: ShareAll, to switch to the ShareAll mode, where the content sharing is shown full screen on all monitors.
/// </summary>
[JsonProperty("can_Switch_Share_On_All_Screens")]
public bool can_Switch_Share_On_All_Screens
{
get
{
return _can_Switch_Share_On_All_Screens;
}
set
{
if (value != _can_Switch_Share_On_All_Screens)
{
_can_Switch_Share_On_All_Screens = value;
NotifyPropertyChanged("can_Switch_Share_On_All_Screens");
}
}
}
/// <summary>
/// [on/off] // Set to On if it is possible to switch to Speaker view by invoking zConfiguration Call Layout Style: Speaker. The active speaker is shown full screen, and other video streams, like self-view, are shown in thumbnails.
/// </summary>
[JsonProperty("can_Switch_Speaker_View")]
public bool can_Switch_Speaker_View
{
get
{
return _can_Switch_Speaker_View;
}
set
{
if (value != _can_Switch_Speaker_View)
{
_can_Switch_Speaker_View = value;
NotifyPropertyChanged("can_Switch_Speaker_View");
}
}
}
/// <summary>
/// [on/off] On if it is possible to invoke zConfiguration Call Layout Style: Gallery, to switch to the Gallery mode, showing video participants in tiled windows: The Zoom Room shows up to a 5x5 array of tiled windows per page.
/// </summary>
[JsonProperty("can_Switch_Wall_View")]
public bool can_Switch_Wall_View
{
get
{
return _can_Switch_Wall_View;
}
set
{
if (value != _can_Switch_Wall_View)
{
_can_Switch_Wall_View = value;
NotifyPropertyChanged("can_Switch_Wall_View");
}
}
}
[JsonProperty("is_In_First_Page")]
public bool is_In_First_Page
{
get
{
return _is_In_First_Page;
}
set
{
if (value != _is_In_First_Page)
{
_is_In_First_Page = value;
NotifyPropertyChanged("is_In_First_Page");
}
}
}
[JsonProperty("is_In_Last_Page")]
public bool is_In_Last_Page
{
get
{
return _is_In_Last_Page;
}
set
{
if (value != _is_In_Last_Page)
{
_is_In_Last_Page = value;
NotifyPropertyChanged("is_In_Last_Page");
}
}
}
public bool is_supported { get; set; }
public int video_Count_In_Current_Page { get; set; }
public string video_type { get; set; }
/// <summary>
/// [Gallery | Strip] Indicates which mode applies: Strip or Gallery.
/// </summary>
[JsonProperty("video_type")]
public string video_type
{
get
{
return _video_type;
}
set
{
if (value != _video_type)
{
_video_type = value;
NotifyPropertyChanged("video_type");
}
}
}
}
public class CallRecordInfo
@@ -685,6 +819,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
public class PinStatusOfScreenNotification
{
[JsonProperty("can_be_pinned")]
public bool CanBePinned { get; set; }
[JsonProperty("can_pin_share")]
@@ -835,12 +971,14 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
}
}
[Flags]
public enum eLayoutStyle
{
Gallery,
Speaker,
Strip,
ShareAll
None = 0,
Gallery = 1,
Speaker = 2,
Strip = 4,
ShareAll = 8,
}
public enum eLayoutSize
@@ -865,20 +1003,64 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
DownLeft
}
public class Layout:NotifiableObject
public class Layout : NotifiableObject
{
public bool ShareThumb { get; set; }
public eLayoutStyle Style { get; set; }
public eLayoutSize Size { get; set; }
private bool _shareThumb;
private eLayoutStyle _style;
private eLayoutSize _size;
private eLayoutPosition _position;
public eLayoutPosition Position {
public bool ShareThumb
{
get { return _shareThumb; }
set
{
if (value != _shareThumb)
{
_shareThumb = value;
NotifyPropertyChanged("ShareThumb");
}
}
}
public eLayoutStyle Style
{
get { return _style; }
set
{
if (value != _style)
{
_style = value;
NotifyPropertyChanged("Style");
}
}
}
public eLayoutSize Size
{
get { return _size; }
set
{
if (value != _size)
{
_size = value;
NotifyPropertyChanged("Size");
}
}
}
public eLayoutPosition Position
{
get { return _position; }
set
{
_position = value;
NotifyPropertyChanged("Position");
} }
if (value != _position)
{
_position = value;
NotifyPropertyChanged("Position");
}
}
}
}
public class Lock
@@ -1137,8 +1319,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
{
[JsonProperty("is_raise_hand")]
public bool IsRaiseHand { get; set; }
[JsonProperty("optimize_vis_validideo_sharing")]
public string IsValid { get; set; }
[JsonProperty("is_valid")]
public bool IsValid { get; set; }
[JsonProperty("time_stamp")]
public string TimeStamp { get; set; }
}
@@ -1213,12 +1395,14 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
p =>
new Participant
{
UserId = p.UserId,
Name = p.UserName,
IsHost = p.IsHost,
CanMuteVideo = p.IsVideoCanMuteByHost,
CanUnmuteVideo = p.IsVideoCanUnmuteByHost,
AudioMuteFb = p.AudioStatusState == "AUDIO_MUTED",
VideoMuteFb = p.VideoStatusIsSending
VideoMuteFb = p.VideoStatusIsSending,
HandIsRaisedFb = p.HandStatus.IsValid && p.HandStatus.IsRaiseHand,
}).ToList();
}
}

View File

@@ -23,7 +23,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
public class ZoomRoom : VideoCodecBase, IHasCodecSelfView, IHasDirectoryHistoryStack, ICommunicationMonitor,
IRouting,
IHasScheduleAwareness, IHasCodecCameras, IHasParticipants, IHasCameraOff, IHasCameraMute, IHasCameraAutoMode,
IHasFarEndContentStatus, IHasSelfviewPosition, IHasPhoneDialing
IHasFarEndContentStatus, IHasSelfviewPosition, IHasPhoneDialing, IHasZoomRoomLayouts, IHasParticipantPinUnpin, IHasParticipantAudioMute
{
private const long MeetingRefreshTimer = 60000;
private const uint DefaultMeetingDurationMin = 30;
@@ -121,6 +121,16 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
PhoneOffHookFeedback = new BoolFeedback(PhoneOffHookFeedbackFunc);
CallerIdNameFeedback = new StringFeedback(CallerIdNameFeedbackFunc);
CallerIdNumberFeedback = new StringFeedback(CallerIdNumberFeedbackFunc);
LocalLayoutFeedback = new StringFeedback(LocalLayoutFeedbackFunc);
LayoutViewIsOnFirstPageFeedback = new BoolFeedback(LayoutViewIsOnFirstPageFeedbackFunc);
LayoutViewIsOnLastPageFeedback = new BoolFeedback(LayoutViewIsOnLastPageFeedbackFunc);
CanSwapContentWithThumbnailFeedback = new BoolFeedback(CanSwapContentWithThumbnailFeedbackFunc);
ContentSwappedWithThumbnailFeedback = new BoolFeedback(ContentSwappedWithThumbnailFeedbackFunc);
NumberOfScreensFeedback = new IntFeedback(NumberOfScreensFeedbackFunc);
}
public CommunicationGather PortGather { get; private set; }
@@ -216,11 +226,6 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
}
}
protected Func<string> LocalLayoutFeedbackFunc
{
get { return () => ""; }
}
protected Func<bool> LocalLayoutIsProminentFeedbackFunc
{
get { return () => false; }
@@ -484,11 +489,28 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
Configuration.Call.Layout.PropertyChanged += (o, a) =>
{
if (a.PropertyName != "Position") return;
switch (a.PropertyName)
{
case "Position":
{
ComputeSelfviewPipStatus();
ComputeSelfviewPipStatus();
SelfviewPipPositionFeedback.FireUpdate();
SelfviewPipPositionFeedback.FireUpdate();
break;
}
case "ShareThumb":
{
ContentSwappedWithThumbnailFeedback.FireUpdate();
break;
}
case "Style":
{
LocalLayoutFeedback.FireUpdate();
break;
}
}
};
Status.Call.Sharing.PropertyChanged += (o, a) =>
@@ -542,6 +564,48 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
break;
}
};
Status.Layout.PropertyChanged += (o, a) =>
{
switch (a.PropertyName)
{
case "can_Switch_Speaker_View":
case "can_Switch_Wall_View":
case "can_Switch_Share_On_All_Screens":
{
ComputeAvailableLayouts();
break;
}
case "is_In_First_Page":
{
LayoutViewIsOnFirstPageFeedback.FireUpdate();
break;
}
case "is_In_Last_Page":
{
LayoutViewIsOnLastPageFeedback.FireUpdate();
break;
}
//case "video_type":
// {
// It appears as though the actual value we want to watch is Configuration.Call.Layout.Style
// LocalLayoutFeedback.FireUpdate();
// break;
// }
}
};
Status.NumberOfScreens.PropertyChanged += (o, a) =>
{
switch (a.PropertyName)
{
case "NumberOfScreens":
{
NumberOfScreensFeedback.FireUpdate();
break;
}
}
};
}
private void SetUpDirectory()
@@ -1222,6 +1286,38 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
JsonConvert.PopulateObject(responseObj.ToString(), Status.PhoneCall);
break;
}
case "pinstatusofscreennotification":
{
var status = responseObj.ToObject<zEvent.PinStatusOfScreenNotification>();
var participant = Participants.CurrentParticipants.FirstOrDefault(p => p.UserId.Equals(status.PinnedUserId));
if (participant != null)
{
participant.IsPinnedFb = true;
participant.ScreenIndexIsPinnedToFb = status.ScreenIndex;
}
else
{
participant = Participants.CurrentParticipants.FirstOrDefault(p => p.ScreenIndexIsPinnedToFb.Equals(status.ScreenIndex));
if (participant == null)
{
Debug.Console(2, this, "no matching participant found by pinned_user_id: {0} or screen_index: {1}", status.PinnedUserId, status.ScreenIndex);
return;
}
else
{
participant.IsPinnedFb = false;
participant.ScreenIndexIsPinnedToFb = -1;
}
}
// fire the event as we've modified the participants list
Participants.OnParticipantsChanged();
break;
}
default:
{
break;
@@ -1600,7 +1696,81 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge)
{
LinkVideoCodecToApi(this, trilist, joinStart, joinMapKey, bridge);
var joinMap = new ZoomRoomJoinMap(joinStart);
var customJoins = JoinMapHelper.TryGetJoinMapAdvancedForDevice(joinMapKey);
if (customJoins != null)
{
joinMap.SetCustomJoinData(customJoins);
}
if (bridge != null)
{
bridge.AddJoinMap(Key, joinMap);
}
LinkVideoCodecToApi(this, trilist, joinMap);
LinkZoomRoomToApi(trilist, joinMap);
}
/// <summary>
/// Links all the specific Zoom functionality to the API bridge
/// </summary>
/// <param name="trilist"></param>
/// <param name="joinMap"></param>
public void LinkZoomRoomToApi(BasicTriList trilist, ZoomRoomJoinMap joinMap)
{
var layoutsCodec = this as IHasZoomRoomLayouts;
if (layoutsCodec != null)
{
layoutsCodec.AvailableLayoutsChanged += (o, a) =>
{
trilist.SetBool(joinMap.LayoutGalleryIsAvailable.JoinNumber, a.AvailableLayouts
== (a.AvailableLayouts & zConfiguration.eLayoutStyle.Gallery));
trilist.SetBool(joinMap.LayoutSpeakerIsAvailable.JoinNumber, a.AvailableLayouts
== (a.AvailableLayouts & zConfiguration.eLayoutStyle.Speaker));
trilist.SetBool(joinMap.LayoutStripIsAvailable.JoinNumber, a.AvailableLayouts
== (a.AvailableLayouts & zConfiguration.eLayoutStyle.Strip));
trilist.SetBool(joinMap.LayoutShareAllIsAvailable.JoinNumber, a.AvailableLayouts
== (a.AvailableLayouts & zConfiguration.eLayoutStyle.ShareAll));
};
layoutsCodec.CanSwapContentWithThumbnailFeedback.LinkInputSig(trilist.BooleanInput[joinMap.CanSwapContentWithThumbnail.JoinNumber]);
trilist.SetSigFalseAction(joinMap.SwapContentWithThumbnail.JoinNumber, () => layoutsCodec.SwapContentWithThumbnail());
layoutsCodec.ContentSwappedWithThumbnailFeedback.LinkInputSig(trilist.BooleanInput[joinMap.SwapContentWithThumbnail.JoinNumber]);
layoutsCodec.LayoutViewIsOnFirstPageFeedback.LinkInputSig(trilist.BooleanInput[joinMap.LayoutIsOnFirstPage.JoinNumber]);
layoutsCodec.LayoutViewIsOnLastPageFeedback.LinkInputSig(trilist.BooleanInput[joinMap.LayoutIsOnLastPage.JoinNumber]);
trilist.SetSigFalseAction(joinMap.LayoutTurnToNextPage.JoinNumber, () => layoutsCodec.LayoutTurnNextPage());
trilist.SetSigFalseAction(joinMap.LayoutTurnToPreviousPage.JoinNumber, () => layoutsCodec.LayoutTurnPreviousPage());
trilist.SetSigFalseAction(joinMap.GetAvailableLayouts.JoinNumber, () => layoutsCodec.GetAvailableLayouts());
trilist.SetStringSigAction(joinMap.GetSetCurrentLayout.JoinNumber, (s) =>
{
try
{
var style = (zConfiguration.eLayoutStyle)Enum.Parse(typeof(zConfiguration.eLayoutStyle), s, true);
SetLayout(style);
}
catch (Exception e)
{
Debug.Console(1, this, "Unable to parse '{0}' to zConfiguration.eLayoutStyle: {1}", s, e);
}
});
layoutsCodec.LocalLayoutFeedback.LinkInputSig(trilist.StringInput[joinMap.GetSetCurrentLayout.JoinNumber]);
}
var pinCodec = this as IHasParticipantPinUnpin;
if (pinCodec != null)
{
pinCodec.NumberOfScreensFeedback.LinkInputSig(trilist.UShortInput[joinMap.NumberOfScreens.JoinNumber]);
// Set the value of the local property to be used when pinning a participant
trilist.SetUShortSigAction(joinMap.ScreenIndexToPinUserTo.JoinNumber, (u) => ScreenIndexToPinUserTo = u);
}
}
public override void ExecuteSwitch(object selector)
@@ -1770,6 +1940,114 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
#endregion
#region IHasParticipantAudioMute Members
public void MuteAudioForParticipant(int userId)
{
SendText(string.Format("zCommand Call MuteParticipant Mute: on Id: {0}", userId));
}
public void UnmuteAudioForParticipant(int userId)
{
SendText(string.Format("zCommand Call MuteParticipant Mute: off Id: {0}", userId));
}
public void ToggleAudioForParticipant(int userId)
{
var user = Participants.CurrentParticipants.FirstOrDefault(p => p.UserId.Equals(userId));
if (user == null)
{
Debug.Console(2, this, "Unable to find user with id: {0}", userId);
return;
}
if (user.AudioMuteFb)
{
UnmuteAudioForParticipant(userId);
}
else
{
MuteAudioForParticipant(userId);
}
}
#endregion
#region IHasParticipantVideoMute Members
public void MuteVideoForParticipant(int userId)
{
SendText(string.Format("zCommand Call MuteParticipantVideo Mute: on Id: {0}", userId));
}
public void UnmuteVideoForParticipant(int userId)
{
SendText(string.Format("zCommand Call MuteParticipantVideo Mute: off Id: {0}", userId));
}
public void ToggleVideoForParticipant(int userId)
{
var user = Participants.CurrentParticipants.FirstOrDefault(p => p.UserId.Equals(userId));
if (user == null)
{
Debug.Console(2, this, "Unable to find user with id: {0}", userId);
return;
}
if (user.VideoMuteFb)
{
UnmuteVideoForParticipant(userId);
}
else
{
MuteVideoForParticipant(userId);
}
}
#endregion
#region IHasParticipantPinUnpin Members
private Func<int> NumberOfScreensFeedbackFunc { get { return () => Status.NumberOfScreens.NumOfScreens; } }
public IntFeedback NumberOfScreensFeedback { get; private set; }
public int ScreenIndexToPinUserTo { get; private set; }
public void PinParticipant(int userId, int screenIndex)
{
SendText(string.Format("zCommand Call Pin Id: {0} Enable: on Screen: {1}", userId, screenIndex));
}
public void UnPinParticipant(int userId)
{
SendText(string.Format("zCommand Call Pin Id: {0} Enable: off", userId));
}
public void ToggleParticipantPinState(int userId, int screenIndex)
{
var user = Participants.CurrentParticipants.FirstOrDefault(p => p.UserId.Equals(userId));
if(user == null)
{
Debug.Console(2, this, "Unable to find user with id: {0}", userId);
return;
}
if (user.IsPinnedFb)
{
UnPinParticipant(userId);
}
else
{
PinParticipant(userId, screenIndex);
}
}
#endregion
#region Implementation of IHasCameraOff
public BoolFeedback CameraIsOffFeedback { get; private set; }
@@ -1899,6 +2177,125 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
}
#endregion
#region IHasZoomRoomLayouts Members
public event EventHandler<LayoutInfoChangedEventArgs> AvailableLayoutsChanged;
private Func<bool> LayoutViewIsOnFirstPageFeedbackFunc {get {return () => Status.Layout.is_In_First_Page; } }
private Func<bool> LayoutViewIsOnLastPageFeedbackFunc { get { return () => Status.Layout.is_In_Last_Page; } }
private Func<bool> CanSwapContentWithThumbnailFeedbackFunc { get { return () => Status.Layout.can_Switch_Floating_Share_Content; } }
private Func<bool> ContentSwappedWithThumbnailFeedbackFunc { get { return () => Configuration.Call.Layout.ShareThumb; } }
public BoolFeedback LayoutViewIsOnFirstPageFeedback { get; private set; }
public BoolFeedback LayoutViewIsOnLastPageFeedback { get; private set; }
public BoolFeedback CanSwapContentWithThumbnailFeedback { get; private set; }
public BoolFeedback ContentSwappedWithThumbnailFeedback { get; private set; }
public zConfiguration.eLayoutStyle LastSelectedLayout { get; private set; }
public zConfiguration.eLayoutStyle AvailableLayouts { get; private set; }
/// <summary>
/// Reads individual properties to determine if which layouts are avalailable
/// </summary>
private void ComputeAvailableLayouts()
{
zConfiguration.eLayoutStyle availableLayouts = zConfiguration.eLayoutStyle.None;
// TODO: #697 [ ] Compute the avaialble layouts and set the value of AvailableLayouts
// Will need to test and confirm that this logic evaluates correctly
if (Status.Layout.can_Switch_Wall_View)
{
availableLayouts |= zConfiguration.eLayoutStyle.Gallery;
}
if (Status.Layout.can_Switch_Speaker_View)
{
availableLayouts |= zConfiguration.eLayoutStyle.Speaker;
}
if (Status.Layout.can_Switch_Share_On_All_Screens)
{
availableLayouts |= zConfiguration.eLayoutStyle.ShareAll;
}
// There is no property that directly reports if strip mode is valid, but API stipulates
// that strip mode is available if the number of screens is 1
if (Status.NumberOfScreens.NumOfScreens == 1)
{
availableLayouts |= zConfiguration.eLayoutStyle.Strip;
}
var handler = AvailableLayoutsChanged;
if (handler != null)
{
handler(this, new LayoutInfoChangedEventArgs() { AvailableLayouts = availableLayouts });
}
AvailableLayouts = availableLayouts;
}
public void GetAvailableLayouts()
{
SendText("zStatus Call Layout");
}
public void SetLayout(zConfiguration.eLayoutStyle layoutStyle)
{
LastSelectedLayout = layoutStyle;
SendText(String.Format("zConfiguration Call Layout Style: {0}", layoutStyle.ToString()));
}
public void SwapContentWithThumbnail()
{
if (CanSwapContentWithThumbnailFeedback.BoolValue)
{
var oppositeValue = ContentSwappedWithThumbnailFeedback.BoolValue ? "on" : "off"; // Get the value based on the opposite of the current state
// TODO: #697 [ ] Need to verify the ternary above and make sure that the correct on/off value is being send based on the true/false value of the feedback
// to toggle the state
SendText(String.Format("zConfiguration Call Layout ShareThumb: {0}", oppositeValue));
}
}
public void LayoutTurnNextPage()
{
SendText("zCommand Call Layout TurnPage Forward: On");
}
public void LayoutTurnPreviousPage()
{
SendText("zCommand Call Layout TurnPage Forward: Off");
}
#endregion
#region IHasCodecLayouts Members
private Func<string> LocalLayoutFeedbackFunc { get { return () => Configuration.Call.Layout.Style.ToString(); } }
public StringFeedback LocalLayoutFeedback { get; private set; }
public void LocalLayoutToggle()
{
throw new NotImplementedException();
}
public void LocalLayoutToggleSingleProminent()
{
throw new NotImplementedException();
}
public void MinMaxLayoutToggle()
{
throw new NotImplementedException();
}
#endregion
}
/// <summary>

View File

@@ -0,0 +1,273 @@
using System;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Bridges.JoinMaps;
namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
{
public class ZoomRoomJoinMap : VideoCodecControllerJoinMap
{
// TODO: #697 [X] Set join numbers
#region Digital
[JoinName("CanSwapContentWithThumbnail")]
public JoinDataComplete CanSwapContentWithThumbnail = new JoinDataComplete(
new JoinData
{
JoinNumber = 206,
JoinSpan = 1
},
new JoinMetadata
{
Description = "FB Indicates if content can be swapped with thumbnail",
JoinCapabilities = eJoinCapabilities.ToSIMPL,
JoinType = eJoinType.Digital
});
[JoinName("SwapContentWithThumbnail")]
public JoinDataComplete SwapContentWithThumbnail = new JoinDataComplete(
new JoinData
{
JoinNumber = 206,
JoinSpan = 1
},
new JoinMetadata
{
Description = "Pulse to swap content with thumbnail. FB reports current state",
JoinCapabilities = eJoinCapabilities.ToFromSIMPL,
JoinType = eJoinType.Digital
});
[JoinName("GetAvailableLayouts")]
public JoinDataComplete GetAvailableLayouts = new JoinDataComplete(
new JoinData
{
JoinNumber = 215,
JoinSpan = 1
},
new JoinMetadata
{
Description = "Gets the available layouts. Will update the LayoutXXXXXIsAvailbale signals.",
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
[JoinName("LayoutIsOnFirstPage")]
public JoinDataComplete LayoutIsOnFirstPage = new JoinDataComplete(
new JoinData
{
JoinNumber = 216,
JoinSpan = 1
},
new JoinMetadata
{
Description = "Indicates if layout is on first page",
JoinCapabilities = eJoinCapabilities.ToSIMPL,
JoinType = eJoinType.Digital
});
[JoinName("LayoutIsOnLastPage")]
public JoinDataComplete LayoutIsOnLastPage = new JoinDataComplete(
new JoinData
{
JoinNumber = 217,
JoinSpan = 1
},
new JoinMetadata
{
Description = "Indicates if layout is on first page",
JoinCapabilities = eJoinCapabilities.ToSIMPL,
JoinType = eJoinType.Digital
});
[JoinName("LayoutTurnToNextPage")]
public JoinDataComplete LayoutTurnToNextPage = new JoinDataComplete(
new JoinData
{
JoinNumber = 216,
JoinSpan = 1
},
new JoinMetadata
{
Description = "Turns layout view to next page",
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
[JoinName("LayoutTurnToPreviousPage")]
public JoinDataComplete LayoutTurnToPreviousPage = new JoinDataComplete(
new JoinData
{
JoinNumber = 217,
JoinSpan = 1
},
new JoinMetadata
{
Description = "Turns layout view to previous page",
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
[JoinName("LayoutGalleryIsAvailable")]
public JoinDataComplete LayoutGalleryIsAvailable = new JoinDataComplete(
new JoinData
{
JoinNumber = 221,
JoinSpan = 1
},
new JoinMetadata
{
Description = "FB Indicates if layout 'Gallery' is available",
JoinCapabilities = eJoinCapabilities.ToSIMPL,
JoinType = eJoinType.Digital
});
[JoinName("LayoutSpeakerIsAvailable")]
public JoinDataComplete LayoutSpeakerIsAvailable = new JoinDataComplete(
new JoinData
{
JoinNumber = 222,
JoinSpan = 1
},
new JoinMetadata
{
Description = "FB Indicates if layout 'Speaker' is available",
JoinCapabilities = eJoinCapabilities.ToSIMPL,
JoinType = eJoinType.Digital
});
[JoinName("LayoutStripIsAvailable")]
public JoinDataComplete LayoutStripIsAvailable = new JoinDataComplete(
new JoinData
{
JoinNumber = 223,
JoinSpan = 1
},
new JoinMetadata
{
Description = "FB Indicates if layout 'Strip' is available",
JoinCapabilities = eJoinCapabilities.ToSIMPL,
JoinType = eJoinType.Digital
});
[JoinName("LayoutShareAllIsAvailable")]
public JoinDataComplete LayoutShareAllIsAvailable = new JoinDataComplete(
new JoinData
{
JoinNumber = 224,
JoinSpan = 1
},
new JoinMetadata
{
Description = "FB Indicates if layout 'ShareAll' is available",
JoinCapabilities = eJoinCapabilities.ToSIMPL,
JoinType = eJoinType.Digital
});
[JoinName("ParticipantAudioMuteToggleStart")]
public JoinDataComplete ParticipantAudioMuteToggleStart = new JoinDataComplete(
new JoinData
{
JoinNumber = 500,
JoinSpan = 100
},
new JoinMetadata
{
Description = "Toggles the participant's audio mute status",
JoinCapabilities = eJoinCapabilities.ToSIMPL,
JoinType = eJoinType.Digital
});
[JoinName("ParticipantVideoMuteToggleStart")]
public JoinDataComplete ParticipantVideoMuteToggleStart = new JoinDataComplete(
new JoinData
{
JoinNumber = 800,
JoinSpan = 100
},
new JoinMetadata
{
Description = "Toggles the participant's video mute status",
JoinCapabilities = eJoinCapabilities.ToSIMPL,
JoinType = eJoinType.Digital
});
[JoinName("ParticipantPinToggleStart")]
public JoinDataComplete ParticipantPinToggleStart = new JoinDataComplete(
new JoinData
{
JoinNumber = 1100,
JoinSpan = 100
},
new JoinMetadata
{
Description = "Toggles the participant's pin status",
JoinCapabilities = eJoinCapabilities.ToSIMPL,
JoinType = eJoinType.Digital
});
#endregion
#region Analog
[JoinName("NumberOfScreens")]
public JoinDataComplete NumberOfScreens = new JoinDataComplete(
new JoinData
{
JoinNumber = 11,
JoinSpan = 1
},
new JoinMetadata
{
Description = "Reports the number of screens connected",
JoinCapabilities = eJoinCapabilities.ToSIMPL,
JoinType = eJoinType.Analog
});
[JoinName("ScreenIndexToPinUserTo")]
public JoinDataComplete ScreenIndexToPinUserTo = new JoinDataComplete(
new JoinData
{
JoinNumber = 11,
JoinSpan = 1
},
new JoinMetadata
{
Description = "Specifies the screen index a participant should be pinned to",
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Analog
});
#endregion
#region Serials
[JoinName("GetSetCurrentLayout")]
public JoinDataComplete GetSetCurrentLayout = new JoinDataComplete(
new JoinData
{
JoinNumber = 215,
JoinSpan = 1
},
new JoinMetadata
{
Description = "Sets and reports the current layout. Use the LayoutXXXXIsAvailable signals to determine valid layouts",
JoinCapabilities = eJoinCapabilities.ToFromSIMPL,
JoinType = eJoinType.Serial
});
#endregion
public ZoomRoomJoinMap(uint joinStart)
: base(joinStart, typeof(ZoomRoomJoinMap))
{
}
public ZoomRoomJoinMap(uint joinStart, Type type)
: base(joinStart, type)
{
}
}
}