This commit is contained in:
parent
ee2a75ba8d
commit
4e7b6a2f92
|
|
@ -0,0 +1,28 @@
|
|||
using Agora.Rtc;
|
||||
using Prism.Events;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Demo.Common.Events
|
||||
{
|
||||
public class AudioMuteStateChangedEvent : PubSubEvent<AudioMuteInfo>
|
||||
{
|
||||
}
|
||||
|
||||
public class AudioMuteInfo
|
||||
{
|
||||
public AudioMuteInfo(long uid, bool isMute, string channel)
|
||||
{
|
||||
Channel = channel;
|
||||
UId = uid;
|
||||
IsMute = isMute;
|
||||
}
|
||||
public string Channel { get; set; }
|
||||
public long UId { get; set; }
|
||||
public bool IsMute { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
using Prism.Events;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Demo.Common.Events
|
||||
{
|
||||
public class VideoMuteStateChangedEvent:PubSubEvent<VideoMuteInfo>
|
||||
{
|
||||
}
|
||||
|
||||
public class VideoMuteInfo
|
||||
{
|
||||
public VideoMuteInfo(long uid, bool isMute, string channel)
|
||||
{
|
||||
Channel = channel;
|
||||
UId = uid;
|
||||
IsMute = isMute;
|
||||
}
|
||||
public string Channel { get; set; }
|
||||
public long UId { get; set; }
|
||||
public bool IsMute { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
using Demo.Common.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Demo.Common.Extensions
|
||||
{
|
||||
public static class ObservableCollectionExtension
|
||||
{
|
||||
public static void InitUserList<T>(this ObservableCollection<T> list, int count) where T : User
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
list.Add((T)User.GetInit());
|
||||
}
|
||||
}
|
||||
|
||||
public static int AddUser<T>(this ObservableCollection<T> list, T user) where T : User
|
||||
{
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
if (list[i].Id == 0)
|
||||
{
|
||||
list[i] = user;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static int ClearUserById<T>(this ObservableCollection<T> list, long Id) where T : User
|
||||
{
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
if (list[i].Id == Id)
|
||||
{
|
||||
list[i] = (T)User.GetInit();
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static void ClearAllUser<T>(this ObservableCollection<T> list) where T : User
|
||||
{
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
list[i] = (T)User.GetInit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,12 +9,16 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace Demo.Common.Helpers
|
||||
{
|
||||
public class AgoraHelper : IRtcEngineEventHandler
|
||||
public class AgoraHelper
|
||||
{
|
||||
public static uint _localUId;
|
||||
public static IRtcEngine _RtcEngineInstance;
|
||||
public static int Init(string appId, uint localUid = 0)
|
||||
{
|
||||
if (_RtcEngineInstance != null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (null == _RtcEngineInstance)
|
||||
{
|
||||
_RtcEngineInstance = RtcEngine.CreateAgoraRtcEngine();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,110 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
|
||||
namespace Demo.Common.Helpers
|
||||
{
|
||||
public class AvatatHelper
|
||||
{
|
||||
private static Image cameraCloseImage = null;
|
||||
public static Image CameraCloseImage
|
||||
{
|
||||
get
|
||||
{
|
||||
if (cameraCloseImage != null)
|
||||
{
|
||||
return cameraCloseImage;
|
||||
}
|
||||
using (var fileStream = new FileStream($@"Assets/camera_close.png", FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
using var _imageStream = new MemoryStream();
|
||||
fileStream.CopyTo(_imageStream);
|
||||
cameraCloseImage = Image.FromStream(_imageStream);
|
||||
}
|
||||
|
||||
return cameraCloseImage;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取姓名对应的圆形图片
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="width"></param>
|
||||
/// <param name="height"></param>
|
||||
/// <returns></returns>
|
||||
public static Bitmap GetNickNameImage(string name, int width = 132, int height = 132)
|
||||
{
|
||||
string color = "#2B6EC0";
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
string firstName = GetFirstTwoChars(name);
|
||||
Bitmap img = new Bitmap(width, height);
|
||||
Graphics g = Graphics.FromImage(img);
|
||||
|
||||
// 启用抗锯齿
|
||||
g.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
|
||||
// 创建圆形路径
|
||||
GraphicsPath path = new GraphicsPath();
|
||||
path.AddEllipse(0, 0, width, height);
|
||||
|
||||
// 设置裁剪区域为圆形
|
||||
g.SetClip(path);
|
||||
|
||||
// 填充背景色
|
||||
System.Drawing.Brush brush = new SolidBrush(ColorTranslator.FromHtml(color));
|
||||
g.FillEllipse(brush, 0, 0, width, height);
|
||||
|
||||
// 填充文字
|
||||
Font font = new Font("微软雅黑", 50);
|
||||
SizeF firstSize = g.MeasureString(firstName, font);
|
||||
g.DrawString(firstName, font, System.Drawing.Brushes.White,
|
||||
new PointF((img.Width - firstSize.Width) / 2, (img.Height - firstSize.Height) / 2));
|
||||
|
||||
g.Dispose();
|
||||
path.Dispose();
|
||||
brush.Dispose();
|
||||
font.Dispose();
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
|
||||
public static string GetFirstTwoChars(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
return string.Empty;
|
||||
input = input.Trim();
|
||||
return input[..Math.Min(2, input.Length)];
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 保存图片到磁盘
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="targetFile"></param>
|
||||
/// <param name="width"></param>
|
||||
/// <param name="height"></param>
|
||||
/// <returns></returns>
|
||||
public static Bitmap SaveNameImage(string name, string targetFile, int width = 132, int height = 132)
|
||||
{
|
||||
Bitmap img = GetNickNameImage(name, width, height);
|
||||
img.Save(targetFile + ".png", ImageFormat.Png);
|
||||
img.Dispose();
|
||||
return img;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,8 @@ using Prism.Ioc;
|
|||
using Prism.Events;
|
||||
using Demo.Common.Events;
|
||||
using Masuit.Tools;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Channels;
|
||||
|
||||
namespace Demo.Common.Helpers
|
||||
{
|
||||
|
|
@ -19,10 +21,16 @@ namespace Demo.Common.Helpers
|
|||
aggregator = ContainerLocator.Container.Resolve<IEventAggregator>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户加入频道回调
|
||||
/// </summary>
|
||||
/// <param name="connection"></param>
|
||||
/// <param name="remoteUid"></param>
|
||||
/// <param name="elapsed"></param>
|
||||
public override void OnUserJoined(RtcConnection connection, uint remoteUid, int elapsed)
|
||||
{
|
||||
//base.OnUserJoined(connection, remoteUid, elapsed);
|
||||
Console.WriteLine($@"新用户加入: channelId:{connection.channelId} localuid: {connection.localUid} remoteuid:{remoteUid} elapsed: {elapsed}");
|
||||
//base.OnUserJoined(connection, remoteUid, elapsed);
|
||||
Console.WriteLine($@" {DateTime.Now.ToString("HH:mm:ss")}新用户加入: channelId:{connection.channelId} localuid: {connection.localUid} remoteuid:{remoteUid} elapsed: {elapsed}");
|
||||
|
||||
|
||||
//var aggregator = ContainerLocator.Container.Resolve<IEventAggregator>();
|
||||
|
|
@ -36,6 +44,12 @@ namespace Demo.Common.Helpers
|
|||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 用户离开频道回调
|
||||
/// </summary>
|
||||
/// <param name="connection"></param>
|
||||
/// <param name="remoteUid"></param>
|
||||
/// <param name="reason"></param>
|
||||
public override void OnUserOffline(RtcConnection connection, uint remoteUid, USER_OFFLINE_REASON_TYPE reason)
|
||||
{
|
||||
//var aggregator = ContainerLocator.Container.Resolve<IEventAggregator>();
|
||||
|
|
@ -48,6 +62,13 @@ namespace Demo.Common.Helpers
|
|||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 音频音量提示回调
|
||||
/// </summary>
|
||||
/// <param name="connection"></param>
|
||||
/// <param name="speakers"></param>
|
||||
/// <param name="speakerNumber"></param>
|
||||
/// <param name="totalVolume"></param>
|
||||
public override void OnAudioVolumeIndication(RtcConnection connection, AudioVolumeInfo[] speakers, uint speakerNumber, int totalVolume)
|
||||
{
|
||||
if (speakers.Length <= 0)
|
||||
|
|
@ -57,12 +78,134 @@ namespace Demo.Common.Helpers
|
|||
speakers.ForEach(sp =>
|
||||
{
|
||||
sp.uid = sp.uid == 0 ? AgoraHelper._localUId : sp.uid;
|
||||
|
||||
|
||||
aggregator.GetEvent<AudioVolumeIndicationEvent>().Publish(sp);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 本地用户音频发布状态改变回调。
|
||||
/// </summary>
|
||||
/// <param name="channel"></param>
|
||||
/// <param name="oldState"></param>
|
||||
/// <param name="newState"></param>
|
||||
/// <param name="elapseSinceLastState"></param>
|
||||
public override void OnAudioPublishStateChanged(string channel, STREAM_PUBLISH_STATE oldState, STREAM_PUBLISH_STATE newState, int elapseSinceLastState)
|
||||
{
|
||||
switch (newState)
|
||||
{
|
||||
case STREAM_PUBLISH_STATE.PUB_STATE_IDLE:
|
||||
break;
|
||||
case STREAM_PUBLISH_STATE.PUB_STATE_NO_PUBLISHED:
|
||||
aggregator.GetEvent<AudioMuteStateChangedEvent>().Publish(new AudioMuteInfo(AgoraHelper._localUId, true, channel));
|
||||
break;
|
||||
case STREAM_PUBLISH_STATE.PUB_STATE_PUBLISHING:
|
||||
break;
|
||||
case STREAM_PUBLISH_STATE.PUB_STATE_PUBLISHED:
|
||||
aggregator.GetEvent<AudioMuteStateChangedEvent>().Publish(new AudioMuteInfo(AgoraHelper._localUId, false, channel));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
///// <summary>
|
||||
///// 远端用户音频订阅状态发生改变回调。
|
||||
///// </summary>
|
||||
///// <param name="channel"></param>
|
||||
///// <param name="uid"></param>
|
||||
///// <param name="oldState"></param>
|
||||
///// <param name="newState"></param>
|
||||
///// <param name="elapseSinceLastState"></param>
|
||||
//public override void OnAudioSubscribeStateChanged(string channel, uint uid, STREAM_SUBSCRIBE_STATE oldState, STREAM_SUBSCRIBE_STATE newState, int elapseSinceLastState)
|
||||
//{
|
||||
// //Console.WriteLine($@"OnAudioSubscribeStateChanged====================== channel {channel} uid {uid} {newState}");
|
||||
|
||||
// switch (newState)
|
||||
// {
|
||||
// case STREAM_SUBSCRIBE_STATE.SUB_STATE_IDLE:
|
||||
// break;
|
||||
// case STREAM_SUBSCRIBE_STATE.SUB_STATE_NO_SUBSCRIBED:
|
||||
// aggregator.GetEvent<AudioMuteStateChangedEvent>().Publish(new AudioMuteInfo(uid, true, channel));
|
||||
// break;
|
||||
// case STREAM_SUBSCRIBE_STATE.SUB_STATE_SUBSCRIBING:
|
||||
// break;
|
||||
// case STREAM_SUBSCRIBE_STATE.SUB_STATE_SUBSCRIBED:
|
||||
// aggregator.GetEvent<AudioMuteStateChangedEvent>().Publish(new AudioMuteInfo(uid, false, channel));
|
||||
// break;
|
||||
// }
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// 远端用户音频订阅状态发生改变回调。
|
||||
/// </summary>
|
||||
/// <param name="connection"></param>
|
||||
/// <param name="remoteUid"></param>
|
||||
/// <param name="muted"></param>
|
||||
public override void OnUserMuteAudio(RtcConnection connection, uint remoteUid, bool muted)
|
||||
{
|
||||
aggregator.GetEvent<AudioMuteStateChangedEvent>().Publish(new AudioMuteInfo(remoteUid, muted, connection.channelId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 远端用户视频订阅状态发生改变回调。
|
||||
/// </summary>
|
||||
/// <param name="connection"></param>
|
||||
/// <param name="remoteUid"></param>
|
||||
/// <param name="muted"></param>
|
||||
public override void OnUserMuteVideo(RtcConnection connection, uint remoteUid, bool muted)
|
||||
{
|
||||
aggregator.GetEvent<VideoMuteStateChangedEvent>().Publish(new VideoMuteInfo(remoteUid, muted, connection.channelId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 本地用户视频发布状态改变回调。
|
||||
/// </summary>
|
||||
/// <param name="source"></param>
|
||||
/// <param name="channel"></param>
|
||||
/// <param name="oldState"></param>
|
||||
/// <param name="newState"></param>
|
||||
/// <param name="elapseSinceLastState"></param>
|
||||
public override void OnVideoPublishStateChanged(VIDEO_SOURCE_TYPE source, string channel, STREAM_PUBLISH_STATE oldState, STREAM_PUBLISH_STATE newState, int elapseSinceLastState)
|
||||
{
|
||||
switch (newState)
|
||||
{
|
||||
case STREAM_PUBLISH_STATE.PUB_STATE_IDLE:
|
||||
break;
|
||||
case STREAM_PUBLISH_STATE.PUB_STATE_NO_PUBLISHED:
|
||||
aggregator.GetEvent<VideoMuteStateChangedEvent>().Publish(new VideoMuteInfo(AgoraHelper._localUId, true, channel));
|
||||
break;
|
||||
case STREAM_PUBLISH_STATE.PUB_STATE_PUBLISHING:
|
||||
break;
|
||||
case STREAM_PUBLISH_STATE.PUB_STATE_PUBLISHED:
|
||||
aggregator.GetEvent<VideoMuteStateChangedEvent>().Publish(new VideoMuteInfo(AgoraHelper._localUId, false, channel));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 远端用户视频订阅状态发生改变回调。
|
||||
/// </summary>
|
||||
/// <param name="channel"></param>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="oldState"></param>
|
||||
/// <param name="newState"></param>
|
||||
/// <param name="elapseSinceLastState"></param>
|
||||
public override void OnVideoSubscribeStateChanged(string channel, uint uid, STREAM_SUBSCRIBE_STATE oldState, STREAM_SUBSCRIBE_STATE newState, int elapseSinceLastState)
|
||||
{
|
||||
switch (newState)
|
||||
{
|
||||
case STREAM_SUBSCRIBE_STATE.SUB_STATE_IDLE:
|
||||
break;
|
||||
case STREAM_SUBSCRIBE_STATE.SUB_STATE_NO_SUBSCRIBED:
|
||||
aggregator.GetEvent<VideoMuteStateChangedEvent>().Publish(new VideoMuteInfo(uid, true, channel));
|
||||
break;
|
||||
case STREAM_SUBSCRIBE_STATE.SUB_STATE_SUBSCRIBING:
|
||||
break;
|
||||
case STREAM_SUBSCRIBE_STATE.SUB_STATE_SUBSCRIBED:
|
||||
aggregator.GetEvent<VideoMuteStateChangedEvent>().Publish(new VideoMuteInfo(uid, false, channel));
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
using Prism.Mvvm;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Demo.Common.Models
|
||||
{
|
||||
public class AgoraDeviceInfo : BindableBase
|
||||
{
|
||||
public AgoraDeviceInfo(string deviceId, string deviceName, bool isSelected = false)
|
||||
{
|
||||
DeviceId = deviceId;
|
||||
DeviceName = deviceName;
|
||||
IsSelected = isSelected;
|
||||
}
|
||||
public string DeviceId { get; set; }
|
||||
|
||||
public string DeviceName { get; set; }
|
||||
|
||||
|
||||
private bool isSelected;
|
||||
public bool IsSelected
|
||||
{
|
||||
get { return isSelected; }
|
||||
set { SetProperty(ref isSelected, value); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
using Prism.Mvvm;
|
||||
using Demo.Common.Helpers;
|
||||
using Prism.Mvvm;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace Demo.Common.Models
|
||||
{
|
||||
|
|
@ -21,8 +24,17 @@ namespace Demo.Common.Models
|
|||
public string UserName
|
||||
{
|
||||
get { return userName; }
|
||||
set { SetProperty(ref userName, value); }
|
||||
set
|
||||
{
|
||||
SetProperty(ref userName, value);
|
||||
}
|
||||
}
|
||||
|
||||
///// <summary>
|
||||
///// 用户头像
|
||||
///// </summary>
|
||||
//public Bitmap Avatar => AvatatHelper.GetNickNameImage(userName);
|
||||
|
||||
private bool isManager;
|
||||
public bool IsManager
|
||||
{
|
||||
|
|
@ -38,11 +50,73 @@ namespace Demo.Common.Models
|
|||
set { SetProperty(ref isLocal, value); }
|
||||
}
|
||||
|
||||
//private bool isMain;
|
||||
//public bool IsMain
|
||||
private bool isMuteAudio;
|
||||
public bool IsMuteAudio
|
||||
{
|
||||
get { return isMuteAudio; }
|
||||
set
|
||||
{
|
||||
SetProperty(ref isMuteAudio, value);
|
||||
RaisePropertyChanged(nameof(MicoSlashLineVisibility));
|
||||
RaisePropertyChanged(nameof(MicStatusText));
|
||||
if (value)
|
||||
{
|
||||
// 静音,音量置为0
|
||||
MicoVolume = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool isMuteVideo;
|
||||
public bool IsMuteVideo
|
||||
{
|
||||
get { return isMuteVideo; }
|
||||
set
|
||||
{
|
||||
SetProperty(ref isMuteVideo, value);
|
||||
RaisePropertyChanged(nameof(VideoSlashLineVisibility));
|
||||
RaisePropertyChanged(nameof(VideoStatusText));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 麦克风音量
|
||||
/// </summary>
|
||||
private uint micoVolume;
|
||||
public uint MicoVolume
|
||||
{
|
||||
get { return micoVolume; }
|
||||
set { SetProperty(ref micoVolume, value); }
|
||||
}
|
||||
|
||||
// 用于控制斜线显示的属性
|
||||
public Visibility MicoSlashLineVisibility => (IsMuteAudio ? Visibility.Visible : Visibility.Collapsed);
|
||||
// 用于控制斜线显示的属性
|
||||
public Visibility VideoSlashLineVisibility => (IsMuteVideo ? Visibility.Visible : Visibility.Collapsed);
|
||||
|
||||
//用于控制麦克风文本显示的属性
|
||||
public string MicStatusText => (IsMuteAudio ? "解除静音" : "静音");
|
||||
|
||||
//用于控制摄像头文本显示的属性
|
||||
public string VideoStatusText => (IsMuteVideo ? "开启视频" : "停止视频");
|
||||
|
||||
|
||||
|
||||
public static User GetInit()
|
||||
{
|
||||
return new User
|
||||
{
|
||||
Id = 0,
|
||||
UserName = "待初始化"
|
||||
};
|
||||
}
|
||||
|
||||
//void IDisposable.Dispose()
|
||||
//{
|
||||
// get { return isMain; }
|
||||
// set { SetProperty(ref isMain, value); }
|
||||
// if (Avatar != null)
|
||||
// {
|
||||
// Avatar.Dispose();
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@
|
|||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<!--<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/SkinDark.xaml" />-->
|
||||
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/SkinDefault.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/Theme.xaml" />
|
||||
<ResourceDictionary Source="/Assets/Resource/MeetingRoomDictionary.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,151 @@
|
|||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:converter="clr-namespace:Demo.Common.Converters;assembly=Demo.Common"
|
||||
xmlns:hc="https://handyorg.github.io/handycontrol">
|
||||
|
||||
<converter:BooleanInverseConverter x:Key="BoolInverseConverter" />
|
||||
<converter:AspectRatioConverter x:Key="AspectRatioConverter" />
|
||||
<!--<converter:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />-->
|
||||
<converter:VolumeToScaleConverter x:Key="VolumeToScaleConverter" />
|
||||
|
||||
<Style
|
||||
x:Key="WGS_ButtonDock"
|
||||
BasedOn="{StaticResource ButtonBaseStyle}"
|
||||
TargetType="{x:Type Button}">
|
||||
<Setter Property="Width" Value="50" />
|
||||
<Setter Property="Height" Value="45" />
|
||||
<Setter Property="FontSize" Value="11" />
|
||||
<Setter Property="Background" Value="#FFFFFF" />
|
||||
<Setter Property="BorderBrush" Value="#FFFFFF" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#E6E8EB" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="Background" Value="#c7c7c7" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="WGS_SplitButtonDock"
|
||||
BasedOn="{StaticResource SplitButtonBaseStyle}"
|
||||
TargetType="hc:SplitButton">
|
||||
<Setter Property="Width" Value="10" />
|
||||
<Setter Property="Height" Value="45" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource PrimaryTextBrush}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="hc:SplitButton">
|
||||
<hc:SimplePanel x:Name="templateRoot">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="{Binding Path=(hc:BorderElement.CornerRadius), RelativeSource={RelativeSource TemplatedParent}}" />
|
||||
<Border
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{Binding Path=(hc:BorderElement.CornerRadius), RelativeSource={RelativeSource TemplatedParent}}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel
|
||||
Margin="{TemplateBinding Padding}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Orientation="Horizontal">
|
||||
<Path
|
||||
x:Name="PathMain"
|
||||
Width="{TemplateBinding hc:IconElement.Width}"
|
||||
Height="{TemplateBinding hc:IconElement.Height}"
|
||||
Data="{TemplateBinding hc:IconElement.Geometry}"
|
||||
Fill="{TemplateBinding Foreground}"
|
||||
SnapsToDevicePixels="True"
|
||||
Stretch="Uniform" />
|
||||
<ContentPresenter
|
||||
x:Name="ContentPresenterMain"
|
||||
Margin="6,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
RecognizesAccessKey="True"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
</StackPanel>
|
||||
<ToggleButton
|
||||
x:Name="PART_Arrow"
|
||||
Grid.Column="1"
|
||||
Width="Auto"
|
||||
Height="Auto"
|
||||
Padding="{Binding Padding, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource ThicknessSplitConverter}, ConverterParameter='0,1,1,1'}"
|
||||
hc:IconElement.Geometry="{StaticResource UpGeometry}"
|
||||
hc:IconElement.Width="9"
|
||||
hc:IconSwitchElement.GeometrySelected="{StaticResource DownGeometry}"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Style="{StaticResource ToggleButtonIconTransparent}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<Popup
|
||||
Margin="1"
|
||||
AllowsTransparency="true"
|
||||
IsOpen="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Placement="Top"
|
||||
PopupAnimation="Fade"
|
||||
StaysOpen="False">
|
||||
<Border
|
||||
x:Name="dropDownBorder"
|
||||
MinWidth="{Binding ActualWidth, ElementName=templateRoot}"
|
||||
MaxHeight="{TemplateBinding MaxDropDownHeight}"
|
||||
Background="{DynamicResource RegionBrush}"
|
||||
BorderBrush="{DynamicResource BorderBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="{Binding Path=(hc:BorderElement.CornerRadius), RelativeSource={RelativeSource TemplatedParent}}">
|
||||
<ScrollViewer x:Name="DropDownScrollViewer" Margin="0,4">
|
||||
<hc:SimplePanel RenderOptions.ClearTypeHint="Enabled">
|
||||
<Canvas
|
||||
Width="0"
|
||||
Height="0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top">
|
||||
<Rectangle
|
||||
x:Name="opaqueRect"
|
||||
Width="{Binding ActualWidth, ElementName=dropDownBorder}"
|
||||
Height="{Binding ActualHeight, ElementName=dropDownBorder}"
|
||||
Fill="{Binding Background, ElementName=dropDownBorder}" />
|
||||
</Canvas>
|
||||
<ContentPresenter ContentSource="DropDownContent" />
|
||||
</hc:SimplePanel>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</Popup>
|
||||
</hc:SimplePanel>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="Content" Value="{x:Null}">
|
||||
<Setter TargetName="ContentPresenterMain" Property="Visibility" Value="Collapsed" />
|
||||
</Trigger>
|
||||
<Trigger Property="hc:IconElement.Geometry" Value="{x:Null}">
|
||||
<Setter TargetName="PathMain" Property="Visibility" Value="Collapsed" />
|
||||
<Setter TargetName="ContentPresenterMain" Property="Margin" Value="0" />
|
||||
</Trigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="HitMode" Value="Click" />
|
||||
<Condition SourceName="PART_Arrow" Property="IsChecked" Value="True" />
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter Property="IsDropDownOpen" Value="True" />
|
||||
</MultiTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="{DynamicResource SecondaryRegionBrush}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="Background" Value="{DynamicResource BorderBrush}" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
|
||||
</ResourceDictionary>
|
||||
|
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1734572431893" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4287" width="128" height="128" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M318.513292 1001.73913a40.70559 40.70559 0 0 1-17.172671-4.452173 35.617391 35.617391 0 0 1-16.536646-21.624845 36.889441 36.889441 0 0 1 3.816149-26.077019l94.767702-164.730435a34.981366 34.981366 0 0 1 30.529193-17.17267 33.073292 33.073292 0 0 1 17.17267 4.452173 34.981366 34.981366 0 0 1 12.720497 47.701864l-94.767702 164.730435a34.981366 34.981366 0 0 1-30.529192 17.17267zM685.499627 1001.73913a33.709317 33.709317 0 0 1-29.893167-17.17267l-95.403727-164.730435a34.981366 34.981366 0 0 1 12.720497-47.701864 34.981366 34.981366 0 0 1 17.808695-4.452173 34.981366 34.981366 0 0 1 29.893168 17.17267l95.403727 164.730435a33.709317 33.709317 0 0 1 3.180124 26.077019 35.617391 35.617391 0 0 1-16.536646 21.624845 38.161491 38.161491 0 0 1-17.172671 4.452173z" fill="#8a8a8a" p-id="4288"></path><path d="M216.749317 1024a34.981366 34.981366 0 0 1-34.345342-34.981366 34.981366 34.981366 0 0 1 34.345342-34.981367h572.42236a34.981366 34.981366 0 0 1 34.981366 34.981367 34.981366 34.981366 0 0 1-34.981366 34.981366zM175.407702 669.734161a410.236025 410.236025 0 0 1-88.407454-254.409937A415.324224 415.324224 0 0 1 502.324472 0.636025a407.691925 407.691925 0 0 1 254.409938 87.771428l-45.157764 50.245963A365.714286 365.714286 0 0 0 502.324472 69.962733 345.997516 345.997516 0 0 0 156.962981 415.324224a338.365217 338.365217 0 0 0 71.870808 204.8l-48.337888 52.154037zM502.324472 829.376398a415.324224 415.324224 0 0 1-284.303106-112.576398l45.157764-49.609938a363.170186 363.170186 0 0 0 239.145342 92.859627 345.361491 345.361491 0 0 0 344.725466-345.36149 342.817391 342.817391 0 0 0-92.223603-234.693168l44.52174-48.973913a395.607453 395.607453 0 0 1 117.664596 283.667081 415.324224 415.324224 0 0 1-414.688199 414.688199z" fill="#8a8a8a" p-id="4289"></path><path d="M334.413913 509.455901a190.807453 190.807453 0 0 1-25.440994-94.767702 190.807453 190.807453 0 0 1 190.807454-190.807454 190.807453 190.807453 0 0 1 94.131677 26.077019l-45.793789 52.154037a183.175155 183.175155 0 0 0-48.337888-8.268323 127.204969 127.204969 0 0 0-124.024845 123.38882 127.204969 127.204969 0 0 0 8.904348 45.157764l-50.245963 53.426087zM502.324472 608.039752a190.807453 190.807453 0 0 1-127.204969-48.337889l44.521739-49.609938a141.83354 141.83354 0 0 0 82.047205 27.985094 123.38882 123.38882 0 0 0 123.38882-123.38882 123.38882 123.38882 0 0 0-28.621118-77.595031l45.157764-48.973913a177.450932 177.450932 0 0 1 52.790062 127.204969 190.807453 190.807453 0 0 1-192.079503 192.715528z" fill="#8a8a8a" p-id="4290"></path><path d="M108.625093 905.699379a34.981366 34.981366 0 0 1-24.168944-10.176398 33.709317 33.709317 0 0 1 0-48.973913L890.299627 40.069565a36.253416 36.253416 0 0 1 24.804969-10.176397 36.253416 36.253416 0 0 1 24.804969 10.176397 34.981366 34.981366 0 0 1 0 48.973913L133.430062 895.522981a34.981366 34.981366 0 0 1-24.804969 10.176398z" fill="#8a8a8a" p-id="4291"></path></svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
|
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1733909306083" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13182" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M504.391111 734.862222h14.933333a200.675556 200.675556 0 0 0 200.533334-200.675555V200.675556A200.675556 200.675556 0 0 0 519.466667 0h-15.075556A200.675556 200.675556 0 0 0 304 200.675556v333.511111a200.675556 200.675556 0 0 0 200.391111 200.675555z" fill="#4E5461" p-id="13183"></path><path d="M843.022222 533.191111a34.986667 34.986667 0 0 0-69.973333 0 261.12 261.12 0 1 1-522.24 0 34.986667 34.986667 0 0 0-69.973333 0 331.662222 331.662222 0 0 0 296.106666 329.244445v91.448888h-121.457778a35.057778 35.057778 0 0 0 0 70.115556h312.888889a34.986667 34.986667 0 1 0 0-69.973333h-120.888889v-91.591111a331.662222 331.662222 0 0 0 295.537778-329.244445z" fill="#4E5461" p-id="13184"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
|
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1734420317677" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9974" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M860.16 322.56h-15.36L716.8 419.84v168.96c0 5.12 0 10.24 5.12 10.24l128 97.28c5.12 0 5.12 5.12 10.24 5.12h5.12c5.12 0 5.12-5.12 5.12-15.36V337.92c0-5.12-5.12-10.24-10.24-15.36zM599.04 291.84H215.04S153.6 291.84 153.6 353.28V665.6c0 61.44 61.44 61.44 61.44 61.44h384s61.44 0 61.44-61.44V358.4c5.12-66.56-61.44-66.56-61.44-66.56z" fill="#666666" p-id="9975"></path></svg>
|
||||
|
After Width: | Height: | Size: 702 B |
|
|
@ -1,6 +1,6 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<UseWindowsForms>True</UseWindowsForms>
|
||||
|
|
@ -8,8 +8,11 @@
|
|||
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Assets\manager.svg" />
|
||||
<None Remove="Assets\camera_close.svg" />
|
||||
<None Remove="Assets\huatong.png" />
|
||||
<None Remove="Assets\mico_btn.svg" />
|
||||
<None Remove="Assets\more.svg" />
|
||||
<None Remove="Assets\video_btn.svg" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="agora_rtc_sdk" Version="4.2.1" />
|
||||
|
|
@ -24,10 +27,20 @@
|
|||
<ProjectReference Include="..\..\Demo.Services\Demo.Services.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Assets\manager.svg" />
|
||||
<Resource Include="Assets\camera_close.svg" />
|
||||
<Resource Include="Assets\mico_btn.svg" />
|
||||
<Resource Include="Assets\more.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Resource>
|
||||
<Resource Include="Assets\mico.svg" />
|
||||
<Resource Include="Assets\video_btn.svg" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="Assets\manager.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Assets\camera_close1.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using Agora.Rtc;
|
||||
using AngleSharp.Dom;
|
||||
using Demo.Common.Events;
|
||||
using Demo.Common.Helpers;
|
||||
using Demo.Common.Models;
|
||||
|
|
@ -6,10 +7,12 @@ using Demo.Services.Interfaces;
|
|||
using HandyControl.Controls;
|
||||
using HandyControl.Data;
|
||||
using Masuit.Tools;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
|
||||
using Prism.Commands;
|
||||
using Prism.Events;
|
||||
using Prism.Mvvm;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows;
|
||||
|
||||
namespace Meeting.V2.Demo.ViewModels
|
||||
|
|
@ -20,6 +23,15 @@ namespace Meeting.V2.Demo.ViewModels
|
|||
{
|
||||
this._aggregator = aggregator;
|
||||
this._agoraConfigService = agoraConfigService;
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
private void Init()
|
||||
{
|
||||
AgoraHelper.Init("4a4f7be64fa1404ebda74784fe9ac381", (uint)UId);
|
||||
InitMicoDevices();
|
||||
InitPlaybackDevices();
|
||||
}
|
||||
|
||||
private string _title = "Prism Application";
|
||||
|
|
@ -46,17 +58,46 @@ namespace Meeting.V2.Demo.ViewModels
|
|||
set { SetProperty(ref isJoin, value); }
|
||||
}
|
||||
|
||||
private ObservableCollection<AgoraDeviceInfo> audioDevices = new();
|
||||
public ObservableCollection<AgoraDeviceInfo> AudioDevices
|
||||
{
|
||||
get { return audioDevices; }
|
||||
set { SetProperty(ref audioDevices, value); }
|
||||
}
|
||||
private ObservableCollection<AgoraDeviceInfo> playbackDevices = new();
|
||||
public ObservableCollection<AgoraDeviceInfo> PlaybackDevices
|
||||
{
|
||||
get { return playbackDevices; }
|
||||
set { SetProperty(ref playbackDevices, value); }
|
||||
}
|
||||
|
||||
|
||||
|
||||
private User localUser;
|
||||
public User LocalUser
|
||||
{
|
||||
get { return localUser; }
|
||||
set
|
||||
{
|
||||
SetProperty(ref localUser, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public DelegateCommand JoinChannelCommand => new DelegateCommand(JoinChannel);
|
||||
async void JoinChannel()
|
||||
{
|
||||
AgoraHelper.Init("4a4f7be64fa1404ebda74784fe9ac381", (uint)UId);
|
||||
{
|
||||
//var rrr = AgoraHelper._RtcEngineInstance.RegisterLocalUserAccount("4a4f7be64fa1404ebda74784fe9ac381", UId.ToString());
|
||||
|
||||
var rtcToken = await _agoraConfigService.GetRtcTokenAsync();
|
||||
if (!string.IsNullOrWhiteSpace(rtcToken))
|
||||
Application.Current.Properties["rtctoken"] = rtcToken;
|
||||
|
||||
//var result = AgoraHelper._RtcEngineInstance.EnableAudio();
|
||||
//Console.WriteLine($@"{nameof(JoinChannel)}-EnableAudio : {result}");
|
||||
|
||||
var result = AgoraHelper._RtcEngineInstance.EnableVideo();
|
||||
Console.WriteLine($@"{nameof(JoinChannel)}-EnableVideo : {result}");
|
||||
|
||||
|
|
@ -76,28 +117,75 @@ namespace Meeting.V2.Demo.ViewModels
|
|||
options.autoSubscribeVideo.SetValue(true);
|
||||
|
||||
AgoraHelper._RtcEngineInstance.JoinChannel(System.Windows.Application.Current.Properties["rtctoken"].ToString(), "999", (uint)UId, options);
|
||||
//AgoraHelper._RtcEngineInstance.JoinChannelWithUserAccount(System.Windows.Application.Current.Properties["rtctoken"].ToString(), "999", UId.ToString(), options);
|
||||
|
||||
///启用用户音量提示。
|
||||
AgoraHelper._RtcEngineInstance.EnableAudioVolumeIndication(200, 5, false);
|
||||
AgoraHelper._RtcEngineInstance.EnableAudioVolumeIndication(200, 3, false);
|
||||
|
||||
_aggregator.GetEvent<UserJoinEvent>().Publish(new User
|
||||
LocalUser = new User
|
||||
{
|
||||
Id = UId,
|
||||
UserName = UId.ToString(),
|
||||
IsManager = true,
|
||||
IsLocal = true
|
||||
});
|
||||
IsLocal = true,
|
||||
IsMuteVideo = false,
|
||||
IsMuteAudio = false
|
||||
};
|
||||
_aggregator.GetEvent<UserJoinEvent>().Publish(LocalUser);
|
||||
|
||||
IsJoin = !IsJoin;
|
||||
Growl.Success("加入频道成功");
|
||||
UserInfo userInfo = new UserInfo();
|
||||
var rrra = AgoraHelper._RtcEngineInstance.GetUserInfoByUserAccount(UId.ToString(), ref userInfo);
|
||||
|
||||
|
||||
//GetDevices();
|
||||
}
|
||||
|
||||
public DelegateCommand JoinChannel4AudienceCommand => new DelegateCommand(JoinChannelForAudience);
|
||||
private async void JoinChannelForAudience()
|
||||
{
|
||||
AgoraHelper.Init("4a4f7be64fa1404ebda74784fe9ac381", (uint)UId);
|
||||
|
||||
var rtcToken = await _agoraConfigService.GetRtcTokenAsync();
|
||||
if (!string.IsNullOrWhiteSpace(rtcToken))
|
||||
Application.Current.Properties["rtctoken"] = rtcToken;
|
||||
|
||||
var result = AgoraHelper._RtcEngineInstance.EnableVideo();
|
||||
AgoraHelper._RtcEngineInstance.EnableLocalAudio(false);
|
||||
AgoraHelper._RtcEngineInstance.MuteLocalVideoStream(false);
|
||||
AgoraHelper._RtcEngineInstance.MuteAllRemoteVideoStreams(true);
|
||||
AgoraHelper._RtcEngineInstance.MuteAllRemoteAudioStreams(true);
|
||||
|
||||
|
||||
ChannelMediaOptions options = new ChannelMediaOptions();
|
||||
options.channelProfile.SetValue(CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING);
|
||||
options.clientRoleType.SetValue(CLIENT_ROLE_TYPE.CLIENT_ROLE_AUDIENCE);
|
||||
options.audienceLatencyLevel.SetValue(AUDIENCE_LATENCY_LEVEL_TYPE.AUDIENCE_LATENCY_LEVEL_LOW_LATENCY);
|
||||
//// 发布麦克风采集的音频流
|
||||
//options.publishMicrophoneTrack.SetValue(true);
|
||||
//// 发布摄像头采集的视频流
|
||||
//options.publishCameraTrack.SetValue(true);
|
||||
// 自动订阅所有音频流
|
||||
options.autoSubscribeAudio.SetValue(true);
|
||||
// 自动订阅所有视频流
|
||||
options.autoSubscribeVideo.SetValue(true);
|
||||
|
||||
AgoraHelper._RtcEngineInstance.JoinChannel(System.Windows.Application.Current.Properties["rtctoken"].ToString(), "999", (uint)UId, options);
|
||||
IsJoin = !IsJoin;
|
||||
Growl.Success("加入频道成功");
|
||||
|
||||
|
||||
//GetDevices();
|
||||
}
|
||||
|
||||
public DelegateCommand LeavelChannelCommand => new DelegateCommand(LeavelChannel);
|
||||
void LeavelChannel()
|
||||
{
|
||||
//AgoraHelper._RtcEngineInstance.DisableAudio();
|
||||
AgoraHelper._RtcEngineInstance.LeaveChannel();
|
||||
AgoraHelper._RtcEngineInstance.StopPreview();
|
||||
AgoraHelper._RtcEngineInstance.DisableVideo();
|
||||
AgoraHelper._RtcEngineInstance.StopPreview();
|
||||
|
||||
|
||||
_aggregator.GetEvent<UserLeaveEvent>().Publish(new User
|
||||
|
|
@ -113,6 +201,115 @@ namespace Meeting.V2.Demo.ViewModels
|
|||
Growl.Success("离开频道成功");
|
||||
}
|
||||
|
||||
public DelegateCommand OperMicoCommand => new DelegateCommand(ExecuteOperMicoCommand);
|
||||
|
||||
void ExecuteOperMicoCommand()
|
||||
{
|
||||
if (LocalUser == null)
|
||||
{
|
||||
Growl.Error("请先加入频道!");
|
||||
return;
|
||||
}
|
||||
// 静音
|
||||
var result = AgoraHelper._RtcEngineInstance.MuteLocalAudioStream(!LocalUser.IsMuteAudio);
|
||||
if (result != 0)
|
||||
{
|
||||
Growl.Error($@"开启音频错误code:{result}");
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public DelegateCommand OperVideoCommand => new DelegateCommand(ExecuteOperVideoCommand);
|
||||
|
||||
void ExecuteOperVideoCommand()
|
||||
{
|
||||
if (LocalUser == null)
|
||||
{
|
||||
Growl.Error("请先加入频道!");
|
||||
return;
|
||||
}
|
||||
if (!LocalUser.IsMuteVideo)
|
||||
{
|
||||
// 停止本地预览
|
||||
AgoraHelper._RtcEngineInstance.StopPreview();
|
||||
}
|
||||
else
|
||||
{
|
||||
AgoraHelper._RtcEngineInstance.StartPreview();
|
||||
}
|
||||
// 关闭摄像头
|
||||
var result = AgoraHelper._RtcEngineInstance.MuteLocalVideoStream(!LocalUser.IsMuteVideo);
|
||||
if (result != 0)
|
||||
{
|
||||
Growl.Error($@"开启视频错误code:{result}");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换麦克风设备
|
||||
/// </summary>
|
||||
public DelegateCommand<string> ChangeAudioDeviceCommand => new DelegateCommand<string>(ExecuteChangeAudioDeviceCommand);
|
||||
void ExecuteChangeAudioDeviceCommand(string audioDeviceId)
|
||||
{
|
||||
var audioDeviceManager = AgoraHelper._RtcEngineInstance.GetAudioDeviceManager();
|
||||
audioDeviceManager.SetRecordingDevice(audioDeviceId);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 切换扬声器设备
|
||||
/// </summary>
|
||||
public DelegateCommand<string> ChangePlaybackDeviceCommand => new DelegateCommand<string>(ExecuteChangePlaybackDeviceCommand);
|
||||
void ExecuteChangePlaybackDeviceCommand(string playbackDeviceId)
|
||||
{
|
||||
var audioDeviceManager = AgoraHelper._RtcEngineInstance.GetAudioDeviceManager();
|
||||
audioDeviceManager.SetPlaybackDevice(playbackDeviceId);
|
||||
}
|
||||
|
||||
|
||||
private void InitMicoDevices()
|
||||
{
|
||||
AudioDevices.Clear();
|
||||
var audioDeviceManager = AgoraHelper._RtcEngineInstance.GetAudioDeviceManager();
|
||||
var defaultAudioDeviceId = string.Empty;
|
||||
var defaultAudioDeviceName = string.Empty;
|
||||
audioDeviceManager.GetRecordingDefaultDevice(ref defaultAudioDeviceId, ref defaultAudioDeviceName);
|
||||
defaultAudioDeviceName = $@"系统默认({defaultAudioDeviceName})";
|
||||
|
||||
AudioDevices.Add(new AgoraDeviceInfo(defaultAudioDeviceId, defaultAudioDeviceName, true));
|
||||
// 获取所有麦克风设备
|
||||
var deviceInfos = audioDeviceManager.EnumerateRecordingDevices();
|
||||
|
||||
foreach (var deviceInfo in deviceInfos)
|
||||
{
|
||||
AudioDevices.Add(new AgoraDeviceInfo(deviceInfo.deviceId, deviceInfo.deviceName));
|
||||
}
|
||||
}
|
||||
|
||||
private void InitPlaybackDevices()
|
||||
{
|
||||
PlaybackDevices.Clear();
|
||||
var audioDeviceManager = AgoraHelper._RtcEngineInstance.GetAudioDeviceManager();
|
||||
var defaultPlaybackDeviceId = string.Empty;
|
||||
var defaultPlaybackDeviceName = string.Empty;
|
||||
audioDeviceManager.GetPlaybackDefaultDevice(ref defaultPlaybackDeviceId, ref defaultPlaybackDeviceName);
|
||||
defaultPlaybackDeviceName = $@"系统默认({defaultPlaybackDeviceName})";
|
||||
|
||||
PlaybackDevices.Add(new AgoraDeviceInfo(defaultPlaybackDeviceId, defaultPlaybackDeviceName, true));
|
||||
// 获取所有扬声器
|
||||
var deviceInfos = audioDeviceManager.EnumeratePlaybackDevices();
|
||||
|
||||
foreach (var deviceInfo in deviceInfos)
|
||||
{
|
||||
PlaybackDevices.Add(new AgoraDeviceInfo(deviceInfo.deviceId, deviceInfo.deviceName));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
using Demo.Common.Events;
|
||||
using Agora.Rtc;
|
||||
using Demo.Common.Events;
|
||||
using Demo.Common.Extensions;
|
||||
using Masuit.Tools;
|
||||
using Meeting.V2.Demo.Core.Mvvm;
|
||||
using Prism.Commands;
|
||||
using Prism.Events;
|
||||
using Prism.Regions;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Threading;
|
||||
|
|
@ -12,6 +16,7 @@ using User = Demo.Common.Models.User;
|
|||
|
||||
namespace Meeting.V2.Demo.ViewModels
|
||||
{
|
||||
|
||||
public class VideoAreaViewModel : RegionViewModelBase
|
||||
{
|
||||
public VideoAreaViewModel(IRegionManager regionManager,
|
||||
|
|
@ -21,8 +26,16 @@ namespace Meeting.V2.Demo.ViewModels
|
|||
|
||||
aggregator.GetEvent<UserJoinEvent>().Subscribe((a) => Application.Current.Dispatcher.BeginInvoke(() =>
|
||||
{
|
||||
|
||||
UserInfos.Add(a);
|
||||
// 不新增,避免重复创建销毁控件
|
||||
UserInfos.AddUser(a);
|
||||
//for (var i = 0; i < UserInfos.Count; i++)
|
||||
//{
|
||||
// if (UserInfos[i].Id == 0)
|
||||
// {
|
||||
// UserInfos[i] = a;
|
||||
// return;
|
||||
// }
|
||||
//}
|
||||
}));
|
||||
aggregator.GetEvent<UserLeaveEvent>().Subscribe((a) =>
|
||||
{
|
||||
|
|
@ -30,27 +43,30 @@ namespace Meeting.V2.Demo.ViewModels
|
|||
{
|
||||
if (a.IsLocal)
|
||||
{
|
||||
UserInfos.Clear();
|
||||
//for (var i = 0; i < UserInfos.Count; i++)
|
||||
//{
|
||||
// UserInfos[i] = new User() { Id = 0, UserName = "待初始化" };
|
||||
//}
|
||||
UserInfos.ClearAllUser();
|
||||
FocusUser = null;
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < UserInfos.Count; i++)
|
||||
{
|
||||
if (UserInfos[i].Id == a.Id)
|
||||
{
|
||||
UserInfos.RemoveAt(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
UserInfos.ClearUserById(a.Id);
|
||||
//for (int i = 0; i < UserInfos.Count; i++)
|
||||
//{
|
||||
// if (UserInfos[i].Id == a.Id)
|
||||
// {
|
||||
// UserInfos[i] = User.GetInit();
|
||||
// //UserInfos.RemoveAt(i);
|
||||
// return;
|
||||
// }
|
||||
//}
|
||||
});
|
||||
|
||||
});
|
||||
//UserInfos.Add(new UserInfo() { Id = 1, UserName = "张三", IsManager = true });
|
||||
//UserInfos.Add(new UserInfo() { Id = 2, UserName = "李四", IsManager = false });
|
||||
//UserInfos.Add(new UserInfo() { Id = 3, UserName = "李四", IsManager = false });
|
||||
//UserInfos.Add(new UserInfo() { Id = 4, UserName = "李四", IsManager = false });
|
||||
//UserInfos.Add(new UserInfo() { Id = 4, UserName = "李四", IsManager = false });
|
||||
//UserInfos.Add(new UserInfo() { Id = 4, UserName = "李四", IsManager = false });
|
||||
|
||||
UserInfos.InitUserList(6);
|
||||
}
|
||||
|
||||
private ObservableCollection<User> userInfos = new();
|
||||
|
|
|
|||
|
|
@ -2,37 +2,62 @@
|
|||
using Demo.Common.Events;
|
||||
using Demo.Common.Helpers;
|
||||
using Demo.Common.Models;
|
||||
using DryIoc;
|
||||
using HandyControl.Controls;
|
||||
using Masuit.Tools;
|
||||
using Meeting.V2.Demo.Core.Mvvm;
|
||||
using Meeting.V2.Demo.Views;
|
||||
using Prism.Commands;
|
||||
using Prism.Events;
|
||||
using SixLabors.ImageSharp;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Meeting.V2.Demo.ViewModels
|
||||
{
|
||||
public class VideoViewModel : ViewModelBase
|
||||
{
|
||||
|
||||
private VideoView _view;
|
||||
private VideoView _this;
|
||||
|
||||
public VideoViewModel(IEventAggregator aggregator)
|
||||
{
|
||||
aggregator.GetEvent<AudioVolumeIndicationEvent>().Subscribe(SetUserVolume,
|
||||
// 音频音量监听
|
||||
aggregator.GetEvent<AudioVolumeIndicationEvent>().Subscribe((arg => UserInfo.MicoVolume = arg.volume),
|
||||
arg =>
|
||||
{
|
||||
return UserInfo != null && UserInfo.Id > 0 && arg.uid > 0 && arg.uid == UserInfo.Id;
|
||||
return UserInfo != null && UserInfo.Id > 0 && arg.uid > 0 && arg.uid == UserInfo.Id && !UserInfo.IsMuteAudio;
|
||||
});
|
||||
|
||||
// 语音状态监听
|
||||
aggregator.GetEvent<AudioMuteStateChangedEvent>().Subscribe((arg) =>
|
||||
{
|
||||
UserInfo.IsMuteAudio = arg.IsMute;
|
||||
}, arg => UserInfo != null && UserInfo.Id > 0 && arg.UId > 0 && arg.UId == UserInfo.Id);
|
||||
|
||||
// 视频状态监听
|
||||
aggregator.GetEvent<VideoMuteStateChangedEvent>().Subscribe((arg) =>
|
||||
{
|
||||
UserInfo.IsMuteVideo = arg.IsMute;
|
||||
if (arg.IsMute)
|
||||
{
|
||||
RefreshPictureBox();
|
||||
//DisplayAvatar();
|
||||
}
|
||||
}, arg => UserInfo != null && UserInfo.Id > 0 && arg.UId > 0 && arg.UId == UserInfo.Id);
|
||||
}
|
||||
|
||||
// 初始化控件时,将控件传入到ViewModel中
|
||||
public DelegateCommand<VideoView> LoadedCommand => new DelegateCommand<VideoView>((u) =>
|
||||
{
|
||||
if (u == null)
|
||||
return;
|
||||
Console.WriteLine($@"LoadedCoommand 被执行了!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
|
||||
this._view = u;
|
||||
this._this = u;
|
||||
RenderVideoView();
|
||||
});
|
||||
|
||||
|
|
@ -55,61 +80,67 @@ namespace Meeting.V2.Demo.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
private uint microphoneVolume;
|
||||
/// <summary>
|
||||
/// 用户麦克风音量
|
||||
/// </summary>
|
||||
public uint MicrophoneVolume
|
||||
{
|
||||
get { return microphoneVolume; }
|
||||
set { SetProperty(ref microphoneVolume, value); }
|
||||
}
|
||||
|
||||
private void SetUserVolume(AudioVolumeInfo volumeInfo)
|
||||
{
|
||||
MicrophoneVolume = volumeInfo.volume;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public DelegateCommand OperClickCommand => new DelegateCommand(() =>
|
||||
{
|
||||
Console.WriteLine($@"id:{UserInfo.Id} userName:{UserInfo.UserName} isManager:{UserInfo.IsManager}");
|
||||
UserInfo.IsManager = !UserInfo.IsManager;
|
||||
userInfo.UserName = DateTime.Now.ToString();
|
||||
Console.WriteLine($@"id:{UserInfo.Id} userName:{UserInfo.UserName} isManager:{UserInfo.IsManager}");
|
||||
//MessageBox.Show($@"id:{Id} userName:{UserName} isManager:{IsManager}");
|
||||
//UserInfo.IsMuteAudio = !UserInfo.IsMuteAudio;
|
||||
//if (UserInfo.IsLocal)
|
||||
//{
|
||||
// // 静音
|
||||
// var result = AgoraHelper._RtcEngineInstance.MuteLocalAudioStream(!UserInfo.IsMuteAudio);
|
||||
// if (result != 0)
|
||||
// {
|
||||
// Growl.Error($@"开启音频错误code:{result}");
|
||||
// return;
|
||||
// }
|
||||
//}
|
||||
|
||||
//if (UserInfo.Id > 0)
|
||||
//{
|
||||
// UserInfo.Id = 0;
|
||||
//}
|
||||
|
||||
});
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 渲染视频画面
|
||||
/// </summary>
|
||||
public void RenderVideoView()
|
||||
{
|
||||
if (_view == null || UserInfo == null || UserInfo.Id <= 0)
|
||||
if (_this == null || UserInfo == null)
|
||||
{
|
||||
if (_view != null)
|
||||
{
|
||||
//_view.pic_frame.Invoke(() =>
|
||||
//{
|
||||
// _view.pic_frame.Refresh();
|
||||
//});
|
||||
|
||||
if (oldUserInfo != null)
|
||||
{
|
||||
AgoraHelper.ClearUserVideo((uint)(oldUserInfo.Id), (long)_view.pic_frame.Handle);
|
||||
_view.pic_frame.Invoke(() =>
|
||||
{
|
||||
_view.pic_frame.Refresh();
|
||||
});
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
AgoraHelper.SetupUserVideo((uint)UserInfo.Id, (long)_view.pic_frame.Handle, (uint)(oldUserInfo == null ? 0 : oldUserInfo.Id));
|
||||
if (_this != null && oldUserInfo != null && (UserInfo == null || UserInfo.Id == 0))
|
||||
{
|
||||
AgoraHelper.ClearUserVideo((uint)(oldUserInfo.Id), (long)_this.pic_frame.Handle);
|
||||
RefreshPictureBox();
|
||||
}
|
||||
|
||||
AgoraHelper.SetupUserVideo((uint)UserInfo.Id, (long)_this.pic_frame.Handle, (uint)(oldUserInfo == null ? 0 : oldUserInfo.Id));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新PictureBox视图,解决最后一帧不刷新的问题
|
||||
/// </summary>
|
||||
private void RefreshPictureBox()
|
||||
{
|
||||
_this.pic_frame.BeginInvoke(() =>
|
||||
{
|
||||
_this.pic_frame.Refresh();
|
||||
});
|
||||
}
|
||||
|
||||
private void DisplayAvatar()
|
||||
{
|
||||
_this.pic_frame.BeginInvoke(() =>
|
||||
{
|
||||
//_this.pic_frame.Image = AvatatHelper.CameraCloseImage;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@
|
|||
xmlns:cmn="clr-namespace:Demo.Common;assembly=Demo.Common"
|
||||
xmlns:cnt="clr-namespace:Demo.Common.Converters;assembly=Demo.Common"
|
||||
xmlns:hc="https://handyorg.github.io/handycontrol"
|
||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
xmlns:intertop="clr-namespace:Microsoft.DwayneNeed.Interop;assembly=MahApps.Microsoft.DwayneNeed"
|
||||
xmlns:local="clr-namespace:Meeting.V2.Demo.Views"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
|
||||
xmlns:wfc="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
|
||||
xmlns:wfh="clr-namespace:System.Windows.Forms.Integration;assembly=WindowsFormsIntegration"
|
||||
Width="962"
|
||||
|
|
@ -15,16 +17,11 @@
|
|||
MinWidth="1050"
|
||||
MinHeight="642"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
Background="#252525"
|
||||
Background="#FFFFFF"
|
||||
CloseButtonForeground="Red"
|
||||
NonClientAreaBackground="Black"
|
||||
NonClientAreaForeground="White"
|
||||
OtherButtonForeground="White"
|
||||
NonClientAreaForeground="#7B818F"
|
||||
OtherButtonForeground="#7B818F"
|
||||
WindowStartupLocation="CenterScreen">
|
||||
<Window.Resources>
|
||||
<cnt:BooleanInverseConverter x:Key="BoolInverseConverter" />
|
||||
</Window.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
|
|
@ -32,44 +29,193 @@
|
|||
</Grid.RowDefinitions>
|
||||
|
||||
<ContentControl Grid.Row="0" prism:RegionManager.RegionName="{x:Static cmn:RegionNames.VideoRegion}" />
|
||||
<!--<ScrollViewer
|
||||
Grid.RowSpan="2"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalScrollBarVisibility="Hidden">
|
||||
<StackPanel Margin="0,10,10,10" VerticalAlignment="Top" />
|
||||
</ScrollViewer>-->
|
||||
|
||||
<ScrollViewer
|
||||
<DockPanel Grid.Row="1">
|
||||
<StackPanel VerticalAlignment="Center" Orientation="Horizontal">
|
||||
<hc:TextBox
|
||||
x:Name="uid"
|
||||
Margin="5"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
hc:InfoElement.Placeholder="输入任意UID"
|
||||
IsEnabled="False"
|
||||
Text="{Binding UId}" />
|
||||
<Button
|
||||
Margin="5"
|
||||
Command="{Binding JoinChannelCommand}"
|
||||
IsEnabled="{Binding IsJoin, Converter={StaticResource BoolInverseConverter}}">
|
||||
主播加入频道
|
||||
</Button>
|
||||
<Button Command="{Binding JoinChannel4AudienceCommand}" IsEnabled="{Binding IsJoin, Converter={StaticResource BoolInverseConverter}}">
|
||||
观众加入频道
|
||||
</Button>
|
||||
<Button
|
||||
Margin="5"
|
||||
Command="{Binding LeavelChannelCommand}"
|
||||
IsEnabled="{Binding IsJoin}"
|
||||
Style="{StaticResource ButtonDanger}">
|
||||
离开频道
|
||||
</Button>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock>仅主播可用</TextBlock>
|
||||
<!--#region 静音开关按钮-->
|
||||
<Button Command="{Binding OperMicoCommand}" Style="{StaticResource WGS_ButtonDock}">
|
||||
<StackPanel>
|
||||
<Grid Width="50">
|
||||
<svgc:SvgViewbox Height="19" Source="Assets/mico_btn.svg" />
|
||||
<Rectangle
|
||||
Width="8"
|
||||
Height="14"
|
||||
Margin="0,-5,0,1"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Fill="#36B282"
|
||||
RadiusX="3"
|
||||
RadiusY="3"
|
||||
RenderTransformOrigin="0.5,1">
|
||||
<Rectangle.RenderTransform>
|
||||
<ScaleTransform>
|
||||
<ScaleTransform.ScaleY>
|
||||
<Binding
|
||||
Converter="{StaticResource VolumeToScaleConverter}"
|
||||
Mode="OneWay"
|
||||
NotifyOnTargetUpdated="True"
|
||||
Path="LocalUser.MicoVolume" />
|
||||
</ScaleTransform.ScaleY>
|
||||
</ScaleTransform>
|
||||
</Rectangle.RenderTransform>
|
||||
</Rectangle>
|
||||
<Line
|
||||
x:Name="SlashLine"
|
||||
Stroke="#FA4E32"
|
||||
StrokeThickness="3"
|
||||
Visibility="{Binding LocalUser.MicoSlashLineVisibility}"
|
||||
X1="35"
|
||||
X2="15"
|
||||
Y1="0"
|
||||
Y2="17" />
|
||||
</Grid>
|
||||
<TextBlock
|
||||
Margin="0,5,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="Black"
|
||||
Text="{Binding LocalUser.MicStatusText}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<!-- 扬声器、麦克风切换按钮 -->
|
||||
<hc:SplitButton
|
||||
x:Name="AudioSelector"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="#4e5461"
|
||||
Style="{StaticResource WGS_SplitButtonDock}">
|
||||
<hc:SplitButton.DropDownContent>
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
Margin="10,10,10,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Style="{StaticResource TextBlockDefaultSecLight}"
|
||||
Text="选择扬声器" />
|
||||
<ItemsControl Margin="10,5,10,0" ItemsSource="{Binding PlaybackDevices}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<RadioButton
|
||||
Margin="5,4,5,2"
|
||||
HorizontalAlignment="Stretch"
|
||||
Content="{Binding DeviceName}"
|
||||
GroupName="PlaybackDeviceGroup"
|
||||
IsChecked="{Binding IsSelected}">
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="Checked">
|
||||
<i:InvokeCommandAction Command="{Binding DataContext.ChangePlaybackDeviceCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}" CommandParameter="{Binding DeviceId}" />
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
</RadioButton>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<hc:Divider Margin="10,6,10,0" />
|
||||
<TextBlock
|
||||
Margin="10,10,10,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Style="{StaticResource TextBlockDefaultSecLight}"
|
||||
Text="选择麦克风" />
|
||||
<ItemsControl Margin="10,5,10,10" ItemsSource="{Binding AudioDevices}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<RadioButton
|
||||
Margin="5,4,5,2"
|
||||
HorizontalAlignment="Stretch"
|
||||
Content="{Binding DeviceName}"
|
||||
GroupName="AudioDeviceGroup"
|
||||
IsChecked="{Binding IsSelected}">
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="Checked">
|
||||
<i:InvokeCommandAction Command="{Binding DataContext.ChangeAudioDeviceCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}" CommandParameter="{Binding DeviceId}" />
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
</RadioButton>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</hc:SplitButton.DropDownContent>
|
||||
</hc:SplitButton>
|
||||
|
||||
<Button
|
||||
Margin="10,0,0,0"
|
||||
Command="{Binding OperVideoCommand}"
|
||||
Style="{StaticResource WGS_ButtonDock}">
|
||||
<StackPanel>
|
||||
<Grid Width="50" Margin="0,4,0,2">
|
||||
<svgc:SvgViewbox Height="15" Source="Assets/video_btn.svg" />
|
||||
<Line
|
||||
Stroke="#FA4E32"
|
||||
StrokeThickness="3"
|
||||
Visibility="{Binding LocalUser.VideoSlashLineVisibility}"
|
||||
X1="35"
|
||||
X2="10"
|
||||
Y1="0"
|
||||
Y2="18" />
|
||||
</Grid>
|
||||
<TextBlock
|
||||
Margin="0,5,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="Black"
|
||||
Text="{Binding LocalUser.VideoStatusText}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
<!--<ScrollViewer
|
||||
Grid.RowSpan="2"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalScrollBarVisibility="Hidden">
|
||||
<StackPanel Margin="0,10,10,10" VerticalAlignment="Top" />
|
||||
</ScrollViewer>
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Background="#242424"
|
||||
Orientation="Horizontal">
|
||||
<hc:TextBox
|
||||
x:Name="uid"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
hc:InfoElement.Placeholder="输入任意UID"
|
||||
IsEnabled="{Binding IsJoin, Converter={StaticResource BoolInverseConverter}}"
|
||||
Text="{Binding UId}" />
|
||||
<Button
|
||||
Height="45"
|
||||
Margin="10"
|
||||
Command="{Binding JoinChannelCommand}"
|
||||
IsEnabled="{Binding IsJoin, Converter={StaticResource BoolInverseConverter}}">
|
||||
加入频道
|
||||
</Button>
|
||||
<Button
|
||||
Height="45"
|
||||
Margin="10"
|
||||
Command="{Binding LeavelChannelCommand}"
|
||||
IsEnabled="{Binding IsJoin}">
|
||||
离开频道
|
||||
</Button>
|
||||
|
||||
|
||||
</StackPanel>
|
||||
<!--<local:VideoView x:Name="videoVIew" />-->
|
||||
</ScrollViewer>-->
|
||||
</Grid>
|
||||
|
||||
</hc:Window>
|
||||
|
|
|
|||
|
|
@ -14,5 +14,10 @@ namespace Meeting.V2.Demo.Views
|
|||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void SplitButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,14 +8,16 @@
|
|||
xmlns:local="clr-namespace:Meeting.V2.Demo.Views"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
x:Name="root"
|
||||
d:DesignHeight="530"
|
||||
d:DesignWidth="960"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
Background="#434549"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
<!--<UserControl.Resources>
|
||||
<cnt:AspectRatioConverter x:Key="AspectRatioConverter" />
|
||||
</UserControl.Resources>
|
||||
</UserControl.Resources>-->
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" MaxHeight="155" />
|
||||
|
|
@ -28,9 +30,16 @@
|
|||
Grid.Row="0"
|
||||
Padding="3"
|
||||
ItemsSource="{Binding UserInfos}">
|
||||
<ItemsControl.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
|
||||
<GradientStop Offset="0" Color="#222426" />
|
||||
<GradientStop Offset="1" Color="#525559" />
|
||||
</LinearGradientBrush>
|
||||
</ItemsControl.Background>
|
||||
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel
|
||||
<VirtualizingStackPanel
|
||||
x:Name="stack_users"
|
||||
HorizontalAlignment="Center"
|
||||
Background="#2D3033"
|
||||
|
|
@ -40,13 +49,13 @@
|
|||
<i:InvokeCommandAction Command="{Binding UserStackPanelSizeChangeCommand}" CommandParameter="{Binding ElementName=stack_users}" />
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>-->
|
||||
</StackPanel>
|
||||
</VirtualizingStackPanel>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<local:VideoView
|
||||
Margin="3"
|
||||
CacheMode="BitmapCache"
|
||||
Command="{Binding DataContext.SelectedUserCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Border}}"
|
||||
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource AncestorType=ContentPresenter}}"
|
||||
UserInfo="{Binding DataContext, RelativeSource={RelativeSource AncestorType=ContentPresenter}}">
|
||||
|
|
@ -56,11 +65,18 @@
|
|||
<Binding ElementName="user_vid" Path="ActualWidth" />
|
||||
<!--<Binding Path="DataContext.UserPageSize" RelativeSource="{RelativeSource AncestorType=UserControl}" />
|
||||
<Binding Path="Margin" RelativeSource="{RelativeSource Mode=Self}" />-->
|
||||
|
||||
</MultiBinding>
|
||||
</local:VideoView.Width>
|
||||
|
||||
|
||||
<!-- 添加数据触发器 -->
|
||||
<local:VideoView.Style>
|
||||
<Style TargetType="local:VideoView">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding DataContext.Id, RelativeSource={RelativeSource AncestorType=ContentPresenter}}" Value="0">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</local:VideoView.Style>
|
||||
</local:VideoView>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
|
|
@ -70,16 +86,7 @@
|
|||
Grid.Row="1"
|
||||
Grid.RowSpan="2"
|
||||
Background="#2D3033">
|
||||
<local:VideoView x:Name="main_video" UserInfo="{Binding DataContext.FocusUser, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Border}}">
|
||||
|
||||
<!--<local:VideoView.Width>
|
||||
<MultiBinding Converter="{StaticResource AspectRatioConverter}">
|
||||
<Binding ElementName="border_main" Path="ActualHeight" />
|
||||
<Binding ElementName="border_main" Path="ActualWidth" />
|
||||
</MultiBinding>
|
||||
</local:VideoView.Width>-->
|
||||
|
||||
</local:VideoView>
|
||||
<local:VideoView x:Name="main_video" UserInfo="{Binding DataContext.FocusUser, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Border}}" />
|
||||
|
||||
</Border>
|
||||
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@
|
|||
x:Name="root"
|
||||
MinWidth="135"
|
||||
MinHeight="75"
|
||||
d:DesignHeight="1080"
|
||||
d:DesignWidth="1920"
|
||||
d:DesignHeight="180"
|
||||
d:DesignWidth="320"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
mc:Ignorable="d">
|
||||
<i:Interaction.Triggers>
|
||||
|
|
@ -26,104 +26,134 @@
|
|||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
|
||||
<UserControl.Resources>
|
||||
<cnt:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<cnt:AspectRatioConverter x:Key="AspectRatioConverter" />
|
||||
<cnt:VolumeToScaleConverter x:Key="VolumeToScaleConverter" />
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid>
|
||||
<!--#region 视频区域 + 右上角按钮-->
|
||||
<intertop:AirspaceDecorator
|
||||
AirspaceMode="Redirect"
|
||||
IsInputRedirectionEnabled="True"
|
||||
IsOutputRedirectionEnabled="True">
|
||||
<WindowsFormsHost x:Name="gridHost">
|
||||
<wfc:PictureBox
|
||||
x:Name="pic_frame"
|
||||
BackColor="#000000"
|
||||
Dock="Fill"
|
||||
MouseDoubleClick="pic_frame_MouseDoubleClick"
|
||||
MouseEnter="pic_frame_MouseEnter"
|
||||
MouseLeave="pic_frame_MouseLeave"
|
||||
SizeMode="StretchImage" />
|
||||
</WindowsFormsHost>
|
||||
</intertop:AirspaceDecorator>
|
||||
<Button
|
||||
x:Name="btn_oper"
|
||||
Margin="0,8,8,0"
|
||||
Padding="5"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
hc:BorderElement.CornerRadius="4"
|
||||
Background="#99727169"
|
||||
BorderBrush="Transparent"
|
||||
Command="{Binding OperClickCommand}"
|
||||
MouseEnter="btn_oper_MouseEnter"
|
||||
MouseLeave="btn_oper_MouseLeave"
|
||||
Visibility="Collapsed">
|
||||
<svgc:SvgViewbox Width="15" Source="Assets/more.svg" />
|
||||
</Button>
|
||||
<!--#endregion-->
|
||||
|
||||
<!--#region 左下角用户名-->
|
||||
<StackPanel
|
||||
Height="22"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Bottom"
|
||||
Orientation="Horizontal">
|
||||
<Border Background="#ff801a" Visibility="{Binding UserInfo.IsManager, Mode=TwoWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<svgc:SvgViewbox
|
||||
x:Name="icon_manager"
|
||||
Margin="3"
|
||||
Source="Assets/manager.svg" />
|
||||
</Border>
|
||||
<Border Background="#8C000000" CornerRadius="0,8,0,0">
|
||||
<DockPanel>
|
||||
<Grid>
|
||||
<!-- 原始的SVG图标 -->
|
||||
<svgc:SvgViewbox
|
||||
x:Name="icon_mic"
|
||||
Margin="3"
|
||||
Source="Assets/mico.svg" />
|
||||
|
||||
<!-- 音量进度条 -->
|
||||
<Rectangle
|
||||
Width="7"
|
||||
Height="12"
|
||||
Margin="0,-4,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Fill="#36B282"
|
||||
RadiusX="3.5"
|
||||
RadiusY="3.5"
|
||||
RenderTransformOrigin="0.5,1">
|
||||
<Rectangle.RenderTransform>
|
||||
<ScaleTransform>
|
||||
<ScaleTransform.ScaleY>
|
||||
<Binding
|
||||
Converter="{StaticResource VolumeToScaleConverter}"
|
||||
Mode="OneWay"
|
||||
NotifyOnTargetUpdated="True"
|
||||
Path="MicrophoneVolume" />
|
||||
</ScaleTransform.ScaleY>
|
||||
</ScaleTransform>
|
||||
</Rectangle.RenderTransform>
|
||||
</Rectangle>
|
||||
|
||||
</Grid>
|
||||
<Border Padding="2" Background="#5E6166">
|
||||
<Grid>
|
||||
<!-- 添加蒙层 -->
|
||||
<Grid
|
||||
x:Name="MaskLayer"
|
||||
Panel.ZIndex="1"
|
||||
Visibility="{Binding UserInfo.IsMuteVideo, Converter={StaticResource Boolean2VisibilityConverter}}">
|
||||
|
||||
<Border
|
||||
Padding="6"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Background="#2B6EC0"
|
||||
CornerRadius="20">
|
||||
<TextBlock
|
||||
x:Name="txt_uname"
|
||||
Margin="5"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="12"
|
||||
FontSize="25"
|
||||
Foreground="White"
|
||||
Text="{Binding UserInfo.UserName, Mode=TwoWay}" />
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
<!--#endregion-->
|
||||
</Grid>
|
||||
Text="{Binding UserInfo.UserName}" />
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
|
||||
<!--#region 视频区域 + 右上角按钮-->
|
||||
<intertop:AirspaceDecorator
|
||||
AirspaceMode="Redirect"
|
||||
IsInputRedirectionEnabled="True"
|
||||
IsOutputRedirectionEnabled="True">
|
||||
<WindowsFormsHost x:Name="gridHost">
|
||||
<wfc:PictureBox
|
||||
x:Name="pic_frame"
|
||||
BackColor="#2D3033"
|
||||
Dock="Fill"
|
||||
MouseDoubleClick="pic_frame_MouseDoubleClick"
|
||||
MouseEnter="pic_frame_MouseEnter"
|
||||
MouseLeave="pic_frame_MouseLeave"
|
||||
SizeMode="Zoom" />
|
||||
</WindowsFormsHost>
|
||||
</intertop:AirspaceDecorator>
|
||||
<!-- 右上角操作按钮 -->
|
||||
<Button
|
||||
x:Name="btn_oper"
|
||||
Margin="0,8,8,0"
|
||||
Padding="5"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Panel.ZIndex="2"
|
||||
hc:BorderElement.CornerRadius="4"
|
||||
Background="#99727169"
|
||||
BorderBrush="Transparent"
|
||||
Command="{Binding OperClickCommand}"
|
||||
MouseEnter="btn_oper_MouseEnter"
|
||||
MouseLeave="btn_oper_MouseLeave"
|
||||
Visibility="Collapsed">
|
||||
<svgc:SvgViewbox Width="15" Source="Assets/more.svg" />
|
||||
</Button>
|
||||
<!--#endregion-->
|
||||
|
||||
<!--#region 左下角用户名-->
|
||||
<StackPanel
|
||||
Height="22"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Bottom"
|
||||
Panel.ZIndex="2"
|
||||
Orientation="Horizontal">
|
||||
<!-- 管理员图标 -->
|
||||
<Border Background="#ff801a" Visibility="{Binding UserInfo.IsManager, Mode=TwoWay, Converter={StaticResource Boolean2VisibilityConverter}}">
|
||||
<svgc:SvgViewbox
|
||||
x:Name="icon_manager"
|
||||
Margin="3"
|
||||
Source="Assets/manager.svg" />
|
||||
</Border>
|
||||
<Border Background="#8C000000" CornerRadius="0,8,0,0">
|
||||
<DockPanel>
|
||||
<Grid Width="18" Height="16">
|
||||
<!-- 麦克风图标 -->
|
||||
<svgc:SvgViewbox x:Name="icon_mic" Source="Assets/mico.svg" />
|
||||
|
||||
<!-- 音量进度条 -->
|
||||
<Rectangle
|
||||
Width="7"
|
||||
Height="12"
|
||||
Margin="-1,-4,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Fill="#36B282"
|
||||
RadiusX="3.5"
|
||||
RadiusY="3.5"
|
||||
RenderTransformOrigin="0.5,1">
|
||||
<Rectangle.RenderTransform>
|
||||
<ScaleTransform>
|
||||
<ScaleTransform.ScaleY>
|
||||
<Binding
|
||||
Converter="{StaticResource VolumeToScaleConverter}"
|
||||
Mode="OneWay"
|
||||
NotifyOnTargetUpdated="True"
|
||||
Path="UserInfo.MicoVolume" />
|
||||
</ScaleTransform.ScaleY>
|
||||
</ScaleTransform>
|
||||
</Rectangle.RenderTransform>
|
||||
</Rectangle>
|
||||
|
||||
<Line
|
||||
x:Name="SlashLine"
|
||||
Stroke="#FA4E32"
|
||||
StrokeThickness="2.5"
|
||||
Visibility="{Binding UserInfo.MicoSlashLineVisibility}"
|
||||
X1="15"
|
||||
X2="1"
|
||||
Y1="1"
|
||||
Y2="16" />
|
||||
|
||||
</Grid>
|
||||
<!-- 用户名 -->
|
||||
<TextBlock
|
||||
x:Name="txt_uname"
|
||||
Margin="5"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="12"
|
||||
Foreground="White"
|
||||
Text="{Binding UserInfo.UserName, Mode=TwoWay}" />
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
<!--#endregion-->
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
|
|
|
|||
|
|
@ -44,47 +44,41 @@ namespace Meeting.V2.Demo.Views
|
|||
|
||||
|
||||
#region 控制右上角操作按钮显示与消失
|
||||
/// <summary>
|
||||
/// 控制操作按钮显示
|
||||
/// </summary>
|
||||
private bool IsOperVisible = false;
|
||||
|
||||
private bool isMouseInPicFrame = false;
|
||||
private bool isMouseInBtnOper = false;
|
||||
|
||||
private void pic_frame_MouseEnter(object sender, EventArgs e)
|
||||
{
|
||||
btn_oper.Visibility = Visibility.Visible;
|
||||
|
||||
isMouseInPicFrame = true;
|
||||
btn_oper.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private async void pic_frame_MouseLeave(object sender, EventArgs e)
|
||||
private void pic_frame_MouseLeave(object sender, EventArgs e)
|
||||
{
|
||||
|
||||
// 使用ConfigureAwait(false)可以避免不必要的线程上下文切换
|
||||
await Task.Delay(50).ConfigureAwait(false);
|
||||
|
||||
// 如果需要操作UI,使用Dispatcher
|
||||
await Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
isMouseInPicFrame = false;
|
||||
// 如果鼠标既不在大控件也不在小控件内,则隐藏小控件
|
||||
if (!isMouseInBtnOper)
|
||||
{
|
||||
// UI相关操作
|
||||
if (!IsOperVisible)
|
||||
{
|
||||
btn_oper.Visibility = Visibility.Hidden;
|
||||
}
|
||||
});
|
||||
|
||||
btn_oper.Visibility = Visibility.Hidden;
|
||||
}
|
||||
}
|
||||
|
||||
private void btn_oper_MouseEnter(object sender, MouseEventArgs e)
|
||||
{
|
||||
IsOperVisible = true;
|
||||
isMouseInBtnOper = true;
|
||||
|
||||
//IsOperVisible = true;
|
||||
}
|
||||
|
||||
private async void btn_oper_MouseLeave(object sender, MouseEventArgs e)
|
||||
private void btn_oper_MouseLeave(object sender, MouseEventArgs e)
|
||||
{
|
||||
//await Task.Delay(50).ConfigureAwait(false);
|
||||
|
||||
|
||||
IsOperVisible = false;
|
||||
|
||||
isMouseInBtnOper = false;
|
||||
// 如果鼠标既不在大控件也不在小控件内,则隐藏小控件
|
||||
if (!isMouseInPicFrame)
|
||||
{
|
||||
btn_oper.Visibility = Visibility.Hidden;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
|
|||
Loading…
Reference in New Issue