zoukankan      html  css  js  c++  java
  • 重做了一下我的音乐播放器

    前些天看到新闻说Windows10自带的Windows Media Player将支持FLAC无损播放,而目前自己的播放器是采用BASS音频库去支持无损的播放的,BASS音频库的用法奇葩不说,文档也不多,遇到问题网上搜半天都找不着答案真是愁死,于是打算将我的音乐播放器重做一番了,这次重新用回WMP内核。


    相信做过WPF媒体方面应用开发的同学都知道,new一个MediaPlayer就可以实现对媒体的操作了,而这个MediaPlayer其实是用的当前操作系统中的Windows Media Player,支持播放暂停停止,播放速率调节,音量设置(BASS的音量设置很蛋疼,会连系统音量都给调了),提供媒体打开,媒体结束,播放错误3个事件,因而如果要自己做一个音乐播放器的话,再加上媒体状态的判断,以及添加媒体状态/播放进度改变的事件就差不多了。因为是重做的,所以只实现了一下基础功能(播放控制,收藏,播放模式)以及几个个性功能(音乐统计,定时停止,播放进度的显示),同时BUG还很多,还在继续开发中,如果有兴趣的话可以一起开发哟,项目已经传到TFS了,地址是http://llyn23.visualstudio.com

    Debug目录文件下载:http://yun.baidu.com/s/1xYWua

    Solution项目源代码下载:http://yun.baidu.com/s/1mguWDCG

    1.构建CoreHelper.cs

    这个播放器核心类包含媒体的播放,添加取消收藏,切换播放模式,歌曲文件的读取,媒体库/播放列表/播放器配置等的保存等

    2.数据的保存和读取

    在程序启动(OnStartUp)时加载XML文件反序列化为对象,保存有媒体库,播放列表,播放记录,收藏的歌曲等,退出(OnExit)时则根据对象是否发生改变,将对象序列化到XML文件中

                string repository = System.IO.Path.Combine(new string[] { AppDomain.CurrentDomain.BaseDirectory, "Repository" });
                if (!System.IO.Directory.Exists(repository))
                {
                    return;
                }
    
                //加载播放器设置
                PlayerConfig = SerializeHelper.ToEntity<PlayerConfigModel>(repository + "\PlayerConfig.xml");
    
                //加载媒体库
                MediaCollection = SerializeHelper.ToEntity<List<MediaModel>>(repository + "\MediaCollection.xml");
    
                //加载播放列表
                Playlists = SerializeHelper.ToEntity<List<PlaylistModel>>(repository + "\Playlists.xml");
    
                //加载播放队列
                PlayQueues = SerializeHelper.ToEntity<List<PlayQueueModel>>(repository + "\PlayQueues.xml");
    
                //加载播放记录
                PlayRecords = SerializeHelper.ToEntity<List<PlayRecordModel>>(repository + "\PlayRecords.xml");
    
                //加载收藏列表
                Favorites = SerializeHelper.ToEntity<List<FavoriteModel>>(repository + "\Favorites.xml");
    
                //初始化计数器
                AutoStopCounter = PlayerConfig.AutoStopTimeset;
    
                string repository = System.IO.Path.Combine(new string[] { AppDomain.CurrentDomain.BaseDirectory, "Repository" });
                if (!System.IO.Directory.Exists(repository))
                {
                    try
                    {
                        System.IO.Directory.CreateDirectory(repository);
                    }
                    catch (Exception ex)
                    {
                        LogHelper.Error(ex.Message);
                        return;
                    }
                }
    
                //保存播放器设置
                if (IsPlayerConfigChanged)
                {
                    SerializeHelper.ToXml<PlayerConfigModel>(PlayerConfig, repository + "\PlayerConfig.xml");
                }
    
                //保存媒体库
                if (IsMediaCollectionChanged)
                {
                    SerializeHelper.ToXml<List<MediaModel>>(MediaCollection, repository + "\MediaCollection.xml");
                }
    

    2.样式和资源使用方面

    界面元素的背景图片和颜色,字体颜色和字体大小全部存放在Application的资源字典中,可以通过代码动态更新

            <ResourceDictionary>
                <ResourceDictionary.MergedDictionaries>
                    <ResourceDictionary Source="/Resources/Color.xaml"></ResourceDictionary>
                    <ResourceDictionary Source="/Resources/ImageBrush.xaml"></ResourceDictionary>
                    <ResourceDictionary Source="/Resources/LinearGradientBrush.xaml"></ResourceDictionary>
                    <ResourceDictionary Source="/Resources/SolidBrush.xaml"></ResourceDictionary>
                    <ResourceDictionary Source="/Styles/Border.xaml"></ResourceDictionary>
                    <ResourceDictionary Source="/Styles/TextBlock.xaml"></ResourceDictionary>
                </ResourceDictionary.MergedDictionaries>
            </ResourceDictionary>
    

    3.播放控制核心部分

    其实就是处理MediaPlayer的4个事件,MediaOpened,MediaFailed,MediaEnded,以及自己加的事件MediaPositionChanging

            private static void _mainPlayer_MediaOpened(object sender, EventArgs e)
            {
                //设置当前媒体
                CurrentMedia = MediaCollection.Where(w => w.FullName == HttpUtility.UrlDecode(MainPlayer.Source.AbsolutePath).Replace("/", "\")).FirstOrDefault();
            }
    
            private static void _mainPlayer_MediaFailed(object sender, ExceptionEventArgs e)
            {
                PlayerStatus = PlayerStatusEnum.已停止;
                LogHelper.Error(e.ErrorException.Message);
            }
    
            private static void _mainPlayer_MediaEnded(object sender, EventArgs e)
            {
                //添加播放记录
                IsPlayRecordsChanged = true;
                PlayRecords.Add(new PlayRecordModel
                {
                    CreatedAt = DateTime.Now,
                    Media = CurrentMedia.FullName
                });
    
                //设置播放器状态
                PlayerStatus = PlayerStatusEnum.已停止;
    
                //根据播放模式来决定接下要播放的歌曲
                switch (PlayerConfig.PlayMode)
                {
                    case (int)PlayModeEnum.单曲循环:
                        {
                            PlayByRepeat();
                            break;
                        }
                    case (int)PlayModeEnum.列表循环:
                        {
                            PlayByRecycle();
                            break;
                        }
                    case (int)PlayModeEnum.顺序播放:
                        {
                            PlayByOrder();
                            break;
                        }
                    case (int)PlayModeEnum.随机播放:
                        {
                            PlayByRandom();
                            break;
                        }
                }
            }
    
            //CoreHelper.cs中触发媒体播放进度改变事件
            private static void _mainTimer1_Tick()
            {
                if (IsAutoStopOpened && AutoStopCounter <= 0)
                {
                    //停止播放(跨线程处理)
                    MainPlayer.Dispatcher.Invoke(new Action(() =>
                        {
                            Stop();
                        }));
    
                    AutoStopCounter = PlayerConfig.AutoStopTimeset;
                    IsAutoStopOpened = false;
                    return;
                }
    
                if (PlayerStatus == PlayerStatusEnum.播放中)
                {
                    AutoStopCounter -= 1;
                    MediaPositionChanging();
                }
            }
    
            //在UI线程中处理媒体播放进度事件
            CoreHelper.MediaPositionChanging += CoreHelper_MediaPositionChanging;
    
            private void CoreHelper_MediaPositionChanging()
            {
                this.Dispatcher.Invoke(new Action(() =>
                {
                    DisplayAutoStop();
    
                    TimeSpan duration = TimeSpan.FromSeconds(0);
                    if (CoreHelper.MainPlayer.NaturalDuration.HasTimeSpan)
                    {
                        duration = CoreHelper.MainPlayer.NaturalDuration.TimeSpan;
                    }
    
                    double percent = 0;
                    if (duration.TotalSeconds > 0)
                    {
                        percent = CoreHelper.MainPlayer.Position.TotalSeconds / duration.TotalSeconds;
                    }
    
                    //显示媒体播放进度背景
                    gs2.Offset = percent;
                    gs3.Offset = percent;
    
                    //显示媒体播放进度和总持续时间文本
                    _mediaPositionTextBlock.Text = String.Format("{0}/{1}", StringHelper.GetTimeSpanString(CoreHelper.MainPlayer.Position), StringHelper.GetTimeSpanString(duration));
                }));
            }
    

    5.更新媒体库

    这里主要涉及到从文件中读取歌曲信息,所以一个步骤是递归读取文件夹中的文件,第二步骤是读取歌曲信息(通过第三方类库ID3.dll,TagLib.dll),第三步骤是在UI上实时读取状态

            public static void FindMatchedFile(string folder, List<string> extensions, double minFileLength, double maxFileLength, UpdateMediaCollectionEventHandler callback)
            {
                string[] files = System.IO.Directory.GetFiles(folder, "*.*", SearchOption.TopDirectoryOnly);
    
                FileInfo info = null;
                foreach (string file in files)
                {
                    info = new FileInfo(file);
                    if (!extensions.Contains(System.IO.Path.GetExtension(file).ToLower()))
                    {
                        continue;
                    }
    
                    if (info.Length < minFileLength)
                    {
                        continue;
                    }
    
                    if (info.Length > maxFileLength)
                    {
                        continue;
                    }
    
                    //通过委托去通知前端UI
                    callback(file, AddToMediaCollection(info));
                }
    
                string[] directories = System.IO.Directory.GetDirectories(folder, "", SearchOption.TopDirectoryOnly);
                foreach (string directory in directories)
                {
                    FindMatchedFile(directory, extensions, minFileLength, maxFileLength, callback);
                }
            }
    
            public static int AddToMediaCollection(FileInfo file)
            {
                ID3Info id3 = null;
                try
                {
                    id3 = new ID3Info(file.FullName, true);
                }
                catch (Exception ex)
                {
                    LogHelper.Error(ex.Message);
                    return -1;
                }
    
                MediaModel media = new MediaModel();
    
                //创建的时间
                media.CreatedTime = DateTime.Now;
    
                //文件长度
                media.FileLength = file.Length;
    
                //文件全路径
                media.FullName = file.FullName;
    
                //歌曲的专辑
                media.Album = id3.ID3v2Info.GetTextFrame("TALB") ?? "未知专辑";
    
                //歌曲的艺术家
                media.Artists = id3.ID3v2Info.GetTextFrame("TPE1") ?? "未知歌手";
    
                //歌曲的标题
                media.Title = id3.ID3v2Info.GetTextFrame("TIT2") ?? System.IO.Path.GetFileNameWithoutExtension(file.FullName);
    
    
                TagLib.File f = null;
                try
                {
                    f = TagLib.File.Create(file.FullName);
    
                    //歌曲的持续时间
                    media.Duration = f.Properties.Duration.TotalSeconds;
    
                    //保存歌曲封面
                    if (f.Tag.Pictures.Length > 0)
                    {
                        string folder = System.IO.Path.Combine(new string[] { AppDomain.CurrentDomain.BaseDirectory, "Repository", "Cover" });
                        if (!System.IO.Directory.Exists(folder))
                        {
                            System.IO.Directory.CreateDirectory(folder);
                        }
    
                        byte[] bytes = f.Tag.Pictures[0].Data.Data;
    
                        string cover = System.IO.Path.Combine(new string[] { folder, StringHelper.RemoveSpecialCharacters(media.Album) + ".jpg" });
                        using (FileStream fs = new FileStream(cover, FileMode.Create))
                        {
                            fs.Write(bytes, 0, bytes.Length);
                        }
                    }
                }
                catch (Exception ex)
                {
                    LogHelper.Debug(ex.Message);
                }
    
                MediaModel model = MediaCollection.Where(w => w.FullName == file.FullName).FirstOrDefault();
                if (model == null || String.IsNullOrEmpty(model.FullName))
                {
                    MediaCollection.Add(media);
                    LogHelper.Debug("添加了一个媒体:" + file.FullName);
                    return 1;
                }
    
                //更新媒体信息
                model.Album = media.Album;
                model.Artists = media.Artists;
                model.Duration = media.Duration;
                model.FileLength = media.FileLength;
                model.Title = media.Title;
    
                LogHelper.Debug("更新了一个媒体:" + file.FullName);
                return 0;
            }
    
            private List<int> _operations = null;
    
            private void UpdateMediaCollectionTextBlock_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
            {
                UpdateMediaCollectionTextBlock.IsEnabled = false;
                ProcessingMediaTextBlock.Visibility = Visibility.Visible;
                _operations = new List<int>();
    
                //开启线程更新媒体库
                BackgroundWorker worker = new BackgroundWorker();
                worker.DoWork += worker_DoWork;
                worker.RunWorkerCompleted += worker_RunWorkerCompleted;
                worker.RunWorkerAsync();
            }
    
            private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
            {
                UpdateMediaCollectionTextBlock.IsEnabled = true;
                ProcessingMediaTextBlock.Visibility = Visibility.Collapsed;
    
                int succeed = _operations.Count(c => c == 1);
                int updated = _operations.Count(c => c == 0);
                int failed = _operations.Count(c => c < 0);
    
                if (succeed > 0 || updated > 0)
                {
                    CoreHelper.IsMediaCollectionChanged = true;
                }
    
                UiHelper.ShowPrompt(String.Format("新增:{0}个,更新:{1}个,失败:{2}个", succeed, updated, failed));
                _operations = null;
            }
    
  • 相关阅读:
    LightOJ 1132 Summing up Powers(矩阵快速幂)
    hdu 3804 Query on a tree (树链剖分+线段树)
    LightOJ 1052 String Growth && uva 12045 Fun with Strings (矩阵快速幂)
    uva 12304 2D Geometry 110 in 1! (Geometry)
    LA 3263 That Nice Euler Circuit (2D Geometry)
    2013 SCAUCPC Summary
    poj 3321 Apple Tree (Binary Index Tree)
    uva 11796 Dog Distance (几何+模拟)
    uva 11178 Morley's Theorem (2D Geometry)
    动手动脑
  • 原文地址:https://www.cnblogs.com/llyn23/p/4156659.html
Copyright © 2011-2022 走看看