This commit is contained in:
youngq 2024-07-16 09:33:16 +08:00
parent 6d25427586
commit cc309355c6
12 changed files with 397 additions and 53 deletions

View File

@ -31,12 +31,12 @@ namespace WGShare.API.Controllers
/// <summary>
/// 检查用户名
/// </summary>
/// <param name="userName"></param>
/// <param name="account"></param>
/// <returns></returns>
[HttpGet("check-user"), AllowAnonymous]
public async Task<bool> CheckUser([FromQuery] string userName)
public async Task<bool> CheckUser([FromQuery] string account)
{
return await _sqlSugar.Queryable<User>().AnyAsync(x => x.IsDelete == false && x.UserName == userName);
return await _sqlSugar.Queryable<User>().AnyAsync(x => x.IsDelete == false && x.Account == account);
}
/// <summary>
@ -47,7 +47,7 @@ namespace WGShare.API.Controllers
public async Task<IActionResult> Login([FromBody] UserLoginDTO loginDTO)
{
var user = await _sqlSugar.Queryable<User>()
.FirstAsync(x => x.UserName == loginDTO.Account && x.IsDelete == false && x.Pwd == loginDTO.Pwd);
.FirstAsync(x => x.Account == loginDTO.Account && x.IsDelete == false && x.Pwd == loginDTO.Pwd);
if (user == null)
{
throw Oops.Oh("用户名或密码不正确!");
@ -84,7 +84,9 @@ namespace WGShare.API.Controllers
roleId = user.RoleId,
userName = user.UserName,
tenantName = tenant.TenantName,
expire= _configuration["Jwt:Expires"].ToInt32()
expire = _configuration["Jwt:Expires"].ToInt32(),
account = user.Account,
uid = user.Id
});
}

View File

@ -1,4 +1,5 @@
using AgoraIO.Media;
using Hangfire.MemoryStorage.Database;
using Mapster;
using Masuit.Tools;
using Microsoft.AspNetCore.Authorization;
@ -6,6 +7,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using Microsoft.IdentityModel.Tokens;
using SqlSugar;
using SqlSugar.Extensions;
using WGShare.API.Controllers.Basic;
using WGShare.API.Helpers;
using WGShare.API.Hubs;
@ -32,18 +34,21 @@ namespace WGShare.API.Controllers.Frontend
private readonly OssHelper _ossHelper;
private readonly AgoraHelper _agoraHelper;
private readonly IHubContext<SessionManageHub, IMessageClient> _hubContext;
private readonly ILogger<RoomController> _logger;
public RoomController(ISqlSugarClient sqlSugar,
IConfiguration configuration,
OssHelper ossHelper,
AgoraHelper agoraHelper,
IHubContext<SessionManageHub, IMessageClient> hubContext)
IHubContext<SessionManageHub, IMessageClient> hubContext,
ILogger<RoomController> logger)
{
this._sqlSugar = sqlSugar;
this._configuration = configuration;
this._ossHelper = ossHelper;
this._agoraHelper = agoraHelper;
this._hubContext = hubContext;
this._logger = logger;
}
///// <summary>
@ -94,18 +99,15 @@ namespace WGShare.API.Controllers.Frontend
[HttpGet("user")]
public async Task<List<UserOutputDTO>> GetUsers([FromQuery] string roomNum)
{
var data = await _agoraHelper.GetChannelUserList(roomNum);
if (data == null)
var channelUsers = RedisHelper.Instance.HVals<ChannelUserInfo>(RedisKeyConstant.SessionManage.GetChannelUserKey(TenantId, roomNum));
if (channelUsers.IsNullOrEmpty())
{
throw Oops.Oh("请求失败");
}
if (!data.channel_exist)
{
throw Oops.Oh("频道不存在");
return new List<UserOutputDTO>();
}
var uids = channelUsers.Select(x => x.UID);
var users = await _sqlSugar.Queryable<User>()
.Where(x => data.users.Contains(x.Account))
.Where(x => uids.Contains(x.Id))
.ToListAsync();
var managerIds = await _sqlSugar.Queryable<RoomManager>()
@ -114,12 +116,57 @@ namespace WGShare.API.Controllers.Frontend
.Select((rm, r) => rm.UserId)
.ToListAsync();
var result = users.Adapt<List<UserOutputDTO>>();
result.ForEach(x => x.IsManager = managerIds.Contains(x.Id));
result.ForEach(x =>
{
x.IsManager = managerIds.Contains(x.Id);
var info = channelUsers.FirstOrDefault(q => q.UID == x.Id);
if (info != null)
{
x.EnableMicr = info.EnableMicr;
x.EnableCamera = info.EnableCamera;
}
});
return result;
#region
//var data = await _agoraHelper.GetChannelUserList(roomNum);
//if (data == null)
//{
// throw Oops.Oh("请求失败");
//}
//if (!data.channel_exist)
//{
// throw Oops.Oh("频道不存在");
//}
//if (data.broadcasters.IsNullOrEmpty())
//{
// return new List<UserOutputDTO>();
//}
//var accounts = data.broadcasters.ConvertAll(x => x.ToString());
//var users = await _sqlSugar.Queryable<User>()
// .Where(x => accounts.Contains(x.Account))
// .ToListAsync();
//var managerIds = await _sqlSugar.Queryable<RoomManager>()
// .InnerJoin<Room>((rm, r) => r.Id == rm.RoomId)
// .Where((rm, r) => r.RoomNum == roomNum)
// .Select((rm, r) => rm.UserId)
// .ToListAsync();
//var result = users.Adapt<List<UserOutputDTO>>();
//result.ForEach(x => x.IsManager = managerIds.Contains(x.Id));
//return result;
#endregion
}
/// <summary>
@ -189,10 +236,89 @@ namespace WGShare.API.Controllers.Frontend
public async Task KickOut([FromQuery] string roomNum, [FromQuery] string kickUid)
{
var connectId = RedisHelper.Instance.HGet(RedisKeyConstant.SessionManage.GetOnlineUserKey(TenantId), kickUid);
await _hubContext.Clients.Client(connectId).ForceExitRoom(roomNum);
if (!string.IsNullOrWhiteSpace(connectId))
{
await _hubContext.Clients.Client(connectId).ForceExitRoom(roomNum);
}
}
/// <summary>
/// 开闭麦
/// </summary>
/// <returns></returns>
[HttpGet("oper-micr")]
public async Task Mute([FromQuery] string roomNum, [FromQuery] bool enableMicr, [FromQuery] string? uid, [FromQuery] bool? isAll = false)
{
if (isAll.HasValue && isAll.Value)
{
// 全员静音
var allUsers = RedisHelper.Instance.HGetAll<ChannelUserInfo>(RedisKeyConstant.SessionManage.GetChannelUserKey(TenantId, roomNum));
if (!allUsers.Any())
{
return;
}
allUsers.ForEach(x => x.Value.EnableMicr = enableMicr);
RedisHelper.Instance.HMSet(RedisKeyConstant.SessionManage.GetChannelUserKey(TenantId, roomNum), allUsers);
await _hubContext.Clients.Group(roomNum).OperMicr(enableMicr);
await _hubContext.Clients.Group(roomNum).RefreshUserList();
return;
}
var userInfo = RedisHelper.Instance.HGet<ChannelUserInfo>(RedisKeyConstant.SessionManage.GetChannelUserKey(TenantId, roomNum), uid);
if (userInfo == null || string.IsNullOrWhiteSpace(userInfo.ConnectId))
{
_logger.LogError($"闭麦操作,用户不存在频道rediskey:{RedisKeyConstant.SessionManage.GetChannelUserKey(TenantId, roomNum)} uid" + uid);
return;
}
userInfo.EnableMicr = enableMicr;
RedisHelper.Instance.HSet(RedisKeyConstant.SessionManage.GetChannelUserKey(TenantId, roomNum), uid, userInfo);
await _hubContext.Clients.Clients(userInfo.ConnectId).OperMicr(enableMicr);
await _hubContext.Clients.Group(roomNum).RefreshUserList();
}
/// <summary>
/// 开关闭摄像头
/// </summary>
/// <returns></returns>
[HttpGet("oper-camera")]
public async Task CloseCamera([FromQuery] string roomNum, [FromQuery] bool enableCamera, [FromQuery] string? uid, [FromQuery] bool? isAll = false)
{
if (isAll.HasValue && isAll.Value)
{
var allUsers = RedisHelper.Instance.HGetAll<ChannelUserInfo>(RedisKeyConstant.SessionManage.GetChannelUserKey(TenantId, roomNum));
if (!allUsers.Any())
{
return;
}
allUsers.ForEach(x => x.Value.EnableCamera = enableCamera);
RedisHelper.Instance.HMSet(RedisKeyConstant.SessionManage.GetChannelUserKey(TenantId, roomNum), allUsers);
// 全员开关闭摄像头
await _hubContext.Clients.Group(roomNum).OperCamera(enableCamera);
await _hubContext.Clients.Group(roomNum).RefreshUserList();
return;
}
var userInfo = RedisHelper.Instance.HGet<ChannelUserInfo>(RedisKeyConstant.SessionManage.GetChannelUserKey(TenantId, roomNum), uid);
if (userInfo == null || string.IsNullOrWhiteSpace(userInfo.ConnectId))
{
_logger.LogError($"关闭摄像头操作,用户不存在频道rediskey:{RedisKeyConstant.SessionManage.GetChannelUserKey(TenantId, roomNum)} uid" + uid);
return;
}
userInfo.EnableCamera = enableCamera;
RedisHelper.Instance.HSet(RedisKeyConstant.SessionManage.GetChannelUserKey(TenantId, roomNum), uid, userInfo);
await _hubContext.Clients.Clients(userInfo.ConnectId).OperCamera(enableCamera);
await _hubContext.Clients.Group(roomNum).RefreshUserList();
}
#region
/// <summary>
/// 分享上传文件

View File

@ -1,4 +1,6 @@
using FreeRedis;
using Masuit.Tools;
using Newtonsoft.Json;
namespace WGShare.API.Helpers
{
@ -43,5 +45,74 @@ namespace WGShare.API.Helpers
/// <param name="maxTimeoutSeconds">最大秒数</param>
/// <returns></returns>
public static int RandomExpired(int minTimeoutSeconds, int maxTimeoutSeconds) => rnd.Value.Next(minTimeoutSeconds, maxTimeoutSeconds);
public static List<T> HVals<T>(string key) where T : class
{
var valueStrings = Instance.HVals(key);
if (valueStrings.IsNullOrEmpty())
{
return null;
}
return valueStrings.ToList().ConvertAll(x => JsonConvert.DeserializeObject<T>(x));
}
public static T HGet<T>(string key, string field) where T : class
{
var valueString = Instance.HGet(key, field);
if (valueString.IsNullOrEmpty())
{
return null;
}
return JsonConvert.DeserializeObject<T>(valueString);
}
public static List<T> HMGet<T>(string key, params string[] fields) where T : class
{
var valueStrings = Instance.HMGet(key, fields);
if (valueStrings.IsNullOrEmpty())
{
return null;
}
return valueStrings.ToList().ConvertAll(x => JsonConvert.DeserializeObject<T>(x));
}
public static Dictionary<string, T> HGetAll<T>(string key) where T : class
{
var dic = Instance.HGetAll(key);
if (dic == null || dic.Count == 0)
{
return null;
}
Dictionary<string, T> result = new Dictionary<string, T>();
foreach (var kv in dic)
{
if (string.IsNullOrWhiteSpace(kv.Key) || string.IsNullOrWhiteSpace(kv.Value))
{
continue;
}
result.Add(kv.Key, JsonConvert.DeserializeObject<T>(kv.Value));
}
return result;
}
public static long HSet<T>(string key, string fields, T value) where T : class
{
return Instance.HSet(key, fields, value.ToJsonString());
}
public static void HMSet<T>(string key, Dictionary<string, T> keyValues) where T : class
{
var dic = new Dictionary<string, string>();
foreach (var kv in keyValues)
{
dic.Add(kv.Key,kv.Value.ToJsonString());
}
Instance.HMSet(key, dic);
}
}
}

View File

@ -11,7 +11,7 @@
/// <param name="userName"></param>
/// <param name="message"></param>
/// <returns></returns>
Task ReceiveMessage(string userName, string message);
Task ReceiveMessage(string uid, string userName, string message);
/// <summary>
@ -29,5 +29,32 @@
/// <param name="roomNum">会议号</param>
/// <returns></returns>
Task ForceExitRoom(string roomNum);
/// <summary>
/// 用户开闭麦
/// </summary>
/// <param name="enableMicr"></param>
/// <returns></returns>
Task OperMicr(bool enableMicr);
/// <summary>
/// 用户开启关闭摄像头
/// </summary>
/// <param name="enableCamera"></param>
/// <returns></returns>
Task OperCamera(bool enableCamera);
/// <summary>
/// 刷新用户列表
/// </summary>
/// <returns></returns>
Task RefreshUserList();
///// <summary>
///// 客户端操作
///// </summary>
///// <param name="type"></param>
///// <returns></returns>
//Task Operation(int type);
}
}

View File

@ -4,6 +4,7 @@ using Microsoft.AspNetCore.SignalR;
using System.Text;
using WGShare.API.Helpers;
using WGShare.Domain.Constant;
using WGShare.Domain.Entities;
namespace WGShare.API.Hubs
{
@ -61,15 +62,23 @@ namespace WGShare.API.Hubs
/// 加入频道
/// </summary>
/// <param name="roomNum"></param>
/// <param name="enableMicr">是否关闭麦克风,默认是</param>
/// <param name="enableCamera">是否关闭摄像头,默认是</param>
[HubMethodName("joinChannel")]
public async Task JoinChannel(string roomNum)
public async Task JoinChannel(string roomNum, bool enableMicr = true, bool enableCamera = true)
{
var tenant = Context.User?.Claims.FirstOrDefault(x => x.Type == "tenant")?.Value;
var uid = Context.User?.Claims.FirstOrDefault(x => x.Type == "uid")?.Value;
await Console.Out.WriteLineAsync("加入频道 会议号:" + roomNum);
await Console.Out.WriteLineAsync("加入频道 uid" + uid);
await Console.Out.WriteLineAsync("加入频道 tenant" + tenant);
using (var pipe = RedisHelper.Instance.StartPipe())
{
pipe.HSet(RedisKeyConstant.SessionManage.GetChannelUserKey(tenant, roomNum), uid, Context.ConnectionId);
var userInfo = new ChannelUserInfo(uid, Context.ConnectionId, enableMicr, enableCamera);
pipe.HSet(RedisKeyConstant.SessionManage.GetChannelUserKey(tenant, roomNum), uid, userInfo.ToJsonString());
pipe.HIncrBy(RedisKeyConstant.SessionManage.GetChannelUserCountKey(tenant), roomNum, 1);
pipe.HSet(RedisKeyConstant.SessionManage.GetUserJoinChannelKey(uid), roomNum, 1);
pipe.EndPipe();
@ -88,6 +97,11 @@ namespace WGShare.API.Hubs
var tenant = Context.User?.Claims.FirstOrDefault(x => x.Type == "tenant")?.Value;
var uid = Context.User?.Claims.FirstOrDefault(x => x.Type == "uid")?.Value;
await Console.Out.WriteLineAsync("离开频道 会议号:" + roomNum);
await Console.Out.WriteLineAsync("离开频道 uid" + uid);
await Console.Out.WriteLineAsync("离开频道 tenant" + tenant);
using (var pipe = RedisHelper.Instance.StartPipe())
{
pipe.HDel(RedisKeyConstant.SessionManage.GetChannelUserKey(tenant, roomNum), uid);
@ -103,38 +117,28 @@ namespace WGShare.API.Hubs
/// 发送频道消息
/// </summary>
/// <param name="rooNum"></param>
/// <param name="userName"></param>
/// <param name="msg"></param>
[HubMethodName("sendChannelMsg")]
public async Task SenMessage(string rooNum, string msg)
{
var uname = Context.User?.Claims.FirstOrDefault(x => x.Type == "uname")?.Value;
var uid = Context.User?.Claims.FirstOrDefault(x => x.Type == "uid")?.Value;
await Clients.GroupExcept(rooNum, Context.ConnectionId).ReceiveMessage(uname, msg);
await Console.Out.WriteLineAsync("发送消息 uname" + uname);
await Console.Out.WriteLineAsync("发送消息 roomNum" + rooNum);
await Console.Out.WriteLineAsync("发送消息 msg" + msg);
await Clients.GroupExcept(rooNum, Context.ConnectionId).ReceiveMessage(uid, uname, msg);
}
///// <summary>
///// 邀请呼叫
///// 发送客户端指令
///// </summary>
///// <param name="inviteeUid">被邀请人id</param>
///// <param name="roomName">会议名称</param>
///// <param name="roomNum">会议号</param>
///// <returns></returns>
//[HubMethodName("call")]
//public async Task<(bool isSuccess, string msg)> CallUser(string inviteeUid, string roomName, string roomNum)
//[HubMethodName("sendOper")]
//public async Task SendOperation(string roomNum, int type)
//{
// var tenant = Context.User?.Claims.FirstOrDefault(x => x.Type == "tenant")?.Value;
// var uname = Context.User?.Claims.FirstOrDefault(x => x.Type == "uname")?.Value;
// var connectId = RedisHelper.Instance.HGet(RedisKeyConstant.SessionManage.GetOnlineUserKey(tenant), inviteeUid);
// if (string.IsNullOrWhiteSpace(connectId))
// {
// return (false, "被邀请人不在线");
// }
// await Clients.Client(connectId).Invitation(roomNum, roomName, uname);
// return (true, "邀请成功");
// await Clients.Group(roomNum).Operation(type);
//}
}

View File

@ -1,5 +1,6 @@
using Hangfire;
using Hangfire.MemoryStorage;
using Masuit.Tools;
using Microsoft.AspNetCore.SignalR;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json;
@ -23,7 +24,12 @@ namespace WGShare.API
// Add services to the container.
RedisHelper.Initialization(new FreeRedis.RedisClient(configuration["Redis:master"]));
RedisHelper.Initialization(new FreeRedis.RedisClient(configuration["Redis:master"])
{
Serialize = (x) => x.ToJsonString(),
Deserialize = (x, t) => JsonConvert.DeserializeObject(x, t),
//DeserializeRaw = (x, t) => JsonConvert.DeserializeObject(x, t),
});
builder.Services.AddControllers(options =>
{
// 全局异常捕获,无需在代码中 写 try catch

View File

@ -8,7 +8,7 @@
<summary>
检查用户名
</summary>
<param name="userName"></param>
<param name="account"></param>
<returns></returns>
</member>
<member name="M:WGShare.API.Controllers.AuthController.Login(WGShare.Domain.DTOs.Login.UserLoginDTO)">
@ -131,6 +131,18 @@
</summary>
<returns></returns>
</member>
<member name="M:WGShare.API.Controllers.Frontend.RoomController.Mute(System.String,System.Boolean,System.String,System.Nullable{System.Boolean})">
<summary>
开闭麦
</summary>
<returns></returns>
</member>
<member name="M:WGShare.API.Controllers.Frontend.RoomController.CloseCamera(System.String,System.Boolean,System.String,System.Nullable{System.Boolean})">
<summary>
开关闭摄像头
</summary>
<returns></returns>
</member>
<member name="M:WGShare.API.Controllers.Frontend.RoomController.AddFile(WGShare.Domain.DTOs.File.ShareFileInputDTO)">
<summary>
分享上传文件
@ -276,7 +288,7 @@
客户端消息
</summary>
</member>
<member name="M:WGShare.API.Hubs.IMessageClient.ReceiveMessage(System.String,System.String)">
<member name="M:WGShare.API.Hubs.IMessageClient.ReceiveMessage(System.String,System.String,System.String)">
<summary>
接受频道消息
</summary>
@ -300,11 +312,33 @@
<param name="roomNum">会议号</param>
<returns></returns>
</member>
<member name="M:WGShare.API.Hubs.SessionManageHub.JoinChannel(System.String)">
<member name="M:WGShare.API.Hubs.IMessageClient.OperMicr(System.Boolean)">
<summary>
用户开闭麦
</summary>
<param name="enableMicr"></param>
<returns></returns>
</member>
<member name="M:WGShare.API.Hubs.IMessageClient.OperCamera(System.Boolean)">
<summary>
用户开启关闭摄像头
</summary>
<param name="enableCamera"></param>
<returns></returns>
</member>
<member name="M:WGShare.API.Hubs.IMessageClient.RefreshUserList">
<summary>
刷新用户列表
</summary>
<returns></returns>
</member>
<member name="M:WGShare.API.Hubs.SessionManageHub.JoinChannel(System.String,System.Boolean,System.Boolean)">
<summary>
加入频道
</summary>
<param name="roomNum"></param>
<param name="enableMicr">是否关闭麦克风,默认是</param>
<param name="enableCamera">是否关闭摄像头,默认是</param>
</member>
<member name="M:WGShare.API.Hubs.SessionManageHub.LevelChannel(System.String)">
<summary>
@ -317,7 +351,6 @@
发送频道消息
</summary>
<param name="rooNum"></param>
<param name="userName"></param>
<param name="msg"></param>
</member>
<member name="M:WGShare.API.ServiceConfigs.AuthenticationServiceExtensions.AddAuth(Microsoft.Extensions.DependencyInjection.IServiceCollection,System.String,System.String,System.String)">

View File

@ -10,6 +10,6 @@
"usercenter": "Database=usercenter;Server=192.168.2.9;Port=3306;Uid=root;Pwd=qwe123!@#;AllowZeroDateTime=True;ConvertZeroDateTime=True;"
},
"Redis": {
"master": "192.168.2.7:6379,password=qwe123!@#,defaultDatabase=13,name=wgshare,prefix=wgshare"
"master": "192.168.2.7:6379,password=qwe123!@#,defaultDatabase=13,name=wgshare,prefix=wgshare:"
}
}

View File

@ -18,7 +18,7 @@
"usercenter": "Database=usercenter;Server=192.168.2.9;Port=3306;Uid=root;Pwd=qwe123!@#;AllowZeroDateTime=True;ConvertZeroDateTime=True;"
},
"Redis": {
"master": "172.29.33.83:16379,password=poiuyt)(*&^%,defaultDatabase=13,prefix=wgshare"
"master": "172.29.33.83:16379,password=poiuyt)(*&^%,defaultDatabase=13,prefix=wgshare:"
},
"Agora": {
"appId": "dcfc466a6ecb4a1f972630065dfb1e75",

View File

@ -23,6 +23,25 @@ namespace WGShare.Domain.AgoraApiResult
/// </summary>
public int total { get; set; }
public List<string> users { get; set; }
/// <summary>
/// 频道内所有主播的用户 ID。该字段仅在直播场景mode 的值为 2下返回。
/// </summary>
public List<int> broadcasters { get; set; }
/// <summary>
/// 频道内所有用户的用户 ID。该字段仅在通信场景mode 的值为 1下返回。
/// </summary>
public List<int> users { get; set; }
/// <summary>
/// 频道内观众的用户 ID。最多包含当前频道内前 10,000 名观众的用户 ID。该字段仅在直播场景mode 的值为 2且未填 hosts_only 参数时返回。
/// </summary>
public List<int> audience { get; set; }
/// <summary>
/// 频道内的观众总人数。该字段仅在直播场景mode 的值为 2下且未填 hosts_only 参数时返回。
/// </summary>
public int audience_total { get; set; }
}
}

View File

@ -26,5 +26,14 @@ namespace WGShare.Domain.DTOs.User
/// </summary>
public bool IsManager { get; set; }
/// <summary>
/// 是否关闭麦克风
/// </summary>
public bool EnableMicr { get; set; }
/// <summary>
/// 是否关闭摄像头
/// </summary>
public bool EnableCamera { get; set; }
}
}

View File

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WGShare.Domain.Entities
{
/// <summary>
/// 用户在频道中的状态
/// </summary>
public class ChannelUserInfo
{
public ChannelUserInfo()
{
}
/// <summary>
///
/// </summary>
/// <param name="uid"></param>
/// <param name="connectId"></param>
/// <param name="isMute">是否关闭麦克风,默认关</param>
/// <param name="enableCamera">是否关闭摄像头,默认关</param>
public ChannelUserInfo(string uid, string connectId, bool enableMicr, bool enableCamera)
{
this.UID = uid;
this.ConnectId = connectId;
this.EnableMicr = enableMicr;
this.EnableCamera = enableCamera;
}
public string UID { get; set; }
public string ConnectId { get; set; }
/// <summary>
/// 是否关闭麦克风
/// </summary>
public bool EnableMicr { get; set; }
/// <summary>
/// 是否关闭摄像头
/// </summary>
public bool EnableCamera { get; set; }
}
}