750 lines
30 KiB
C#
750 lines
30 KiB
C#
using JianGongYun.TRTC.Components;
|
||
using JianGongYun.TRTC.Models;
|
||
using JianGongYun.TRTC.Utils;
|
||
using JianGongYun.TRTC.ViewModels;
|
||
using JianGongYun.TRTC.Windows;
|
||
using ManageLiteAV;
|
||
using OpenCvSharp;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Text;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using System.Windows;
|
||
using System.Windows.Controls;
|
||
using Window = System.Windows.Window;
|
||
using System.Collections.Concurrent;
|
||
using System.Windows.Data;
|
||
using System.Drawing;
|
||
using System.Diagnostics;
|
||
using System.Runtime.InteropServices;
|
||
using AduSkin.Controls.Metro;
|
||
|
||
namespace JianGongYun.TRTC
|
||
{
|
||
/// <summary>
|
||
/// 直播调用对象全放这里
|
||
/// </summary>
|
||
public static class LiveClassroom
|
||
{
|
||
const uint SDKAppID = 1400463444;
|
||
const string SDKAppKEY = "6ee2586282eb8ab5bff3f917b44500c4ffd9bbd3d820258b1fa8cdd470cfd1ee";
|
||
const uint LIVEAppID = 1252883401;
|
||
const uint LIVEBizid = 126866;
|
||
/// <summary>
|
||
/// TRTC实例
|
||
/// </summary>
|
||
public static ITRTCCloud lTRTCCloud;
|
||
/// <summary>
|
||
/// 设备管理器
|
||
/// </summary>
|
||
public static ITXDeviceManager lTXDeviceManager;
|
||
/// <summary>
|
||
/// live窗口的调用窗口
|
||
/// </summary>
|
||
public static Window CallerWindow { get; private set; }
|
||
/// <summary>
|
||
/// 直播窗口
|
||
/// </summary>
|
||
public static Window CurrentLiveWindow;
|
||
private static LiveWindowViewModel liveWinMode;
|
||
public static ClassroomEntity CurrentClassroomEntity { get; private set; }
|
||
public static TRTCCloudCallback TRTCCloudCallback = new TRTCCloudCallback();
|
||
/// <summary>
|
||
/// 摄像头帧
|
||
/// </summary>
|
||
public static Mat MainFrame = new Mat();
|
||
/// <summary>
|
||
/// 屏幕帧
|
||
/// </summary>
|
||
public static Mat SubFrame = new Mat();
|
||
/// <summary>
|
||
/// 背景帧
|
||
/// </summary>
|
||
public static Mat BackgroundFrame = null;
|
||
//public static int UserCount = 999;
|
||
/// <summary>
|
||
/// 进入直播教室
|
||
/// </summary>
|
||
/// <param name="callerWindow">调用窗口</param>
|
||
/// <param name="classroomEntity">教室数据</param>
|
||
public static void EnterTheClassroom(Window callerWindow, ClassroomEntity classroomEntity)
|
||
{
|
||
if (CurrentLiveWindow != null)
|
||
{
|
||
_ = AduSkin.Controls.Metro.AduMessageBox.Show("已经进入教室", "提醒");
|
||
CurrentLiveWindow.Topmost = true;
|
||
return;
|
||
}
|
||
CallerWindow = callerWindow;
|
||
CurrentClassroomEntity = classroomEntity;
|
||
CurrentLiveWindow = new LiveWindow();
|
||
liveWinMode = CurrentLiveWindow.DataContext as LiveWindowViewModel;
|
||
CurrentLiveWindow.Closed += CurrentLiveWindow_Closed;
|
||
|
||
lTRTCCloud = ITRTCCloud.getTRTCShareInstance();//创建TRTC实例
|
||
lTRTCCloud.addCallback(TRTCCloudCallback);//注册回调
|
||
var roomPars = new TRTCParams
|
||
{
|
||
sdkAppId = SDKAppID,
|
||
userId = CurrentClassroomEntity.TeacherId,
|
||
userSig = GenTestUserSig(CurrentClassroomEntity.TeacherId),
|
||
roomId = 0,//使用strRoomId
|
||
strRoomId = CurrentClassroomEntity.TeacherId,
|
||
role = TRTCRoleType.TRTCRoleAnchor,
|
||
streamId = CurrentClassroomEntity.TeacherId,//旁路CDN流名字为教师id
|
||
userDefineRecordId= CurrentClassroomEntity.TeacherId//指定id录制教师
|
||
};
|
||
lTRTCCloud.enterRoom(ref roomPars, TRTCAppScene.TRTCAppSceneLIVE);//创建房间
|
||
|
||
//设备
|
||
//var settingViewMode = ViewModels.SettingWindowViewModel.GetInstance();
|
||
lTXDeviceManager = lTRTCCloud.getDeviceManager();
|
||
var currentMic = settingWindowViewModel.CurrentMic;//麦克风
|
||
if (!string.IsNullOrEmpty(currentMic))
|
||
{
|
||
var res = lTXDeviceManager.setCurrentDevice(TRTCDeviceType.TXMediaDeviceTypeMic, currentMic);
|
||
}
|
||
lTXDeviceManager.setCurrentDeviceVolume(TRTCDeviceType.TXMediaDeviceTypeMic, settingWindowViewModel.MicVolume);//麦克风采集音量
|
||
lTRTCCloud.setSystemAudioLoopbackVolume(settingWindowViewModel.SytemGatherVolume);//系统声音采集音量
|
||
if (settingWindowViewModel.AudioSource == "2")
|
||
{
|
||
lTRTCCloud.startSystemAudioLoopback(null);
|
||
}
|
||
else
|
||
{
|
||
lTRTCCloud.stopSystemAudioLoopback();
|
||
}
|
||
//设备完结
|
||
|
||
//liveWinMode.LoadAllScreen();
|
||
_RecoderDir = $"{classroomEntity.ClassHead}_{classroomEntity.ClassSubHead}";
|
||
|
||
|
||
callerWindow.Hide();//隐藏调用窗口
|
||
CurrentLiveWindow.Show();
|
||
}
|
||
|
||
private static string _RecoderDir;
|
||
|
||
/// <summary>
|
||
/// 音频录制文件
|
||
/// </summary>
|
||
public static string AudioTempPath
|
||
{
|
||
get
|
||
{
|
||
return Path.Combine(RecoderDir, $"temp_audio.aac");
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 视频录制文件
|
||
/// </summary>
|
||
public static string VideoTempPath
|
||
{
|
||
get
|
||
{
|
||
return Path.Combine(RecoderDir, $"temp_video.avi");
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 合并后的文件
|
||
/// </summary>
|
||
public static string ResultFilePath
|
||
{
|
||
get
|
||
{
|
||
return Path.Combine(RecoderDir, $"result.avi");
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 录制内容的具体路径
|
||
/// </summary>
|
||
public static string RecoderDir
|
||
{
|
||
get
|
||
{
|
||
var temp = Path.Combine(settingWindowViewModel.ScreenRecordingDir, _RecoderDir);
|
||
if (!Directory.Exists(temp))
|
||
{
|
||
Directory.CreateDirectory(temp);
|
||
}
|
||
return temp;
|
||
}
|
||
}
|
||
|
||
private static SettingWindowViewModel settingWindowViewModel = SettingWindowViewModel.GetInstance();
|
||
|
||
|
||
/// <summary>
|
||
/// 视频画面
|
||
/// </summary>
|
||
public static Dictionary<string, TXLiteAVVideoView> VideoViews = new Dictionary<string, TXLiteAVVideoView>();
|
||
/// <summary>
|
||
/// 启动摄像头
|
||
/// </summary>
|
||
/// <param name="parent">视频容器</param>
|
||
public static TXLiteAVVideoView StartVideoMain(Panel parent)
|
||
{
|
||
if (!liveWinMode.CameraRunning)
|
||
{
|
||
var encParams = settingWindowViewModel.MainEncParams;
|
||
var qosParams = settingWindowViewModel.MainQosParams;
|
||
var renderParams = settingWindowViewModel.MainRenderParams;
|
||
lTRTCCloud.setVideoEncoderParam(ref encParams);
|
||
lTRTCCloud.setNetworkQosParam(ref qosParams);
|
||
lTRTCCloud.setLocalRenderParams(ref renderParams);
|
||
lTRTCCloud.startLocalPreview(IntPtr.Zero);
|
||
liveWinMode.CameraRunning = true;
|
||
var view = AddCustomVideoView(parent, CurrentClassroomEntity.TeacherId, TRTCVideoStreamType.TRTCVideoStreamTypeBig, true);
|
||
if (liveWinMode.IsLive)
|
||
{
|
||
view.OnRenderVideoFrameHandler += (data, w, h) =>
|
||
{
|
||
lock (MainFrame)
|
||
{
|
||
if (MainFrame.Cols != w || MainFrame.Rows != h)
|
||
{
|
||
MainFrame.Create(h, w, MatType.CV_8UC4);
|
||
}
|
||
Marshal.Copy(data, 0, MainFrame.Data, data.Length);
|
||
}
|
||
};
|
||
}
|
||
return view;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 停止摄像头
|
||
/// </summary>
|
||
/// <param name="parent"></param>
|
||
public static void StopVideoMain(Panel parent)
|
||
{
|
||
if (liveWinMode.CameraRunning)
|
||
{
|
||
liveWinMode.CameraRunning = false;
|
||
lTRTCCloud.stopLocalPreview();
|
||
RemoveCustomVideoView(parent, CurrentClassroomEntity.TeacherId, TRTCVideoStreamType.TRTCVideoStreamTypeBig, true);
|
||
lock (MainFrame)
|
||
{
|
||
MainFrame.Create(1, 1, MatType.CV_8UC4);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 启动屏幕分享
|
||
/// </summary>
|
||
/// <param name="parent"></param>
|
||
public static TXLiteAVVideoView StartVideoSub(Panel parent)
|
||
{
|
||
if (!liveWinMode.ScreenRunning)
|
||
{
|
||
liveWinMode.ScreenRunning = true;
|
||
SelectVieoSub();
|
||
lTRTCCloud.startScreenCapture(IntPtr.Zero, TRTCVideoStreamType.TRTCVideoStreamTypeSub, settingWindowViewModel.SubEncParams);
|
||
var view = AddCustomVideoView(parent, CurrentClassroomEntity.TeacherId, TRTCVideoStreamType.TRTCVideoStreamTypeSub, true);
|
||
if (liveWinMode.IsLive)
|
||
{
|
||
view.OnRenderVideoFrameHandler += (data, w, h) =>
|
||
{
|
||
lock (SubFrame)
|
||
{
|
||
if (SubFrame.Cols != w || SubFrame.Rows != h)
|
||
{
|
||
SubFrame.Create(h, w, MatType.CV_8UC4);
|
||
}
|
||
Marshal.Copy(data, 0, SubFrame.Data, data.Length);
|
||
}
|
||
};
|
||
}
|
||
return view;
|
||
}
|
||
return null;
|
||
}
|
||
/// <summary>
|
||
/// 停止屏幕分享
|
||
/// </summary>
|
||
/// <param name="parent"></param>
|
||
public static void StopVideoSub(Panel parent)
|
||
{
|
||
if (liveWinMode.ScreenRunning)
|
||
{
|
||
liveWinMode.ScreenRunning = false;
|
||
lTRTCCloud.stopScreenCapture();
|
||
RemoveCustomVideoView(parent, CurrentClassroomEntity.TeacherId, TRTCVideoStreamType.TRTCVideoStreamTypeSub, true);
|
||
lock (SubFrame)
|
||
{
|
||
SubFrame.Create(1, 1, MatType.CV_8UC4);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 选择要分享的屏幕
|
||
/// </summary>
|
||
public static void SelectVieoSub()
|
||
{
|
||
var current = liveWinMode.CurrentShareScreen;
|
||
var rect = new RECT();
|
||
var property = new TRTCScreenCaptureProperty();
|
||
lTRTCCloud.selectScreenCaptureTarget(ref current, ref rect, ref property);
|
||
}
|
||
|
||
//开启音频后调用
|
||
public static void StartMUX(bool stop = false)
|
||
{
|
||
if (stop)
|
||
{
|
||
lTRTCCloud.setMixTranscodingConfig(null);
|
||
return;
|
||
}
|
||
var resolution = settingWindowViewModel.SubEncParams.videoResolution.ToString().Split('_');//屏幕分辨率,本地储存分辨率以屏幕分享分辨率为基准
|
||
lTRTCCloud.setMixTranscodingConfig(new TRTCTranscodingConfig //设置混流
|
||
{
|
||
mode = TRTCTranscodingConfigMode.TRTCTranscodingConfigMode_Template_ScreenSharing,
|
||
appId = LIVEAppID,
|
||
bizId = LIVEBizid,
|
||
videoWidth = uint.Parse(resolution[1]),
|
||
videoHeight = uint.Parse(resolution[2]),
|
||
videoBitrate = settingWindowViewModel.SubEncParams.videoBitrate,
|
||
videoFramerate = settingWindowViewModel.SubEncParams.videoFps,
|
||
videoGOP = 3,
|
||
backgroundColor = 0x202020,
|
||
audioSampleRate = 48000,
|
||
audioBitrate = 128,
|
||
audioChannels = 2
|
||
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 启动麦克风
|
||
/// </summary>
|
||
public static void StartMic()
|
||
{
|
||
if (!liveWinMode.MicRunning)
|
||
{
|
||
liveWinMode.MicRunning = true;
|
||
lTRTCCloud.startLocalAudio(settingWindowViewModel.LiveAudioLevel);
|
||
StartRecordAudio();
|
||
|
||
StartMUX();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 录音
|
||
/// </summary>
|
||
public static void StartRecordAudio()
|
||
{
|
||
if (liveWinMode.IsLive && !liveWinMode.AudioRecordRunning)
|
||
{
|
||
liveWinMode.AudioRecordRunning = true;
|
||
var pars = new TRTCAudioRecordingParams { filePath = AudioTempPath };
|
||
lTRTCCloud.startAudioRecording(ref pars);
|
||
}
|
||
}
|
||
public static void StopRecordAudio()
|
||
{
|
||
if (liveWinMode.AudioRecordRunning)
|
||
{
|
||
liveWinMode.AudioRecordRunning = false;
|
||
lTRTCCloud.stopAudioRecording();
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 关闭麦克风
|
||
/// </summary>
|
||
public static void StopMic()
|
||
{
|
||
if (liveWinMode.MicRunning)
|
||
{
|
||
liveWinMode.MicRunning = false;
|
||
StopRecordAudio();
|
||
lTRTCCloud.stopLocalAudio();
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 设置麦克风静音
|
||
/// </summary>
|
||
public static void SetMicMute(bool? mute = true)
|
||
{
|
||
if (liveWinMode.MicRunning)
|
||
{
|
||
liveWinMode.MicMute = mute.HasValue ? mute.Value : !liveWinMode.MicMute;
|
||
lTRTCCloud.muteLocalAudio(liveWinMode.MicMute);
|
||
}
|
||
}
|
||
|
||
|
||
public static void VideoRecordTask(ref Action onEnd)
|
||
{
|
||
var backColor = Scalar.FromRgb(0x20, 0x20, 0x20);
|
||
var resolution = settingWindowViewModel.SubEncParams.videoResolution.ToString().Split('_');//屏幕分辨率,本地储存分辨率以屏幕分享分辨率为基准
|
||
var fps = settingWindowViewModel.LiveFps;//视频采集的fps
|
||
BackgroundFrame = new Mat(int.Parse(resolution[2]), int.Parse(resolution[1]), MatType.CV_8UC4, backColor);//合成双路视频的背景
|
||
var delay = 1000 / (int)fps;//每帧时间
|
||
var _recoderDir = RecoderDir;
|
||
//var videoFrameTemp = Path.Combine(_recoderDir, $"videoFrameTemp.bmp");
|
||
var backHeight = BackgroundFrame.Rows;//画面高度
|
||
var backWidth = BackgroundFrame.Cols;//画面宽度
|
||
|
||
|
||
var end = false;
|
||
|
||
|
||
ConcurrentQueue<Mat> mats = new ConcurrentQueue<Mat>();
|
||
|
||
int runFps = 0;//实时帧
|
||
Timer timer = new Timer((a) =>
|
||
{
|
||
//Debug.Print($"runFps {runFps}. leavingsMat {mats.Count}.");
|
||
Interlocked.Exchange(ref runFps, 0);
|
||
}, null, 0, 1000);
|
||
|
||
onEnd = () =>
|
||
{
|
||
end = true;
|
||
timer.Dispose();
|
||
};
|
||
|
||
//帧合并线程
|
||
Task.Factory.StartNew(() =>
|
||
{
|
||
|
||
Stopwatch stopwatch = new Stopwatch();//计时器
|
||
bool onlyCameraInit = false;//只有摄像头的话背景填充为backColor,变量标记只需设置一次
|
||
bool noImgInit = false;//没有画面背景也填充backColor,变量标记只需设置一次
|
||
|
||
|
||
var delayEqualize = 0;//每帧时间补偿,在性能和其他因素影响下delay的时间不一定充足
|
||
|
||
//屏幕分享画面
|
||
var screenRoi = BackgroundFrame[new OpenCvSharp.Rect(0, 0, backWidth, backHeight)];
|
||
|
||
//摄像头小画面定位
|
||
var cameraMargin = 20;//右下角偏移量
|
||
var smallCameraSize = 150;
|
||
var smallX = backWidth - smallCameraSize - cameraMargin;
|
||
var smallY = backHeight - smallCameraSize - cameraMargin;
|
||
var smallRoi = BackgroundFrame[new OpenCvSharp.Rect(smallX, smallY, smallCameraSize, smallCameraSize)];
|
||
|
||
//摄像头大画面位置
|
||
var bigRoi = BackgroundFrame[new OpenCvSharp.Rect((backWidth - backHeight) / 2, 0, backHeight, backHeight)];
|
||
|
||
|
||
|
||
|
||
Debug.Print("开始处理帧线程");
|
||
while (!end)
|
||
{
|
||
stopwatch.Restart();
|
||
|
||
if (!liveWinMode.ScreenRunning && !liveWinMode.CameraRunning)//有画面的中途关闭画面,清空上一帧
|
||
{
|
||
if (!noImgInit)
|
||
{
|
||
noImgInit = true;
|
||
BackgroundFrame.SetTo(backColor);
|
||
}
|
||
goto Skip2;
|
||
}
|
||
else
|
||
{
|
||
if (noImgInit)
|
||
{
|
||
noImgInit = false;
|
||
}
|
||
}
|
||
|
||
if (liveWinMode.ScreenRunning)//屏幕分享中
|
||
{
|
||
if (onlyCameraInit)
|
||
{
|
||
onlyCameraInit = false;
|
||
}
|
||
lock (SubFrame)
|
||
{
|
||
if (SubFrame.Cols != BackgroundFrame.Cols || SubFrame.Rows != BackgroundFrame.Rows)
|
||
{
|
||
goto Skip1;//图像数据不正常直接跳过
|
||
}
|
||
SubFrame.CopyTo(screenRoi);//屏幕分享和背景一样大,直接覆盖上去
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (!onlyCameraInit)//双画面的中途关闭屏幕画面,清空上一帧
|
||
{
|
||
onlyCameraInit = true;
|
||
BackgroundFrame.SetTo(backColor);
|
||
}
|
||
}
|
||
|
||
Skip1:
|
||
|
||
if (liveWinMode.CameraRunning)//摄像头分享中
|
||
{
|
||
lock (MainFrame)
|
||
{
|
||
if (MainFrame.Cols <= 0 || MainFrame.Rows <= 0)
|
||
{
|
||
goto Skip2;//图像数据不正常直接跳过
|
||
}
|
||
var mainHeight = MainFrame.Rows;//摄像头画面高度
|
||
var mainWidth = MainFrame.Cols;//摄像头画面宽度
|
||
var dst = MainFrame[new OpenCvSharp.Rect((mainWidth - mainHeight) / 2, 0, mainHeight, mainHeight)];//采集大小为原图高的居中正方形,程序分辨率只实现了横屏,因此宽大于高
|
||
if (liveWinMode.ScreenRunning)//屏幕分享中摄像头150正方形大小并悬浮再右下角
|
||
{
|
||
dst = dst.Resize(new OpenCvSharp.Size(smallCameraSize, smallCameraSize), interpolation: InterpolationFlags.Area);
|
||
dst.Rectangle(new OpenCvSharp.Rect(0, 0, dst.Cols, dst.Rows), Scalar.FromRgb(239, 239, 239), 1);
|
||
dst.CopyTo(smallRoi);
|
||
}
|
||
else//只有摄像头,摄像头画面全屏正方形
|
||
{
|
||
dst = dst.Resize(new OpenCvSharp.Size(backHeight, backHeight), interpolation: InterpolationFlags.Linear);
|
||
dst.CopyTo(bigRoi);
|
||
}
|
||
dst.Dispose();
|
||
}
|
||
}
|
||
|
||
Skip2:
|
||
|
||
mats.Enqueue(BackgroundFrame.CvtColor(ColorConversionCodes.BGRA2BGR));
|
||
Interlocked.Increment(ref runFps);
|
||
stopwatch.Stop();
|
||
var sleep = delay - (int)stopwatch.ElapsedMilliseconds - 1;//每帧时间减去每帧处理时间为sleep时间//视频实际时长偏短,再减1是微调
|
||
if (sleep > 0)
|
||
{
|
||
if (delayEqualize == 0)
|
||
{
|
||
Thread.Sleep(sleep);
|
||
continue;
|
||
}
|
||
else
|
||
{
|
||
delayEqualize += sleep;
|
||
if (delayEqualize > 0)
|
||
{
|
||
Thread.Sleep(delayEqualize);
|
||
delayEqualize = 0;
|
||
continue;
|
||
}
|
||
}
|
||
}
|
||
else if (sleep < 0)//如果处理时间超过了每帧时间,记录下来
|
||
{
|
||
delayEqualize += sleep;
|
||
}
|
||
//Debug.Print($"video frame run {stopwatch.ElapsedMilliseconds}. sleep{sleep}. delayEqualize {delayEqualize}. delay {delay}.");
|
||
}
|
||
BackgroundFrame?.Dispose();
|
||
BackgroundFrame = null;
|
||
Debug.Print("结束处理帧线程");
|
||
}, TaskCreationOptions.LongRunning);//新开线程
|
||
|
||
//新开线程写文件,减少帧处理时间
|
||
Task.Factory.StartNew(() =>
|
||
{
|
||
VideoWriter vw = new VideoWriter(VideoTempPath, FourCC.H264, fps, BackgroundFrame.Size());
|
||
//Stopwatch stopwatch = new Stopwatch();//计时器
|
||
Debug.Print("开始写入视频线程");
|
||
while (!end || mats.Count > 0)
|
||
{
|
||
if (mats.Count > 0)
|
||
{
|
||
if (mats.TryDequeue(out var frame))
|
||
{
|
||
//stopwatch.Restart();
|
||
vw.Write(frame);
|
||
frame.Dispose();
|
||
//stopwatch.Stop();
|
||
//Debug.Print($"video frame write {stopwatch.ElapsedMilliseconds}");
|
||
}
|
||
else
|
||
{
|
||
Thread.Sleep(delay);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Thread.Sleep(delay);
|
||
}
|
||
}
|
||
vw.Dispose();
|
||
//合并视频
|
||
var arguments = $@"-i ""{VideoTempPath}"" -i ""{AudioTempPath}"" -c:v copy -c:a copy -strict experimental -y ""{ResultFilePath}""";
|
||
Process prc = new Process { StartInfo = new ProcessStartInfo { FileName = Path.Combine(Environment.CurrentDirectory, "ffmpeg.exe"), Arguments = arguments, CreateNoWindow = true, UseShellExecute = false } };
|
||
prc.Start();
|
||
prc.WaitForExit();
|
||
CurrentLiveWindow.Dispatcher.Invoke(new Action(() =>
|
||
{
|
||
CurrentLiveWindow.Close();
|
||
NoticeManager.NotifiactionShow.AddNotifiaction(new NotifiactionModel()
|
||
{
|
||
Title = "提醒",
|
||
Content = $"成功保存直播视频,路径:{ResultFilePath}。",
|
||
NotifiactionType = AduSkin.Controls.EnumPromptType.Success
|
||
});
|
||
var res = AduMessageBox.ShowYesNoCancel("是否删除音视频源文件?", "提醒", "删除", "保留", "浏览");
|
||
if (res == MessageBoxResult.Cancel)
|
||
{
|
||
var process = new Process
|
||
{
|
||
StartInfo = new ProcessStartInfo { FileName = "explorer.exe", Arguments = RecoderDir }
|
||
};
|
||
process.Start();
|
||
process.WaitForExit();
|
||
}
|
||
else if (res == MessageBoxResult.Yes)
|
||
{
|
||
if (File.Exists(VideoTempPath))
|
||
{
|
||
File.Delete(VideoTempPath);
|
||
}
|
||
if (File.Exists(AudioTempPath))
|
||
{
|
||
File.Delete(AudioTempPath);
|
||
}
|
||
}
|
||
}));
|
||
Debug.Print("结束写入视频线程");
|
||
}, TaskCreationOptions.LongRunning);
|
||
|
||
}
|
||
|
||
/// <summary>
|
||
/// 添加自定义渲染 View 并绑定渲染回调
|
||
/// </summary>
|
||
private static TXLiteAVVideoView AddCustomVideoView(Panel parent, string userId, TRTCVideoStreamType streamType, bool local = false)
|
||
{
|
||
TXLiteAVVideoView videoView = new TXLiteAVVideoView();
|
||
videoView.RegEngine(userId, streamType, lTRTCCloud, local);
|
||
videoView.SetRenderMode(streamType == TRTCVideoStreamType.TRTCVideoStreamTypeBig ? settingWindowViewModel.MainRenderParams.fillMode : settingWindowViewModel.SubRenderParams.fillMode);
|
||
//videoView.Width = parent.ActualWidth;
|
||
videoView.SetBinding(TXLiteAVVideoView.WidthProperty, new Binding("ActualWidth") { Source = parent });
|
||
//videoView.Height = parent.ActualHeight;
|
||
videoView.SetBinding(TXLiteAVVideoView.HeightProperty, new Binding("ActualHeight") { Source = parent });
|
||
parent.Dispatcher.Invoke(new Action(() =>
|
||
{
|
||
parent.Children.Add(videoView);
|
||
}));
|
||
string key = string.Format("{0}_{1}", userId, streamType);
|
||
VideoViews.Add(key, videoView);
|
||
return videoView;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取屏幕列表需要暂停屏幕分享渲染
|
||
/// </summary>
|
||
/// <param name="action"></param>
|
||
public static void WillGetScreens(Action action)
|
||
{
|
||
if (liveWinMode.ScreenRunning && VideoViews.TryGetValue($"{CurrentClassroomEntity.TeacherId}_{TRTCVideoStreamType.TRTCVideoStreamTypeSub}", out var view))
|
||
{
|
||
view.SetPause(true);
|
||
action.Invoke();
|
||
view.SetPause(false);
|
||
}
|
||
else
|
||
{
|
||
action.Invoke();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 移除自定义渲染 View 并解绑渲染回调
|
||
/// </summary>
|
||
private static void RemoveCustomVideoView(Panel parent, string userId, TRTCVideoStreamType streamType, bool local = false)
|
||
{
|
||
TXLiteAVVideoView videoView = null;
|
||
string key = string.Format("{0}_{1}", userId, streamType);
|
||
if (VideoViews.TryGetValue(key, out videoView))
|
||
{
|
||
videoView.RemoveEngine(lTRTCCloud);
|
||
parent.Dispatcher.Invoke(new Action(() =>
|
||
{
|
||
parent.Children.Remove(videoView);
|
||
}));
|
||
VideoViews.Remove(key);
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 重启音频录制
|
||
/// </summary>
|
||
//public static void RestartAudio() { }
|
||
/// <summary>
|
||
/// 重设主视频(摄像头)
|
||
/// </summary>
|
||
//public static void ResetVideoMain() { }
|
||
/// <summary>
|
||
/// 重设副视频(录屏)
|
||
/// </summary>
|
||
//public static void ResetVideoSub() { }
|
||
|
||
|
||
/// <summary>
|
||
/// 停止所有实时渲染
|
||
/// </summary>
|
||
/// <param name="pause"></param>
|
||
public static void PauseAllView(bool pause)
|
||
{
|
||
foreach (var item in VideoViews)
|
||
{
|
||
item.Value.SetPause(pause);
|
||
}
|
||
}
|
||
|
||
private static void CurrentLiveWindow_Closed(object sender, EventArgs e)
|
||
{
|
||
CurrentLiveWindow = null;
|
||
CurrentClassroomEntity = null;
|
||
CallerWindow.Show();//还原调用者窗口
|
||
|
||
lTXDeviceManager.Dispose();
|
||
lTXDeviceManager = null;
|
||
lTRTCCloud.exitRoom();
|
||
lTRTCCloud.removeCallback(TRTCCloudCallback);//注册回调
|
||
ITRTCCloud.destroyTRTCShareInstance();//销毁TRTC实例
|
||
lTRTCCloud.Dispose();
|
||
lTRTCCloud = null;
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 计算 UserSig 签名
|
||
///
|
||
/// 函数内部使用 HMAC-SHA256 非对称加密算法,对 SDKAPPID、userId 和 EXPIRETIME 进行加密
|
||
///
|
||
/// 该方案仅适合本地跑通demo和功能调试,产品真正上线发布,要使用服务器获取方案避免私钥被破解。
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// 请不要将如下代码发布到您的线上正式版本的 App 中,原因如下:
|
||
///
|
||
/// 本文件中的代码虽然能够正确计算出 UserSig,但仅适合快速调通 SDK 的基本功能,不适合线上产品,
|
||
/// 这是因为客户端代码中的 SECRETKEY 很容易被反编译逆向破解,尤其是 Web 端的代码被破解的难度几乎为零。
|
||
/// 一旦您的密钥泄露,攻击者就可以计算出正确的 UserSig 来盗用您的腾讯云流量。
|
||
///
|
||
/// 正确的做法是将 UserSig 的计算代码和加密密钥放在您的业务服务器上,然后由 App 按需向您的服务器获取实时算出的 UserSig。
|
||
/// 由于破解服务器的成本要高于破解客户端 App,所以服务器计算的方案能够更好地保护您的加密密钥。
|
||
///
|
||
/// 文档:https://cloud.tencent.com/document/product/647/17275#GetFromServer
|
||
/// </remarks>
|
||
public static string GenTestUserSig(string userId)
|
||
{
|
||
if (SDKAppID == 0 || string.IsNullOrEmpty(SDKAppKEY)) return null;
|
||
TLSSigAPIv2 api = new TLSSigAPIv2((int)SDKAppID, SDKAppKEY);
|
||
// 统一转换为UTF8,SDK内部是用UTF8编码。
|
||
return api.GenSig(Util.UTF16To8(userId));
|
||
}
|
||
}
|
||
}
|