C# WPF 低仿网易云音乐(PC)歌词控件
2021-07-02 16:40
标签:oca com bug www. change span 歌词 完整 nav 提醒:本篇博客记录了修改的过程,废话比较多,需要项目源码和看演示效果的直接拉到文章最底部~ 网易云音乐获取歌词的api地址 填写歌曲的id即可获取到json格式的数据(歌曲ID获取的方法是:点击分享按钮>其他分享>复制链接,就可以在链接中看到了): 我们需要用到的数据只有lyric部分。 可以看到歌词的结构很简单:“[歌曲时间]歌词部分\n”,\n作为换行符。 看完歌词结构思路已经有了,将时间和歌词提取出来,播放音乐,获取音乐播放进度,跟歌词对应的时间作比对。 这里我们要做的控件效果如网易云音乐(以下简称网音)的效果差不多,先看下网音的效果动图: 网音的效果如图所示,播放到当前进度的歌词字体颜色变成白色,并且会滚动视图使焦点停在歌词整体中间的位置。 新建项目命名为:网易云音乐歌词显示控件,新建一个用户控件命名为:LrcView。 LrcView.xaml: 应该很好理解,用一个scrollviewer包住stackpanel,把歌词(textblock)添加到stackpanel里,当前音乐所在进度的歌词颜色只要修改相应的textblock的字体颜色即可,歌词自动滚动则依靠scrollviewer。(小知识:*只要不设置固定高度stackpanel会随着包含的内容高度的增大而增大,这时候套个scrollviewer就可以滚动stackpanel了。) 转到控件后台代码,为了方便修改歌词颜色等一些操作,我们建立一个歌词模型类: 添加一些需要用到的变量 接着是加载歌词 将歌词复制出来,用\n切割(split),再用正则表达式取出时间、歌词,将获取到的数据添加到集合内以及对textblock进行赋值即可。 接下来是歌词滚动以及判断音乐进度对应的歌词 输入当前播放的音乐所在时间后,找到对应的歌词,将其标亮,scrollviewer滚动到相对于歌词整体中间的位置。 控件部分写完了,现在重新生成项目,我们调用试试效果。 打开MainWindow.xaml,修改为: 后台代码: 这里用了一个计时器,在播放音乐开始时启动,每隔1秒执行一次控件的LrcRoll方法实现歌词标记和焦点滚动。 启动程序看看效果吧~ 效果看似实现了,但是发现了新的问题,如果当“用户调整了音乐的进度”后,歌词显示将完全乱套。明天再修复吧 - 2018年1月11日16:39:10 继续昨天的优化(修复) 1,将歌词模型的时间属性数据类型修改为double。歌词的位置计算将用总毫秒数去判断,将音乐的播放进度转换为毫秒数,载入歌词时也将歌词的对应时间转为毫秒数,通过进度的毫秒数去查找相应的歌词; 2,修改变量部分代码: 3,修改核心部分代码: 为了能看到调整音乐进度时歌词的显示效果,我们需要在mainwindow.xaml加入一个slider控制音乐的进度并且修改一下后台代码。 后台代码: OK,搞定,看下效果视频 我发现写个歌词控件都快写成一个完整的播放器了(逃 调用控件步骤 第一步,载入歌词部分字符串: 将网音的歌词接口数据的lyric部分复制下来填到lrcstr参数(这里要注意的是这个控件并不完整,没有对一些地方进行处理,如:项目中获取到的歌词开始的地方有几个代表音乐制作人等一些信息的参数,不处理的话会导致后面的时间获取失败) 第二步,播放音乐实时 / 调整进度的时候都要调用LRCROLL方法更新歌词和焦点滚动: 其中nowtime即音乐当前进度的总毫秒数 项目下载: 点我下载 - 2018年1月12日10:19:11 关于如何做到酷狗那样的歌词逐字描色效果我的思路是这样的,在歌词结构中的每句歌词都标记上时间段(即某个字到某个字所需要的时间),然后在程序中读取时间段,根据时间段给时间段内的歌词上色。至于怎么一个字一个字地上色我暂时不知道。 - 2018年1月13日08:53:26 实现了歌词逐字描色,在这篇博客C# WPF 歌词逐字定位描色效果实现 - 2018年4月23日 C# WPF 低仿网易云音乐(PC)歌词控件 标签:oca com bug www. change span 歌词 完整 nav 原文地址:https://www.cnblogs.com/lonelyxmas/p/9919852.htmlhttp://music.163.com/api/song/media?id=歌曲ID
{"songStatus":0,"lyricVersion":10,"lyric":"[by:Esida]\n[ti:起风了]\n[ar:买辣椒也用劵]\n[al:起风了]\n[by:九九Lrc歌词网~www.99Lrc.net]\n\n\n[00:04.00]原曲: ヤキモチ\n\n\n\n[00:20.00]后期: 圣雨轻纱\n\n[00:24.00]海报:不 咸\n\n\n\n[00:28.64]这一路上走走停停 顺着少年漂流的痕迹\n\n[00:35.11]迈出车站的前一刻 竟有些犹豫\n\n[00:41.08]不禁笑这近乡情怯 仍无可避免\n\n[00:46.49]而长野的天 依旧这么暖 风吹起了从前\n\n[00:52.02]从前初识这世间 万般流连 看着天边似在眼前\n\n[00:59.50]也甘愿赴汤蹈火去走它一遍\n\n[01:04.52]如今走过这世间 万般流连 翻过岁月不同侧脸\n\n[01:11.75]措不及防闯入你的笑颜\n\n[01:17.37]我曾难自拔于世界之大 也沉溺于其中梦话 不得真假 不做挣扎 不惧笑话\n\n[01:30.39]我曾将青春翻涌成她 也曾指尖弹出盛夏 心之所动 且就随缘去吧\n\n[01:42.40]逆着光行走 任风吹雨打\n\n[01:49.89]-M-\n\n[01:59.14]短短的路走走停停 也有了几分的距离\n\n[02:05.20]不知抚摸的是故事 还是段心情\n\n[02:11.22]也许期待的不过是 与时间为敌\n\n[02:16.94]再次看到你 微凉晨光里 笑的很甜蜜\n\n[02:22.29]从前初识这世间 万般流连 看着天边似在眼前\n\n[02:29.58]也甘愿赴汤蹈火去走它一遍\n\n[02:34.42]如今走过这世间 万般流连 翻过岁月不同侧脸\n\n[02:41.87]措不及防闯入你的笑颜\n\n[02:47.23]我曾难自拔于世界之大 也沉溺于其中梦话 不得真假 不做挣扎 不惧笑话\n\n[03:00.13]我曾将青春翻涌成她 也曾指尖弹出盛夏 心之所动 且就随缘去吧\n\n[03:15.51]-=-\n\n[03:38.30]晚风吹起你鬓间的白发 抚平回忆留下的疤 你的眼中 明暗交杂 一笑生花\n\n[03:50.53]暮色遮住你蹒跚的步伐 走进床头藏起的画 画中的你 低着头说话\n\n[04:03.05]我仍感叹于世界之大 也沉醉于儿时情话 不剩真假 不做挣扎 无谓笑话\n\n[04:15.34]我终将青春还给了她 连同指尖弹出的盛夏 心之所动 就随风去了\n\n[04:27.63]以爱之名 你还愿意吗\n\n[04:35.36]-E-","code":200}
UserControl x:Class="网易云音乐歌词显示控件.Controls.LrcView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
Grid>
ScrollViewer Name="c_scrollviewer">
StackPanel Name="c_lrc_items">
StackPanel>
ScrollViewer>
Grid>
UserControl>
#region 歌词模型
public class LrcModel
{
///
#region 变量
//歌词集合
public Dictionarystring, LrcModel> Lrcs = new Dictionarystring, LrcModel>();
//当前焦点所在歌词集合位置
public int FoucsLrcLocation { get; set; } = -1;
//非焦点歌词颜色
public SolidColorBrush NoramlLrcColor = new SolidColorBrush(Colors.Black);
//焦点歌词颜色
public SolidColorBrush FoucsLrcColor = new SolidColorBrush(Colors.OrangeRed);
#endregion
#region 加载歌词
public void LoadLrc(string lrcstr)
{
//循环以换行\n切割出歌词
foreach (string str in lrcstr.Split(‘\n‘))
{
//过滤空行
if (str.Length > 0)
{
//歌词时间
string time = GetTime(str);
//歌词
string lrc = str.Replace("[" + time + "]", "");
//过滤空行
if (time.Length > 0)
{
//歌词显示textblock控件
TextBlock c_lrcbk = new TextBlock();
//赋值
c_lrcbk.Text = lrc;
if (c_lrc_items.Children.Count > 0)
{
//增加一些行间距,see起来不那么拥挤~
c_lrcbk.Margin = new Thickness(0, 10, 0, 0);
}
//添加到集合,方便日后操作
Lrcs.Add(time, new LrcModel()
{
c_LrcTb = c_lrcbk,
LrcText = lrc,
Time = time
});
//将歌词显示textblock控件添加到界面中显示
c_lrc_items.Children.Add(c_lrcbk);
}
}
}
}
//正则表达式提取时间
public string GetTime(string str)
{
Regex reg = new Regex(@"\[(?", RegexOptions.IgnoreCase);
return reg.Match(str).Groups["time"].Value;
}
#endregion
#region 歌词滚动
public void LrcRoll(string nowtime)
{
nowtime = "00:" + nowtime;
if (FoucsLrcLocation 0)
{
//音乐开始时歌词焦点到第一句
FoucsLrcLocation = 0;
Lrcs.Values.ToList()[FoucsLrcLocation].c_LrcTb.Foreground = FoucsLrcColor;
}
else
{
//循环获取歌词
for (int i = FoucsLrcLocation + 1; i )
{
LrcModel lrc = Lrcs.Values.ToList()[i];
//调整格式方便计算
string lrctime = "00:" + lrc.Time;
//计算当前音乐播放时间与歌词时间的差值,大于等于0时代表到达歌词位置
double s = DateDiff(Convert.ToDateTime(nowtime), Convert.ToDateTime(lrctime));
if (s >= 0)
{
//取消当前焦点歌词
Lrcs.Values.ToList()[FoucsLrcLocation].c_LrcTb.Foreground = NoramlLrcColor;
//给歌词控件设置颜色突出显示
lrc.c_LrcTb.Foreground = FoucsLrcColor;
//重新设置当前歌词位置
FoucsLrcLocation = i;
ResetLrcviewScroll();
//Debug.WriteLine("nowtime:" + nowtime + ",lrctime:" + lrctime + ",s:" + s);
break;
}
}
}
}
//计算时间差
public double DateDiff(DateTime DateTime1, DateTime DateTime2)
{
double dateDiff = 0;
try
{
TimeSpan ts1 = new TimeSpan(DateTime1.Ticks);
TimeSpan ts2 = new TimeSpan(DateTime2.Ticks);
TimeSpan t = ts1 - ts2;
TimeSpan ts = ts1.Subtract(ts2).Duration();
dateDiff = t.Seconds + t.Milliseconds;
}
catch
{
}
return dateDiff;
}
#endregion
#region 调整歌词控件滚动条位置
public void ResetLrcviewScroll()
{
//获得焦点歌词位置
GeneralTransform gf = Lrcs.Values.ToList()[FoucsLrcLocation].c_LrcTb.TransformToVisual(c_lrc_items);
Point p = gf.Transform(new Point(0, 0));
//滚动条当前位置
Debug.WriteLine(c_scrollviewer.VerticalOffset + "/" + p.Y);
//计算滚动位置(p.Y是焦点歌词控件(c_LrcTb)相对于父级控件c_lrc_items(StackPanel)的位置)
//拿焦点歌词位置减去滚动区域控件高度除以2的值得到的【大概】就是歌词焦点在滚动区域控件的位置
double os = p.Y - (c_scrollviewer.ActualHeight / 2) + 10;
//滚动
c_scrollviewer.ScrollToVerticalOffset(os);
}
#endregion
Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:网易云音乐歌词显示控件"
xmlns:Controls="clr-namespace:网易云音乐歌词显示控件.Controls" x:Class="网易云音乐歌词显示控件.MainWindow"
mc:Ignorable="d"
Title="网易云音乐歌词显示控件" Height="350" Width="525">
Grid>
Grid.RowDefinitions>
RowDefinition Height="80">RowDefinition>
RowDefinition Height="*">RowDefinition>
Grid.RowDefinitions>
Grid Grid.Row="0">
StackPanel Height="25" Orientation="Horizontal">
Button Name="pbtn" Width="100" Content="播放" Click="Button_Click">Button>
Button Name="sbtn" Width="100" Content="暂停" Click="Button_Click_1">Button>
TextBlock Name="metime" VerticalAlignment="Center" Text="00:00">TextBlock>
MediaElement Name="me" LoadedBehavior="Manual"/>
StackPanel>
Grid>
Controls:LrcView x:Name="LrcView" Grid.Row="1" Margin="10"/>
Grid>
Window>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Media;
using System.Text;
using System.Threading.Tasks;
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.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace 网易云音乐歌词显示控件
{
///
//歌词集合
public Dictionarydouble, LrcModel> Lrcs = new Dictionarydouble, LrcModel>();
//添加当前焦点歌词变量
public LrcModel foucslrc { get; set; }
#region 加载歌词
public void LoadLrc(string lrcstr)
{
//循环以换行\n切割出歌词
foreach (string str in lrcstr.Split(‘\n‘))
{
//过滤空行,判断是否存在时间
if (str.Length > 0 && str.IndexOf(":") != -1)
{
//歌词时间
TimeSpan time = GetTime(str);
//歌词取]后面的就行了
string lrc = str.Split(‘]‘)[1];
//歌词显示textblock控件
TextBlock c_lrcbk = new TextBlock();
//赋值
c_lrcbk.Text = lrc;
if (c_lrc_items.Children.Count > 0)
{
//增加一些行间距,see起来不那么拥挤~
c_lrcbk.Margin = new Thickness(0, 10, 0, 0);
}
//添加到集合,方便日后操作
Lrcs.Add(time.TotalMilliseconds, new LrcModel()
{
c_LrcTb = c_lrcbk,
LrcText = lrc,
Time = time.TotalMilliseconds
});
//将歌词显示textblock控件添加到界面中显示
c_lrc_items.Children.Add(c_lrcbk);
}
}
}
//正则表达式提取时间
public TimeSpan GetTime(string str)
{
Regex reg = new Regex(@"\[(?", RegexOptions.IgnoreCase);
string timestr = reg.Match(str).Groups["time"].Value;
//获得分
int m = Convert.ToInt32(timestr.Split(‘:‘)[0]);
//判断是否有小数点
int s = 0, f = 0;
if (timestr.Split(‘:‘)[1].IndexOf(".") != -1)
{
//有
s = Convert.ToInt32(timestr.Split(‘:‘)[1].Split(‘.‘)[0]);
//获得毫秒位
f = Convert.ToInt32(timestr.Split(‘:‘)[1].Split(‘.‘)[1]);
}
else
{
//没有
s = Convert.ToInt32(timestr.Split(‘:‘)[1]);
}
Debug.WriteLine(m + "-" + s + "-" + f + "->" + new TimeSpan(0, 0, m, s, f).TotalMilliseconds);
return new TimeSpan(0, 0, m, s, f);
}
#endregion
#region 歌词滚动
///
Slider IsSnapToTickEnabled="True" TickFrequency="1" Name="sd" Height="20" VerticalAlignment="Top" />
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Media;
using System.Text;
using System.Threading.Tasks;
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.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace 网易云音乐歌词显示控件
{
///
public void LoadLrc(string lrcstr);
public void LrcRoll(double nowtime);