[MAUI 项目实战] 音乐播放器(一):概述与架构
系列文章将分步解读音乐播放器核心业务及代码:
- [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
AlbumInfo
,ArtistInfo
,PlaylistInfo
,BillboardInfo
都是曲目集合的子类
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 项目实战] 音乐播放器(一):概述与架构相关推荐
- Vue项目实战——音乐播放器
界面概览 体验地址 http://duing.site:888/MusicPlayer/#/discovery 源码已上传GitHub https://github.com/Du-ing/Vue_Mu ...
- Andriod小项目——在线音乐播放器
Andriod小项目--在线音乐播放器 转载请注明:http://blog.csdn.net/sunkes/article/details/51189189 Android在线音乐播放器 从大一开始就 ...
- 怎么添加本地音乐_展示 | 传一学员优秀项目之音乐播放器
?点击上方蓝字关注我们 前言:随着移动互联网在国内的飞速发展,移动音乐作为国内最受欢迎的娱乐休闲方式之一也得到飞速的发展,有包括酷狗.QQ.酷我等在内的全用户覆盖的音乐软件,又有像网易云.虾米等在内的 ...
- 安卓实训项目:音乐播放器3.0——实训报告3
一.功能要求 在基于存储卡音乐播放器V0.2基础上,增加功能: 添加一个音乐播放列表(显示歌名) – 单击某一首音乐就立刻播放,播放进度清零,显示当前音乐的播放时长. 添加一个[上一首]与[下一首]按 ...
- [转]大话企业级Android应用开发实战 音乐播放器的开发
29.2 创 建 界 面 在main.xml中添按钮等控件,完成音乐播放器的界面,代码如下: main.xml <?xml version="1.0" encoding=& ...
- php项目网页音乐播放器插件,10个免费开源的JS音乐播放器插件
10个免费开源的JS音乐播放器插件 三月 24, 2015 评论 (3) Sponsor 音乐播放器在网页设计中有时候会用到,比如一些时尚类.音乐或影视类等项目,但这些网页播放器插件比较少见,所以这里 ...
- Android项目:音乐播放器
登录页面 首先是系统登录界面,用户安装app启动后最先看到的界面,需要使用正确的用户名和密码才能进入播放器,再该页面可以选择是否记住密码和自动登录,如果只是选择记住密码,退出应用后再次进入账号密码将会 ...
- 安卓实训项目:音乐播放器2.0——实训报告2
(一)功能要求 添加一个进度条,动态显示音乐的播放进度.----已完成 添加一个标签显示音乐播放的当前位置(格式--mm:ss).----已完成 添加一个标签显示音乐的播放时长(格式--mm:ss). ...
- 梦之翼团队项目(音乐播放器)
一 设计思路 整体概括: 当我们得到这个项目时,也感觉到了迷茫,不过经过查阅资料,我们还是克服了,我们想做一个类似于酷狗音乐的那样,但是只是能力有限,做到了现在这个程度,我们用的是程序自身的一个插件, ...
最新文章
- linux根-文件系统-目录管理-文件管理-用户及权限详解-用户组-用户管理-权限管理...
- 【C/C++】代码换行问题
- ElasticSearch 索引、更新和删除数据
- XX银行 机器学习平台使用情况访谈总结
- 最新最全的微信小程序入门学习教程,微信小程序零基础入门到精通
- 会员直推奖php程序_直推奖+对碰奖+*奖+互助奖+见点奖
- 网络广告的效果测定与评估
- APISpace 绕口令API接口 免费好用
- linux ap 模式,无线AP是什么,客户端模式(apclient)是什么意思?
- 皕杰报表之小程序代码质量检测
- L1 操作系统的启动
- 【C标准库】详解feof函数与EOF
- 在gmail中使用邮件模板功能
- Unix操作系统设计第一章学习
- 查看库文件编译时所使用的GCC版本号
- 直接插入排序算法视频
- 使用免费的CDN平台jsDelivr搭建个人cdn资源网盘
- Java 求解岛屿数量
- Tesseract-OCR 字库训练
- 我和滴滴打车司机的那些事
热门文章
- TF卡里删掉文件后内存没变大_不用第三方,手机自带软件也能清扫内存!教你4个正确清理技巧...
- 【Redis系列】缓存击穿、穿透、雪崩解决方案详解
- 与众不同 独树一帜,传智播客2018春季课程发布会在京举行
- springboot定制和关闭banner
- IOS开发入门(6)-自动布局(1)
- JavaScript 判断是否是数字 isFinite() Number.isFinite()
- 张凯龙 西北工业大学计算机学院,张凯龙的个人主页-西北工业大学教师个人主页...
- 7-45 水果忍者 (30 point(s))
- 论华为云的气质与修养
- Ribo-seq的下游分析方法1-ORFquant以及RiboQC