系列文章将分步解读音乐播放器核心业务及代码:

  • [MAUI 项目实战] 音乐播放器(一):概述与架构
  • [MAUI 项目实战] 音乐播放器(二):播放内核
  • [MAUI 项目实战] 音乐播放器(三):界面交互

为什么想起来这个项目了呢?

这是一个Windows Phone 8的老项目,2014年用作为兴趣写了个叫“番茄播放器”的App,顺便提高编程技能。

这个项目的架构历经多次迁移,从WP8到UWP再到Xamarin.Forms。去年底随着MAUI的正式发布,又尝试把它迁移到MAUI上来。

虽然历经数次迁移,但命名空间和播放内核的代码基本没怎么改动,这个项目随着解决方案升级,依赖库、API调用方式的变更,见证了微软在移动互联网领域的动荡。我偶然发现8年前提交到微软商店的App,竟然还能够打开下载页面 - Microsoft应用商店,但由于我手边没有一台Windows Phone设备,也没法让它在任何的模拟器中跑起来。也只能从商店截图和源代码中重温这个物件和那段时光。

这个项目现在已经没有任何的商业价值,但我知道它对于我意味着什么,曾给我带来的在编程时的那种欣喜和享受,可以说真正让我知道什么叫“Code 4 Fun”——编程带来的快乐,对于那时刚进入社会的我,树立信心和坚持道路有莫大的帮助。

这个项目可能从来就没有价值。那么写博文和开源能发挥多少价值就算多少吧。

当下在.Net平台上有不少开源的音频封装库,如Plugin.Maui.Audio,本项目没有依赖任何音频的第三方库,希望大家以学习的态度交流,如果您有更好的实现方式,欢迎在文章下留言。因为代码年代久远且近年来没有重构,C#语言版本和代码写法上会有不少繁冗,这里还要向大家说声抱歉。

架构

使用Abp框架,我之前写过如何 将Abp移植进.NET MAUI项目,本项目也是按照这篇博文完成项目搭建。

跨平台

使用.NET MAU实现跨平台支持,从Xamarin.Forms移植的应用可以在Android和iOS平台上顺利运行。

播放内核是由分部类提供跨平台支持的,在Xamarin.Forms时代,需要维护不同平台的项目,MAUI是单个项目支持多个平台。
MAUI 应用项目包含 一个 Platform 文件夹,每个子文件夹表示 .NET MAUI 可以面向的平台

每个文件夹代表了每个平台特定的代码, 在默认的情况下 编译阶段仅仅会编译当前选择的平台文件夹代码。

这属于利用分部类和方法创建平台特定内容,详情请参考官方文档

IMusicControlService在项目中分部类实现:

MatoMusic.Core\Impl\MusicControlService.cs
MatoMusic.Core\Platforms\Android\MusicControlService.cs
MatoMusic.Core\Platforms\iOS\MusicControlService.cs
MatoMusic.Core\Platforms\Windows\MusicControlService.cs

核心类

在设计播放内核时,从用户的交互路径思考,抽象出了曲目管理器IMusicInfoManager和播放控制服务IMusicControlService

播放器行为和曲目操作行为在各自领域相互隔离,通过生产-消费模型,数据流转和消息通知冒泡协调一致。尽量规避了大规模使用线程锁,以及复杂的线程同步逻辑。在跨平台方案中,通过分部类实现了这些接口,类图如下:

音乐播放相关服务类MusicRelatedService是播放控制服务的一层封装,在实际播放器业务逻辑上,利用封装的代码能更方便的完成任务。

项目遵循MVVM设计模式,MusicRelatedViewModel作为音乐播放相关ViewModel的基类,包含了曲目管理器IMusicInfoManager和播放控制服务IMusicControlService对象,通过双向绑定开发者可以从表现层轻松进行音乐控制和曲目访问

ViewModelBase是个基础类,它继承自AbpServiceBase,封装了Abp框架通用功能的调用。比如Setting、Localization和UnitOfWork功能。并且实现了INotifyPropertyChanged,它为绑定类型的每个属性提供变更事件。

核心类图如下

定义

  • Queue - 歌曲队列,当前用于播放歌曲的有序列表
  • Playlist - 歌单,存储可播放内容的集合,用于收藏曲目,添加到我的最爱等。
  • PlaylistEntry - 歌单条目,可播放内容,关联一个本地音乐或在线音乐信息
  • MyFavourite - 我的最爱,一个id为1的特殊的歌单,不可编辑和删除,用于记录点亮歌曲小红心
  • MusicInfo - 曲目信息
  • AlbumInfo - 专辑信息
  • ArtistInfo - 艺术家信息
  • BillboardInfo - 排行榜,在线音乐歌单

曲目

曲目包含:

  • Title - 音乐标题
  • AlbumTitle - 专辑标题
  • GroupHeader - 标题头,用于列表分组显示的依据
  • Url - 音频文件地址
  • Artist - 艺术家
  • Genre - 流派
  • IsFavourite - 是否已“我最喜爱”
  • IsPlaying - 是否正在播放
  • AlbumArtPath - 封面图片
  • Duration - 歌曲总时长

如果配合模糊搜索控件,需要实现IClueObject,使用方式请参考AutoComplete控件

public class MusicInfo : ObservableObject, IBasicInfo, IClueObject
{ .. }
public List<string> ClueStrings
{get{var result = new List<string>();result.Add(Title);result.Add(Artist);result.Add(AlbumTitle);return result;}
}

它继承自ObservableObject,构造函数中注册属性更改事件
IsFavourite更改时,将调用MusicInfoManager将当前曲目设为或取消设为“我最喜爱”

private void MusicInfo_PropertyChanged(object sender, PropertyChangedEventArgs e)
{var MusicInfoManager = IocManager.Instance.Resolve<MusicInfoManager>();if (e.PropertyName == nameof(IsFavourite)){if (IsFavourite){MusicInfoManager.CreatePlaylistEntryToMyFavourite(this);}else{MusicInfoManager.DeletePlaylistEntryFromMyFavourite(this);}}
}

曲目集合

曲目集合是歌单,音乐专辑或者艺术家(演唱者)创作的音乐的抽象,它包含:

  • Title - 标题,歌单,音乐专辑或者艺术家名称
  • GroupHeader - 标题头,用于列表分组显示的依据
  • Musics - 曲目信息集合
  • AlbumArtPath - 封面图片
  • Count - 歌曲集合曲目数
  • Time - 歌曲集合总时长

它继承自ObservableObject

AlbumInfoArtistInfoPlaylistInfoBillboardInfo 都是曲目集合的子类

Musics是曲目集合的内容,类型为ObservableCollection<MusicInfo>,双向绑定时提供队列变更事件。

集合曲目数和集合总时长依赖这个变量

public int Count => Musics.Count();
public string Time
{get{var totalSec = Math.Truncate((double)Musics.Sum(c => (long)c.Duration));var totalTime = TimeSpan.FromSeconds(totalSec);var time = totalTime.ToString("g");return time;}
}

当集合内容增删时,同步通知歌曲集合曲目数以及总时长变更

private void _musics_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{if (e.Action == NotifyCollectionChangedAction.Remove || e.Action == NotifyCollectionChangedAction.Add){RaisePropertyChanged(nameof(Time));RaisePropertyChanged(nameof(Count));}
}

GroupHeader标题头,一般取得是标题的首字母,若标题为中文,则使用Microsoft.International.Converters.PinYinConverter获取中文第一个字的拼音首字母,跨平台实现方式如下:

private partial string GetGroupHeader(string title)
{string result = string.Empty;if (!string.IsNullOrEmpty(title)){if (Regex.IsMatch(title.Substring(0, 1), @"^[\u4e00-\u9fa5]+$")){try{var chinese = new ChineseChar(title.First());result = chinese.Pinyins[0].Substring(0, 1);}catch (Exception ex){return string.Empty;}}else{result = title.Substring(0, 1);}}return result;}

GroupHeader用于列表分组显示的内容将在后续文章中阐述

数据库

应用程序里使用Sqlite,作为播放列表,歌单,设置等数据的持久化
,使用CodeFirst方式用EF初始化Sqlite数据库文件:mato.db

在MatoMusic.Core项目的appsettings.json中添加本地sqlite连接字符串

  "ConnectionStrings": {"Default": "Data Source=file:{0};"},...

这里文件是一个占位符,通过代码hardcode到配置文件

在MatoMusicCoreModule.cs中,重写PreInitialize并设置Configuration.DefaultNameOrConnectionString:

public override void PreInitialize()
{LocalizationConfigurer.Configure(Configuration.Localization);Configuration.Settings.Providers.Add<CommonSettingProvider>();string documentsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), MatoMusicConsts.LocalizationSourceName);var configuration = AppConfigurations.Get(documentsPath, development);var connectionString = configuration.GetConnectionString(MatoMusicConsts.ConnectionStringName);var dbName = "mato.db";string dbPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), MatoMusicConsts.LocalizationSourceName, dbName);Configuration.DefaultNameOrConnectionString = String.Format(connectionString, dbPath);base.PreInitialize();
}

接下来定义实体类

播放队列

定义于\MatoMusic.Core\Models\Entities\Queue.cs

public class Queue : FullAuditedEntity<long>
{[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]public override long Id { get; set; }public long MusicInfoId { get; set; }public int Rank { get; set; }public string MusicTitle { get; set; }
}

歌单

定义于\MatoMusic.Core\Models\Entities\Playlist.cs

public class Playlist : FullAuditedEntity<long>
{[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]public override long Id { get; set; }public string Title { get; set; }public bool IsHidden { get; set; }public bool IsRemovable { get; set; }public ICollection<PlaylistItem> PlaylistItems { get; set; }
}

歌单条目

定义于\MatoMusic.Core\Models\Entities\PlaylistItem.cs

public class PlaylistItem : FullAuditedEntity<long>
{[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]public override long Id { get; set; }public int Rank { get; set; }public long PlaylistId { get; set; }[ForeignKey("PlaylistId")]public Playlist Playlist { get; set; }public string MusicTitle { get; set; }public long MusicInfoId { get; set; }
}

配置

数据库上下文对象MatoMusicDbContext定义如下

public class MatoMusicDbContext : AbpDbContext
{//Add DbSet properties for your entities...public DbSet<Queue> Queue { get; set; }public DbSet<Playlist> Playlist { get; set; }public DbSet<PlaylistItem> PlaylistItem { get; set; }...

MatoMusic.EntityFrameworkCore是应用程序数据库的维护和管理项目,依赖于Abp.EntityFrameworkCore。
在MatoMusic.EntityFrameworkCore项目中csproj文件中,引用下列包

<PackageReference Include="Abp.EntityFrameworkCore" Version="7.4.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="1.1.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.0">

在该项目MatoMusicEntityFrameworkCoreModule.cs 中,将注册上下文对象,并在程序初始化运行迁移,此时将在设备上生成mato.db文件

public override void PostInitialize()
{Helper.WithDbContextHelper.WithDbContext<MatoMusicDbContext>(IocManager, RunMigrate);if (!SkipDbSeed){SeedHelper.SeedHostDb(IocManager);}
}public static void RunMigrate(MatoMusicDbContext dbContext)
{dbContext.Database.Migrate();
}

项目地址

GitHub:MatoMusic

下一章将介绍播放器核心功能:播放服务类

[MAUI 项目实战] 音乐播放器(一):概述与架构相关推荐

  1. Vue项目实战——音乐播放器

    界面概览 体验地址 http://duing.site:888/MusicPlayer/#/discovery 源码已上传GitHub https://github.com/Du-ing/Vue_Mu ...

  2. Andriod小项目——在线音乐播放器

    Andriod小项目--在线音乐播放器 转载请注明:http://blog.csdn.net/sunkes/article/details/51189189 Android在线音乐播放器 从大一开始就 ...

  3. 怎么添加本地音乐_展示 | 传一学员优秀项目之音乐播放器

    ?点击上方蓝字关注我们 前言:随着移动互联网在国内的飞速发展,移动音乐作为国内最受欢迎的娱乐休闲方式之一也得到飞速的发展,有包括酷狗.QQ.酷我等在内的全用户覆盖的音乐软件,又有像网易云.虾米等在内的 ...

  4. 安卓实训项目:音乐播放器3.0——实训报告3

    一.功能要求 在基于存储卡音乐播放器V0.2基础上,增加功能: 添加一个音乐播放列表(显示歌名) – 单击某一首音乐就立刻播放,播放进度清零,显示当前音乐的播放时长. 添加一个[上一首]与[下一首]按 ...

  5. [转]大话企业级Android应用开发实战 音乐播放器的开发

    29.2  创 建 界 面 在main.xml中添按钮等控件,完成音乐播放器的界面,代码如下: main.xml <?xml version="1.0" encoding=& ...

  6. php项目网页音乐播放器插件,10个免费开源的JS音乐播放器插件

    10个免费开源的JS音乐播放器插件 三月 24, 2015 评论 (3) Sponsor 音乐播放器在网页设计中有时候会用到,比如一些时尚类.音乐或影视类等项目,但这些网页播放器插件比较少见,所以这里 ...

  7. Android项目:音乐播放器

    登录页面 首先是系统登录界面,用户安装app启动后最先看到的界面,需要使用正确的用户名和密码才能进入播放器,再该页面可以选择是否记住密码和自动登录,如果只是选择记住密码,退出应用后再次进入账号密码将会 ...

  8. 安卓实训项目:音乐播放器2.0——实训报告2

    (一)功能要求 添加一个进度条,动态显示音乐的播放进度.----已完成 添加一个标签显示音乐播放的当前位置(格式--mm:ss).----已完成 添加一个标签显示音乐的播放时长(格式--mm:ss). ...

  9. 梦之翼团队项目(音乐播放器)

    一 设计思路 整体概括: 当我们得到这个项目时,也感觉到了迷茫,不过经过查阅资料,我们还是克服了,我们想做一个类似于酷狗音乐的那样,但是只是能力有限,做到了现在这个程度,我们用的是程序自身的一个插件, ...

最新文章

  1. linux根-文件系统-目录管理-文件管理-用户及权限详解-用户组-用户管理-权限管理...
  2. 【C/C++】代码换行问题
  3. ElasticSearch 索引、更新和删除数据
  4. XX银行 机器学习平台使用情况访谈总结
  5. 最新最全的微信小程序入门学习教程,微信小程序零基础入门到精通
  6. 会员直推奖php程序_直推奖+对碰奖+*奖+互助奖+见点奖
  7. 网络广告的效果测定与评估
  8. APISpace 绕口令API接口 免费好用
  9. linux ap 模式,无线AP是什么,客户端模式(apclient)是什么意思?
  10. 皕杰报表之小程序代码质量检测
  11. L1 操作系统的启动
  12. 【C标准库】详解feof函数与EOF
  13. 在gmail中使用邮件模板功能
  14. Unix操作系统设计第一章学习
  15. 查看库文件编译时所使用的GCC版本号
  16. 直接插入排序算法视频
  17. 使用免费的CDN平台jsDelivr搭建个人cdn资源网盘
  18. Java 求解岛屿数量
  19. Tesseract-OCR 字库训练
  20. 我和滴滴打车司机的那些事

热门文章

  1. TF卡里删掉文件后内存没变大_不用第三方,手机自带软件也能清扫内存!教你4个正确清理技巧...
  2. 【Redis系列】缓存击穿、穿透、雪崩解决方案详解
  3. 与众不同 独树一帜,传智播客2018春季课程发布会在京举行
  4. springboot定制和关闭banner
  5. IOS开发入门(6)-自动布局(1)
  6. JavaScript 判断是否是数字 isFinite() Number.isFinite()
  7. 张凯龙 西北工业大学计算机学院,张凯龙的个人主页-西北工业大学教师个人主页...
  8. 7-45 水果忍者 (30 point(s))
  9. 论华为云的气质与修养
  10. Ribo-seq的下游分析方法1-ORFquant以及RiboQC