重做了一下我的音樂播放器


前些天看到新聞說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;
        }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM