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;
            }
    
  • 相关阅读:
    JS创建类和对象(好多方法哟!)
    BMI身体质量指数计算公式
    点击button显示文字
    xml中设置button的背景颜色
    Android layout的属性介绍
    eclipse中自动补齐代码设置
    android开发中常用的快捷键
    eclipse修改Android工程图标显示
    Android运行报错
    读《人月神话》有感
  • 原文地址:https://www.cnblogs.com/llyn23/p/4156659.html
Copyright © 2011-2022 走看看