2026-02-05 14:48:49 +08:00

601 lines
23 KiB
C#
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using AiSportsMicrospaceDB.DBContext;
using Common;
using Dto;
using Enum;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using SportExpand;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
using Wpf_AiSportsMicrospace.Common;
using Wpf_AiSportsMicrospace.Dto;
using Wpf_AiSportsMicrospace.Enum;
using Wpf_AiSportsMicrospace.MyUserControl;
using Wpf_AiSportsMicrospace.Views;
using WpfAnimatedGif;
using Yztob.AiSports.Inferences.Things;
using Yztob.AiSports.Postures;
using Yztob.AiSports.Postures.Sports;
using Yztob.AiSports.Sensors.Things;
namespace Wpf_AiSportsMicrospace.Views.JumpRope
{
/// <summary>
/// MusicJumpRope.xaml 的交互逻辑
/// </summary>
public partial class MusicJumpRope : UserControl
{
private Main _mainWin => Application.Current.MainWindow as Main;
private MediaPlayer _mediaPlayer = new MediaPlayer();
private readonly object _updateLock = new object();
private readonly Dictionary<int, (string lastNumber, DateTime lastChangeTime, string currentState)> _jumpStatus = new Dictionary<int, (string, DateTime, string)>();
private MusicJumpRopeContext _musicJumpRopeContext;
private GameState _currentGameState = GameState.NotStarted;
private readonly HttpClient _httpClient;
SportOperate sportOperate = new SportOperate();
// 容忍时间(节拍误差)
public double _beatTolerance = 0.24; // ±150ms
// 滚动显示的节拍点集合
public ObservableCollection<BeatItem> BeatDisplayLeft { get; set; } = new();
public ObservableCollection<BeatItem> BeatDisplayRight { get; set; } = new();
public MusicJumpRope()
{
InitializeComponent();
Loaded += UserControl_Loaded;
Unloaded += UserControl_Unloaded;
_musicJumpRopeContext = new MusicJumpRopeContext();
}
private async void UserControl_Loaded(object sender, RoutedEventArgs e)
{
DrawCirclesWithText();
// 播放音乐
PlayMusic("raisehand.mp3");
PkBar.LeftProgress = 0.5;
popSilder1.MusicBeats = _musicJumpRopeContext.MusicBeats["1"];
popSilder2.MusicBeats = _musicJumpRopeContext.MusicBeats["1"];
var app = (App)Application.Current;
using var scope = App.AppHost.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var cfg = await db.GroupPoints.FirstOrDefaultAsync(x => x.Code == AppSettings.Code);
//var pointlist = JsonSerializer.Deserialize<List<(double, double)>>(cfg!.PointsJson);
List<(double XNorm, double YNorm)> pointlist = [(cfg.ControlPoint.X, cfg.ControlPoint.Y), (cfg.Point3.X, cfg.Point3.Y)];
_musicJumpRopeContext.UpdateCirclePositions(pointlist);
_mainWin.HumanFrameUpdated += OnHumanFrameUpdated;
}
private void UserControl_Unloaded(object sender, RoutedEventArgs e)
{
_mainWin.HumanFrameUpdated -= OnHumanFrameUpdated;
}
private void PlayMusic(string musicFileName)
{
// 获取项目根目录
string projectRoot = System.IO.Path.Combine(AppContext.BaseDirectory, @"..\..\..");
string musicPath = System.IO.Path.Combine(projectRoot, "Resources", "Music", musicFileName);
string imgPath = System.IO.Path.Combine(projectRoot, "Resources", "Img", "提示图.png");
if (!File.Exists(musicPath))
{
Console.WriteLine($"音乐文件不存在: {musicPath}");
return;
}
_mediaPlayer.Open(new Uri(musicPath, UriKind.Absolute));
ShowCenterTip(imgPath, TimeSpan.FromSeconds(3));
// 监听播放完成事件
_mediaPlayer.MediaEnded += MediaPlayer_MediaEnded;
_mediaPlayer.Play();
}
private void ShowCenterTip(string imagePath, TimeSpan duration)
{
var tipImage = new Image
{
Source = new BitmapImage(new Uri(imagePath, UriKind.Absolute)),
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Opacity = 0,
Margin = new Thickness(0, -100, 0, 0),
};
// 增加图片的大小,调整比例
tipImage.Width = 1920 * 0.9; // 宽度为 Canvas 宽度的 90%
tipImage.Height = 1080 * 0.6; // 高度为 Canvas 高度的 60%
// 将图片添加到 Overlay Canvas
userBox.Children.Add(tipImage);
// 渐变出现动画
var fadeInAnimation = new DoubleAnimation
{
From = 0,
To = 1,
Duration = TimeSpan.FromSeconds(1.5)
};
tipImage.BeginAnimation(UIElement.OpacityProperty, fadeInAnimation);
// 定时移除,并且渐变消失
Task.Delay(duration).ContinueWith(_ =>
{
Dispatcher.Invoke(() =>
{
// 渐变消失动画
var fadeOutAnimation = new DoubleAnimation
{
From = 1,
To = 0,
Duration = TimeSpan.FromSeconds(1.5)
};
tipImage.BeginAnimation(UIElement.OpacityProperty, fadeOutAnimation);
// 完成后移除图片
fadeOutAnimation.Completed += (s, e) =>
{
userBox.Children.Remove(tipImage);
};
});
});
}
private void MediaPlayer_MediaEnded(object sender, EventArgs e)
{
// 音乐播放完成后的逻辑
Console.WriteLine("音乐播放完成!");
// 可在这里绑定抽帧事件
_mainWin.HumanFrameUpdated += OnHumanFrameUpdated;
}
private bool _isCountdownStarted = false; // 新增标记
private void OnHumanFrameUpdated(object sender, List<Human> humans)
{
try
{
if (humans == null || humans.Count == 0) return;
switch (_currentGameState)
{
case GameState.NotStarted:
if (!_isCountdownStarted)
{
int leftWaving = sportOperate.VerifyLeftWavingAction(humans);
if (leftWaving == 5)
{
_mainWin.HumanFrameUpdated -= OnHumanFrameUpdated;
_mainWin.WebcamClient.StopExtract();
// 举左手逻辑,例如结束动画或退出
var newPage = new Home();
_mainWin?.SwitchPageWithMaskAnimation(newPage, true);
return; // 提前退出
}
}
int rightWaving = sportOperate.VerifyRightWavingAction(humans);
if (rightWaving >= 3 && RankingGrid.Visibility != Visibility.Visible)
{
switch (rightWaving)
{
case (int)WavingAction.FirstHand:
StartCountdown(3);
_isCountdownStarted = true;
break;
case (int)WavingAction.Raising:
UpdateCountdown();
break;
case (int)WavingAction.RaiseHand:
FinishCountdown();
_currentGameState = GameState.Running;
break;
}
}
break;
case GameState.Running:
UpdateCircleCounts(humans);
break;
}
}
catch (Exception ex)
{
Console.WriteLine("OnFrameExtracted error: " + ex.Message);
}
}
private int _currentCountdown = 3;
private DateTime _lastUpdateTime = DateTime.Now;
private void StartCountdown(int start = 4)
{
_currentCountdown = start;
//countdownText.Text = _currentCountdown.ToString();
//countdownGrid.Visibility = Visibility.Visible;
_lastUpdateTime = DateTime.Now;
_mainWin.ShowCountDownAnimation();
Utils.PlayBackgroundMusic("countdown_3.mp3", false);
//开始人脸识别=============================================
Action<VideoFrame> handler = null;
handler = async (frame) =>
{
double[] xValues = [.. _musicJumpRopeContext.CirclePositions.Select(p => p.XNorm)];
var buffer = frame.GetImageBuffer(ImageFormat.Jpeg).ToArray();
var studentList = Utils.FacialRecognition(buffer, xValues);
if (studentList != null && studentList.Count > 0)
{
// 解绑事件
_mainWin.FacialRecognitionEvent -= handler;
name1.Text = studentList[0]?.StudentName ?? "一号玩家";
name2.Text = studentList[1]?.StudentName ?? "二号玩家";
if (studentList.Count > 1 && studentList[0].Photo != "")
{
await LoadImageFromUrlAsync(studentList[0]?.Photo ?? "", avatar1);
}
if (studentList.Count > 2 && studentList[1].Photo != "")
{
await LoadImageFromUrlAsync(studentList[1]?.Photo ?? "", avatar2);
}
}
// 这里可以继续处理 studentList
};
// 绑定事件
_mainWin.FacialRecognitionEvent += handler;
}
private async Task LoadImageFromUrlAsync(string imageUrl, Image image)
{
try
{
// 异步下载图片数据
byte[] imageData = await _httpClient.GetByteArrayAsync(imageUrl);
// 创建BitmapImage并设置源
var bitmap = new BitmapImage();
using (var stream = new MemoryStream(imageData))
{
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad; // 图片加载后缓存到内存
bitmap.StreamSource = stream;
bitmap.EndInit(); // 确保EndInit,否则可能导致图片加载不完整
}
bitmap.Freeze(); // 跨线程访问时可选
// 在UI线程上设置Image控件的Source
image.Source = bitmap;
}
catch (Exception ex)
{
// 处理异常(例如,网络错误、URL无效、图片格式不支持等)
//MessageBox.Show($"加载图片失败: {ex.Message}");
}
}
private void UpdateCountdown()
{
if ((DateTime.Now - _lastUpdateTime).TotalSeconds >= 1)
{
_lastUpdateTime = DateTime.Now;
_currentCountdown--;
if (_currentCountdown > 0)
{
countdownText.Text = _currentCountdown.ToString();
}
else
{
FinishCountdown();
}
}
}
private async void FinishCountdown()
{
countdownText.Text = "GO!";
countdownGrid.Visibility = Visibility.Hidden;
// 启动60秒倒计时(独立任务)
StartGameCountdown(108);
ShowMusicAnimation();
}
private async void StartGameCountdown(int seconds)
{
//_musicCurrentTime = Utils.GetMusicTotalDuration();
countdownGrid.Visibility = Visibility.Visible;
// 播放背景音乐(循环)
Utils.PlayBackgroundMusic("1.MP3", true);
popSilder1.StartMarginAnimation();
popSilder2.StartMarginAnimation();
for (int i = seconds; i >= 0; i--)
{
countdownText.Text = i.ToString();
await Task.Delay(1000);
}
// 停止音乐
Utils.StopBackgroundMusic();
// 停止实时检测和视频流
countdownGrid.Visibility = Visibility.Hidden;
_mainWin.HumanFrameUpdated -= OnHumanFrameUpdated;
_mainWin.WebcamClient.StopExtract();
// 计算排名
var rankList = _musicJumpRopeContext.UpdateRankList();
var scoreList = _musicJumpRopeContext.UpdateScoreRankList();
// 更新游戏状态
_currentGameState = GameState.NotStarted;
_isCountdownStarted = false;
// 恢复视频流
Application.Current.Dispatcher.Invoke(() =>
{
_mainWin.HumanFrameUpdated += OnHumanFrameUpdated;
_mainWin.WebcamClient.StartExtract();
showRank();
// 更新 UI(排行榜或分数板)
// ShowRankingBoard(rankList);
});
}
public void showRank()
{
double left = _musicJumpRopeContext.UserBeatSyncList[0];
double right = _musicJumpRopeContext.UserBeatSyncList[1];
score4.Text = (left * 10).ToString();
score3.Text = (right * 10).ToString();
if (left > right)
{
lose1.Margin = new Thickness(60, 0, 0, 0);
lose2.Margin = new Thickness(60, 0, 0, 0);
lose3.Margin = new Thickness(60, 0, 0, 0);
win1.Margin = new Thickness(980, -123, 0, 0);
win2.Margin = new Thickness(980, -123, 0, 0);
text1.Text = "LOSE";
text2.Text = "WIN";
text1.Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#7987a2"));
text2.Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#fdcb50"));
}
else if (right == left)
{
text2.Text = "WIN";
text2.Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#fdcb50"));
lose1.Visibility = Visibility.Hidden;
lose2.Visibility = Visibility.Hidden;
lose3.Visibility = Visibility.Hidden;
win3.Visibility = Visibility.Visible;
win4.Visibility = Visibility.Visible;
}
showElement("RankingGrid");
}
public void showElement(string name, int time = 600)
{
var Element = FindName(name) as FrameworkElement;
if (Element == null) return;
Element.Visibility = Visibility.Visible;
var fadeIn = new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(time));
Element.BeginAnimation(UIElement.OpacityProperty, fadeIn);
}
private void DrawCirclesWithText()
{
userBox.Children.Clear();
_musicJumpRopeContext.Sports.Clear();
_musicJumpRopeContext.UserList.Clear();
_musicJumpRopeContext.UserNumberList.Clear();
_musicJumpRopeContext.UserBeatSyncList.Clear();
double imgWidth = userBox.ActualWidth;
double imgHeight = userBox.ActualHeight;
double radius = 100;
for (int i = 0; i < _musicJumpRopeContext.CirclePositions.Count; i++)
{
//var pos = _musicJumpRopeContext.CirclePositions[i == 0 ? 1 : 0];
var pos = _musicJumpRopeContext.CirclePositions[i];
double x = pos.XNorm * imgWidth;
double y = pos.YNorm * imgHeight;
// 绘制发光圆
var userItem = AddUserItem(x, y, i);
// 绑定运动对象
var sport = SportBase.Create("rope-skipping");
int indexCopy = i;
var currentItem = userItem;
// 订阅事件
sport.OnTicked += (count, times) =>
{
// 更新UI
//userItem.NumberText = count.ToString();
// 更新数字源
_musicJumpRopeContext.UserNumberList[indexCopy] = count.ToString();
if (userItem.ImageState != "2")
userItem.ImageState = "2";
//ChangeImgState(indexCopy, "2"); //改变当前图的状态
var currentTime = Utils.GetMusicCurrentTime();
int currentSecond = (int)Math.Floor(currentTime);
// 判断是否命中节拍
var beats = _musicJumpRopeContext.MusicBeats["1"];
bool hit = beats.Any(b => Math.Abs(b - currentTime) <= _beatTolerance);
if (hit)
{
_musicJumpRopeContext.UserBeatSyncList[indexCopy] += 1;
double left = _musicJumpRopeContext.UserBeatSyncList[0];
double right = _musicJumpRopeContext.UserBeatSyncList[1];
double total = left + right;
PkBar.LeftProgress = 1 - (total == 0 ? 0.5 : Math.Round(left / total, 2));
if (indexCopy == 0)
{
popSilder2.IsMiss = false;
//UpdateScore(left, score1);
score2.Text = (left * 10).ToString();
}
else
{
popSilder1.IsMiss = false;
//UpdateScore(right, score2);
score1.Text = (right * 10).ToString();
}
}
};
sport.Start();
_musicJumpRopeContext.Sports.Add(sport);
}
}
public void ShowMusicAnimation()
{
ImageBehavior.SetRepeatBehavior(leftm, RepeatBehavior.Forever);
ImageBehavior.SetRepeatBehavior(rightm, RepeatBehavior.Forever);
ImageBehavior.GetAnimationController(leftm).Play();
ImageBehavior.GetAnimationController(rightm).Play();
}
private void UpdateCircleCounts(List<Human> humans)
{
double halfW = AppSettings.AreaSize.X;
double halfH = AppSettings.AreaSize.Y;
for (int i = 0; i < _musicJumpRopeContext.CirclePositions.Count; i++)
{
var circle = _musicJumpRopeContext.CirclePositions[i];
double cx = circle.XNorm;
double cy = circle.YNorm;
Human humanInCircle = null;
// 1️⃣ 找圈内的人(单脚命中即可)
foreach (var h in humans)
{
if (sportOperate.IsAnyFootInCircle(h, cx, cy, halfW, halfH))
{
humanInCircle = h;
break; // 每个圈只认一个人
}
}
// 根据是否有人和跳绳数字判断状态
bool hasHuman = humanInCircle != null;
lock (_updateLock)
{
_musicJumpRopeContext.UserList[i].ImageState = GetJumpState(i, hasHuman);
}
// 3️⃣ 推送计数
if (hasHuman)
{
_musicJumpRopeContext.Sports[i].Pushing(humanInCircle);
}
}
}
private string GetJumpState(int circleIndex, bool humanInCircle)
{
if (!humanInCircle)
{
// 无人 → 出圈
return "3";
}
string currentNumber = _musicJumpRopeContext.UserNumberList[circleIndex];
if (!_jumpStatus.ContainsKey(circleIndex))
{
_jumpStatus[circleIndex] = (currentNumber, DateTime.Now, "1"); // 初始状态先为停止
return "1";
}
var (lastNumber, lastChangeTime, lastState) = _jumpStatus[circleIndex];
if (currentNumber != lastNumber)
{
// 数字变化 → 跳绳中
_jumpStatus[circleIndex] = (currentNumber, DateTime.Now, "2");
return "2";
}
// 数字未变化,判断是否超过 2 秒
double elapsed = (DateTime.Now - lastChangeTime).TotalSeconds;
if (elapsed >= 0.8)
{
// 超过 2 秒未变化 → 停止
_jumpStatus[circleIndex] = (currentNumber, lastChangeTime, "1");
return "1";
}
// 维持上次状态
return lastState;
}
private MusicUserItem AddUserItem(double centerX, double centerY, int index)
{
var userItem = new MusicUserItem();
userItem.VerticalAlignment = VerticalAlignment.Top;
userItem.HorizontalAlignment = index == 1 ? HorizontalAlignment.Left : HorizontalAlignment.Right;
userItem.ImageState = "1";
userItem.Margin = index == 1 ? new Thickness(226, 345, 0, 0) : new Thickness(0, 345, 226, 0);
userBox.Children.Add(userItem);
_musicJumpRopeContext.UserList.Add(userItem);
_musicJumpRopeContext.UserNumberList.Add("0");
_musicJumpRopeContext.UserBeatSyncList.Add(0);
return userItem; //
}
private void Image_MouseDown(object sender, MouseButtonEventArgs e)
{
popSilder1.StartMarginAnimation();
popSilder2.StartMarginAnimation();
}
}
}