C# WPF 低仿网易云音乐(PC)歌词控件

简介: 原文:C# WPF 低仿网易云音乐(PC)歌词控件提醒:本篇博客记录了修改的过程,废话比较多,需要项目源码和看演示效果的直接拉到文章最底部~   网易云音乐获取歌词的api地址 http://music.

提醒:本篇博客记录了修改的过程,废话比较多,需要项目源码和看演示效果的直接拉到文章最底部~

 

网易云音乐获取歌词的api地址

http://music.163.com/api/song/media?id=歌曲ID

填写歌曲的id即可获取到json格式的数据(歌曲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}

我们需要用到的数据只有lyric部分。

可以看到歌词的结构很简单:“[歌曲时间]歌词部分\n”\n作为换行符。

看完歌词结构思路已经有了,将时间和歌词提取出来,播放音乐,获取音乐播放进度,跟歌词对应的时间作比对。

这里我们要做的控件效果如网易云音乐(以下简称网音)的效果差不多,先看下网音的效果动图:

网音的效果如图所示,播放到当前进度的歌词字体颜色变成白色,并且会滚动视图使焦点停在歌词整体中间的位置。

 

新建项目命名为:网易云音乐歌词显示控件,新建一个用户控件命名为:LrcView。

LrcView.xaml:

<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>

应该很好理解,用一个scrollviewer包住stackpanel,把歌词(textblock)添加到stackpanel里,当前音乐所在进度的歌词颜色只要修改相应的textblock的字体颜色即可,歌词自动滚动则依靠scrollviewer。(小知识:*只要不设置固定高度stackpanel会随着包含的内容高度的增大而增大,这时候套个scrollviewer就可以滚动stackpanel了。)

转到控件后台代码,为了方便修改歌词颜色等一些操作,我们建立一个歌词模型类:

   #region 歌词模型
        public class LrcModel
        {
            /// 
            /// 歌词所在控件
            /// 
            public TextBlock c_LrcTb { get; set; }

            /// 
            /// 歌词字符串
            /// 
            public string LrcText { get; set; }

            /// 
            /// 时间(读取格式参照网易云音乐歌词格式:xx:xx.xx,即分:秒.毫秒,秒小数点保留2位。如:00:28.64)
            /// 
            public string Time { get; set; }
        }
        #endregion

添加一些需要用到的变量

 #region 变量
        //歌词集合
        public Dictionary<string, LrcModel> Lrcs = new Dictionary<string, LrcModel>();
        //当前焦点所在歌词集合位置
        public int FoucsLrcLocation { get; set; } = -1;
        //非焦点歌词颜色
        public SolidColorBrush NoramlLrcColor = new SolidColorBrush(Colors.Black);
        //焦点歌词颜色
        public SolidColorBrush FoucsLrcColor = new SolidColorBrush(Colors.OrangeRed);

        #endregion

接着是加载歌词

将歌词复制出来,用\n切割(split),再用正则表达式取出时间、歌词,将获取到的数据添加到集合内以及对textblock进行赋值即可。

 #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

接下来是歌词滚动以及判断音乐进度对应的歌词

输入当前播放的音乐所在时间后,找到对应的歌词,将其标亮,scrollviewer滚动到相对于歌词整体中间的位置。

 #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 < Lrcs.Values.Count; 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

控件部分写完了,现在重新生成项目,我们调用试试效果。

打开MainWindow.xaml,修改为:

<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 网易云音乐歌词显示控件
{
    /// 
    /// MainWindow.xaml 的交互逻辑
    /// 
    public partial class MainWindow : Window
    {
        DispatcherTimer dt;
       
        public MainWindow()
        {
            InitializeComponent();

            string lrc = "[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-";

            LrcView.LoadLrc(lrc);

            dt = new DispatcherTimer();
            dt.Interval = TimeSpan.FromSeconds(1);
            dt.Tick += (e, c) =>
            {
                string time = me.Position.ToString("mm") + ":" + me.Position.ToString("ss") + "." + me.Position.ToString("ff");

                metime.Text = me.Position.ToString("mm") + ":" + me.Position.ToString("ss");

                LrcView.LrcRoll(time);
            };

            Play(false);
            me.Source = new Uri(@"C:\CloudMusic\qfl.mp3", UriKind.RelativeOrAbsolute);
        }
        void Play(bool s)
        {
            if (s)
            {
                sbtn.IsEnabled = true;
                pbtn.IsEnabled = false;
            }
            else
            {
                sbtn.IsEnabled = false;
                pbtn.IsEnabled = true;
            }
        }
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Play(true);

            dt.Start();
            me.Play();
        }

        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            Play(false);

            dt.Stop();
            me.Pause();
        }
    }
}

这里用了一个计时器,在播放音乐开始时启动,每隔1秒执行一次控件的LrcRoll方法实现歌词标记和焦点滚动。

启动程序看看效果吧~

效果看似实现了,但是发现了新的问题,如果当“用户调整了音乐的进度”后,歌词显示将完全乱套。明天再修复吧

 - 2018年1月11日16:39:10

 

继续昨天的优化(修复)

1,将歌词模型的时间属性数据类型修改为double。歌词的位置计算将用总毫秒数去判断,将音乐的播放进度转换为毫秒数,载入歌词时也将歌词的对应时间转为毫秒数,通过进度的毫秒数去查找相应的歌词;

2,修改变量部分代码:

//歌词集合
        public Dictionary<double, LrcModel> Lrcs = new Dictionary<double, LrcModel>();

        //添加当前焦点歌词变量
        public LrcModel foucslrc { get; set; }

3,修改核心部分代码:

  #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 歌词滚动
        /// 
        /// 歌词滚动、定位焦点
        /// 
        /// 
public void LrcRoll( double nowtime) { if (foucslrc == null ) { foucslrc = Lrcs.Values.First(); foucslrc.c_LrcTb.Foreground = FoucsLrcColor; } else { // 查找焦点歌词 IEnumerabledouble, LrcModel>> s = Lrcs.Where(m => nowtime>= m.Key); if (s.Count() > 0 ) { LrcModel lm = s.Last().Value; foucslrc.c_LrcTb.Foreground = NoramlLrcColor; foucslrc = lm; foucslrc.c_LrcTb.Foreground = FoucsLrcColor; // 定位歌词在控件中间区域 ResetLrcviewScroll(); } } // if (FoucsLrcLocation < 0) // { // // 音乐开始时歌词焦点到第一句 // FoucsLrcLocation = 0; // Lrcs.Values.ToList()[FoucsLrcLocation].c_LrcTb.Foreground = FoucsLrcColor; // } // else // { // // 循环获取歌词 // for (int i = FoucsLrcLocation + 1; i < Lrcs.Values.Count; i++) // { // LrcModel lrc = Lrcs.Values.ToList()[i]; // // 计算当前音乐播放时间与歌词时间的差值 // if () // { // // 取消当前焦点歌词 // 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; // } // } // } } #endregion #region 调整歌词控件滚动条位置 public void ResetLrcviewScroll() { // 获得焦点歌词位置 GeneralTransform gf = foucslrc.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

为了能看到调整音乐进度时歌词的显示效果,我们需要在mainwindow.xaml加入一个slider控制音乐的进度并且修改一下后台代码。

 <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 网易云音乐歌词显示控件
{
    /// 
    /// MainWindow.xaml 的交互逻辑
    /// 
    public partial class MainWindow : Window
    {
        DispatcherTimer dt;

        public MainWindow()
        {
            InitializeComponent();

            string lrc = "[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-";
            //加载歌词
            LrcView.LoadLrc(lrc);
            //初始化计时器
            dt = new DispatcherTimer();
            /*
             * 2018年1月12日10:04:48
             * 1秒有些延迟调整为0.5秒
             * 
             * */
            dt.Interval = TimeSpan.FromSeconds(0.5);

            dt.Tick += (e, c) =>
            {
                LrcRoll();
            };

            //设置按钮状态
            Play(false);
            me.Source = new Uri(@"C:\CloudMusic\qfl.mp3", UriKind.RelativeOrAbsolute);
            //设置为pause时才能达到启动应用后马上加载音乐以获取总时长
            me.LoadedBehavior = MediaState.Pause;
            //加载音乐后获取总时长
            me.MediaOpened += (e, c) =>
            {
                //将总时长转换为秒单位
                sd.Maximum = (me.NaturalDuration.TimeSpan.Minutes * 60) + me.NaturalDuration.TimeSpan.Seconds;
                //重新设置为手动播放模式,否则无法播放音乐
                me.LoadedBehavior = MediaState.Manual;
            };


            //监听slider的鼠标释放事件,即拖动时不调整音乐的进度,拖动后再调整
            sd.PreviewMouseLeftButtonUp += (e, c) =>
            {
                STime st = GetStime(sd.Value);
                me.Position = new TimeSpan(0, st.m, st.s);

                LrcRoll();

            };
            //对slider的值变化监听,修改时可实时显示调整的时间
            sd.ValueChanged += (e, c) =>
            {
                STime st = GetStime(sd.Value);
                TimeSpan ts = new TimeSpan(0, st.m, st.s);
                metime.Text = ts.ToString("mm") + ":" + ts.ToString("ss");
            };
        }
        void LrcRoll()
        {

            metime.Text = me.Position.ToString("mm") + ":" + me.Position.ToString("ss");

            LrcView.LrcRoll(me.Position.TotalMilliseconds);
        }
        void Play(bool s)
        {
            if (s)
            {
                sbtn.IsEnabled = true;
                pbtn.IsEnabled = false;
            }
            else
            {
                sbtn.IsEnabled = false;
                pbtn.IsEnabled = true;
            }
        }
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Play(true);

            dt.Start();
            me.Play();
        }

        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            Play(false);

            dt.Stop();
            me.Pause();
        }


        public class STime
        {
            public int m { get; set; }
            public int s { get; set; }
        }
        public STime GetStime(double v)
        {
            var st = new STime();

            TimeSpan ts = new TimeSpan(0, 0, Convert.ToInt32(v));
            //得到多少分钟
            st.m = ts.Minutes;
            st.s = ts.Seconds;
            return st;
        }


    }
}

OK,搞定,看下效果视频

我发现写个歌词控件都快写成一个完整的播放器了(逃

调用控件步骤

第一步,载入歌词部分字符串:

public void LoadLrc(string lrcstr);

将网音的歌词接口数据的lyric部分复制下来填到lrcstr参数(这里要注意的是这个控件并不完整,没有对一些地方进行处理,如:项目中获取到的歌词开始的地方有几个代表音乐制作人等一些信息的参数,不处理的话会导致后面的时间获取失败)

第二步,播放音乐实时 / 调整进度的时候都要调用LRCROLL方法更新歌词和焦点滚动:

 public void LrcRoll(double nowtime);

其中nowtime即音乐当前进度的总毫秒数

 

项目下载:

 - 2018年1月12日10:19:11

 

关于如何做到酷狗那样的歌词逐字描色效果我的思路是这样的,在歌词结构中的每句歌词都标记上时间段(即某个字到某个字所需要的时间),然后在程序中读取时间段,根据时间段给时间段内的歌词上色。至于怎么一个字一个字地上色我暂时不知道。

 - 2018年1月13日08:53:26

 

实现了歌词逐字描色,在这篇博客

目录
相关文章
|
1月前
|
SQL 开发框架 .NET
C#一分钟浅谈:数据绑定与数据源控件
在Web开发中,数据绑定和数据源控件是实现动态网页的关键技术。本文从基础概念入手,详细讲解数据绑定的原理及其在ASP.NET中的应用,并介绍常见数据绑定方式:手动绑定和自动绑定。接着,文章重点介绍了ASP.NET中的数据源控件,如`SqlDataSource`、`ObjectDataSource`、`XmlDataSource`和`LinqDataSource`,并通过具体示例演示如何使用`SqlDataSource`和`GridView`进行数据绑定。最后,还列举了一些常见问题及其解决办法,帮助读者更好地理解和应用这些技术。
66 4
|
3月前
|
C#
|
28天前
|
C# Python
使用wxpython开发跨平台桌面应用,对wxpython控件实现类似C#扩展函数处理的探究
【10月更文挑战第30天】使用 `wxPython` 开发跨平台桌面应用时,可以通过创建辅助类来模拟 C# 扩展函数的功能。具体步骤包括:1. 创建辅助类 `WxWidgetHelpers`;2. 在该类中定义静态方法,如 `set_button_color`;3. 在应用中调用这些方法。这种方法提高了代码的可读性和可维护性,无需修改 `wxPython` 库即可为控件添加自定义功能。但需要注意显式调用方法和避免命名冲突。
C# WPF 中 外部图标引入iconfont,无法正常显示问题 【小白记录】
本文介绍了在C# WPF应用程序中引入外部iconfont图标时可能遇到的显示问题及其解决方法:1) 检查资源路径和引入格式是否正确,确保字体文件引用格式为“#xxxx”,并正确指向字体文件位置;2) 确保图标资源被包含在程序集中,通过设置字体文件的生成操作为Resource(资源)来实现。
C# WPF 中 外部图标引入iconfont,无法正常显示问题 【小白记录】
|
2月前
|
编解码 C# 数据库
C# + WPF 音频播放器 界面优雅,体验良好
【9月更文挑战第18天】这是一个用 C# 和 WPF 实现的音频播放器示例,界面简洁美观,功能丰富。设计包括播放/暂停按钮、进度条、音量控制滑块、歌曲列表和专辑封面显示。功能实现涵盖音频播放、进度条控制、音量调节及歌曲列表管理。通过响应式设计、动画效果、快捷键支持和错误处理,提升用户体验。可根据需求扩展更多功能。
125 3
|
3月前
|
C# 开发者 Windows
一款基于Fluent设计风格、现代化的WPF UI控件库
一款基于Fluent设计风格、现代化的WPF UI控件库
|
3月前
|
C# 前端开发 UED
WPF数据验证实战:内置控件与自定义规则,带你玩转前端数据验证,让你的应用程序更上一层楼!
【8月更文挑战第31天】在WPF应用开发中,数据验证是确保输入正确性的关键环节。前端验证能及时发现错误,提升用户体验和程序可靠性。本文对比了几种常用的WPF数据验证方法,并通过示例展示了如何使用内置验证控件(如`TextBox`)及自定义验证规则实现有效验证。内置控件结合`Validation`类可快速实现简单验证;自定义规则则提供了更灵活的复杂逻辑支持。希望本文能帮助开发者更好地进行WPF数据验证。
116 0
|
3月前
|
C# UED 定位技术
WPF控件大全:初学者必读,掌握控件使用技巧,让你的应用程序更上一层楼!
【8月更文挑战第31天】在WPF应用程序开发中,控件是实现用户界面交互的关键元素。WPF提供了丰富的控件库,包括基础控件(如`Button`、`TextBox`)、布局控件(如`StackPanel`、`Grid`)、数据绑定控件(如`ListBox`、`DataGrid`)等。本文将介绍这些控件的基本分类及使用技巧,并通过示例代码展示如何在项目中应用。合理选择控件并利用布局控件和数据绑定功能,可以提升用户体验和程序性能。
69 0
|
3月前
|
C#
C# WPF 将第三方DLL嵌入 exe
C# WPF 将第三方DLL嵌入 exe
82 0
|
3月前
|
前端开发 C# 容器
WPF/C#:实现导航功能
WPF/C#:实现导航功能
75 0