
简介这是一份面向C#初学者与.NET跨平台开发者的实战型学习资源提供基于.NET MAUI框架构建的多平台在线音乐播放器完整源码帮助开发者掌握跨平台UI开发、网络音频流处理及MVVM架构实践。资源共345个文件包含247个C#核心逻辑文件如MediaPlayerService、PlayerService、各音乐平台Provider、19个XAML界面文件、9个.csproj项目配置文件、45张PNG图标与UI资源以及sln解决方案、JSON配置、YML CI脚本等压缩包仅1.33MB结构清晰、模块解耦明确。已有613人下载学习适合通过真实项目理解MAUI生命周期管理、异步HTTP请求、音频播放控制与多平台适配要点。代码采用极简设计风格涵盖网易云、酷狗、酷我、咪咕四大主流音乐API接入方案附带SettingPage与SearchResult页面的ViewModel实现是深入学习C#现代应用开发与跨端工程组织的优质范例。1. 这不是又一个“Hello World” MAUI Demo它真能跑在 Windows、Android、macOS 上播酷我/网易云/咪咕的在线音频流你试过用同一套 C# 代码在 Windows 上点开播放器切到 Android 手机上继续播同一首歌再扔给 macOS 笔记本无缝续播吗不是模拟器不是 WebView 壳是原生控件、原生音频管道、原生通知栏控制——这个C#基于.NET MAUI开发的多平台在线音乐播放器源码.zip就干这事。它不是玩具项目从KuWoMusicProvider.cs到NetEaseMusicProvider.cs再到MiGuMusicProvider.cs三个主流中文音乐平台的 API 封装已实装从MediaPlayerService.cs到PlayerService.cs底层音频生命周期管理已解耦SettingPageViewModel.cs和SearchResultPageViewModel.cs说明它连用户偏好和搜索结果分页都做了 MVVM 规范落地。适合两类人一是正在评估 .NET MAUI 能否扛住真实业务场景的团队技术负责人二是想甩掉 Xamarin.Forms 迁移包袱、用纯 C# 写跨平台音视频 App 的一线开发者。它不教你怎么写Label它直接告诉你当MediaElement在 iOS 上静音失败、Android 上后台播放被系统杀掉、Windows 上 WASAPI 设备切换卡顿——你该改哪行Player.xaml.cs重写哪个MediaPlayerService的ResumePlaybackAsync()。2. 从解压到真机运行五步走通 MAUI 多平台构建链路2.1 解压后第一眼必须确认的三件事拿到C#基于.NET MAUI开发的多平台在线音乐播放器源码.zip后别急着双击.sln。先打开终端PowerShell / Terminal进到解压目录执行ls -la你必须看到ListenTogether-main/目录不是ListenTogether-main.zip或其他嵌套压缩包该目录下存在ListenTogether.sln解决方案文件ListenTogether-main/Platforms/子目录里有Android/,iOS/,Windows/,MacCatalyst/四个文件夹缺任何一个说明 MAUI SDK 未完整安装或项目模板损坏。提示如果只看到src/或app/目录但没Platforms/大概率是下载了 GitHub 源码 ZIP 但没拉取 submodule比如Maui.Controls或CommunityToolkit.Maui此时需执行git submodule update --init --recursive——但本项目压缩包已含全部源码无需 git 操作直接检查文件结构即可。2.2 环境检查MAUI SDK 版本与目标平台工具链本项目基于 .NET 7 或 .NET 8 构建从csproj中TargetFrameworknet7.0-android;net7.0-ios;net7.0-maccatalyst;net7.0-windows10.0.19041/TargetFramework可推断。验证本地环境dotnet --list-sdks # 输出应包含类似7.0.400 [/usr/share/dotnet/sdk] 或 8.0.100 dotnet workload list | findstr maui # Windows 下应看到maui (Microsoft.NET.Workload.MAUI) 7.0.400/8.0.100Android 开发者需额外确认JDK 17 已安装java -version输出17.x.xAndroid SDK Platform-Tools 和 Build-Tools 33 已通过 Visual Studio Installer 或sdkmanager安装ANDROID_HOME环境变量指向 SDK 根目录如C:\Users\XXX\AppData\Local\Android\Sdk。macOS 用户注意Xcode 14.3 必须已安装且xcode-select --install成功否则dotnet build -t:Run -f net7.0-maccatalyst会卡在mtouch阶段。2.3 编译前必改的两处硬编码配置打开ListenTogether-main/ListenTogether/Platforms/Android/AndroidManifest.xml找到application android:usesCleartextTraffictrue ...⚠️ 生产环境必须删掉android:usesCleartextTraffictrue——否则 Android 9 会拒绝 HTTP 请求酷我、咪咕部分接口仍为 HTTP。对应地KuWoMusicProvider.cs中所有http://开头的 URL 必须替换为https://或在AndroidManifest.xml中添加domain-config白名单见 4.2 节。再打开ListenTogether-main/ListenTogether/App.xaml.cs检查初始化逻辑public partial class App : Application { public App() { InitializeComponent(); // 注意此处若 Provider 初始化顺序错乱会导致启动时 NetworkException // 正确顺序先注册服务再设置 MainPage Microsoft.Extensions.DependencyInjection.ServiceCollection serviceCollection new(); serviceCollection.AddSingletonMediaPlayerService(); serviceCollection.AddSingletonKuWoMusicProvider(); serviceCollection.AddSingletonNetEaseMusicProvider(); serviceCollection.AddSingletonMiGuMusicProvider(); // ⚠️ 错误写法serviceCollection.AddSingletonPlayerService(); // PlayerService 依赖 MediaPlayerService必须后注册 ... } }PlayerService依赖MediaPlayerService若PlayerService先注册MediaPlayerService尚未注入运行时NullReferenceException会发生在Player.xaml.cs的OnAppearing()中。2.4 四平台一键构建命令与输出验证进入ListenTogether-main/目录后按平台执行构建以 Release 模式# Windows需 VS 2022 17.4 或 .NET SDK 7.0.400 dotnet build -c Release -f net7.0-windows10.0.19041 # 输出路径bin\Release\net7.0-windows10.0.19041\publish\ListenTogether.exe # Android生成 APK dotnet build -c Release -f net7.0-android -r android-arm64 # 输出路径bin\Release\net7.0-android\android-arm64\publish\ListenTogether.Android.dll → 用 dotnet publish 打包成 APK # iOS需 macOS Xcode dotnet build -c Release -f net7.0-ios -r ios-arm64 # 输出路径bin\Release\net7.0-ios\ios-arm64\publish\ListenTogether.iOS.dll → Xcode 导入后 Archive # macOSMac Catalyst dotnet build -c Release -f net7.0-maccatalyst -r maccatalyst-x64 # 输出路径bin\Release\net7.0-maccatalyst\maccatalyst-x64\publish\ListenTogether.app验证是否成功Windows双击ListenTogether.exe主界面出现「搜索框 播放控制栏」即通过Androidadb install安装 APK 后图标显示「耳机音符」点击启动无闪退macOSopen ListenTogether.appDock 出现应用图标菜单栏显示「ListenTogether」而非「dotnet」iOSXcode Organizer 中 Archive 成功Export 后 IPA 可安装至真机。注意首次构建 Android 时dotnet build会自动下载android-ndk-r25c和android-sdk组件耗时 5–15 分钟勿中断。3. 音频流核心链路拆解从搜索到播放的七层调用栈3.1 搜索请求如何穿透三层 Provider 抽象用户在SearchResultPage.xaml输入关键词触发SearchResultPageViewModel.cs的SearchCommand// SearchResultPageViewModel.cs public ICommand SearchCommand new Command(async () { if (string.IsNullOrWhiteSpace(SearchText)) return; // 关键此处不直接调用某 Provider而是由 ServiceLocator 解析 var provider ServiceHelper.GetMusicProvider(SelectedProvider); // SelectedProvider 是枚举KuWo / NetEase / MiGu var results await provider.SearchAsync(SearchText, PageIndex); SearchResults new ObservableCollectionMusicItem(results); });ServiceHelper.GetMusicProvider()实际返回的是IMusicProvider接口实现其注册在App.xaml.cs的 DI 容器中。以KuWoMusicProvider.cs为例SearchAsync()实现public async TaskIEnumerableMusicItem SearchAsync(string keyword, int page 1) { // Step 1构造酷我搜索 URL含 Referer 防盗链 var url $https://www.kuwo.cn/api/www/search/searchMusicBykeyWord?key{Uri.EscapeDataString(keyword)}pn{page}rn20; // Step 2发送带 Header 的 HttpClient 请求关键 Header using var client new HttpClient(); client.DefaultRequestHeaders.Add(Referer, https://www.kuwo.cn/); client.DefaultRequestHeaders.Add(User-Agent, Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36); // Step 3解析 JSON 响应酷我返回的是标准 JSON非 HTML var response await client.GetStringAsync(url); var searchResult JsonSerializer.DeserializeKuWoSearchResponse(response); // Step 4映射为统一 MusicItem 模型屏蔽平台差异 return searchResult.Data?.List?.Select(x new MusicItem { Id x.MusicId.ToString(), Title x.Name, Artist x.Artist, Album x.Album, Duration TimeSpan.FromSeconds(x.Duration), CoverUrl $https://www.kuwo.cn/star/album/{x.AlbumId}/cover }) ?? Enumerable.EmptyMusicItem(); }MusicItem是跨平台统一模型所有 Provider 都必须将其搜索结果映射至此确保SearchResultPage.xaml的CollectionView绑定无歧义。3.2 播放控制如何绕过 MAUI MediaElement 的平台限制Player.xaml.cs中的PlayButton_Clicked并未直接操作MediaElement而是委托给PlayerServiceprivate async void PlayButton_Clicked(object sender, EventArgs e) { // 不写mediaElement.Source xxx; mediaElement.Play(); // 而是 await playerService.PlayAsync(currentMusicItem); }PlayerService.cs内部逻辑public async Task PlayAsync(MusicItem item) { // Step 1预加载音频元数据时长、采样率等 var metadata await GetAudioMetadataAsync(item.Url); // 用 FFmpeg.Wasm 或原生 libavcodecAndroid/iOS 用 JNI/ObjC 封装 // Step 2交由平台专属 MediaPlayerService 处理 await mediaPlayerService.PlayAsync(item.Url, metadata); // Step 3触发 UI 更新MVVM CurrentItem item; IsPlaying true; OnPropertyChanged(nameof(CurrentItem)); OnPropertyChanged(nameof(IsPlaying)); }MediaPlayerService.cs是平台抽象基类各平台实现Android/Services/MediaPlayerService.cs使用ExoPlayer比原生MediaPlayer更稳定支持 DASH/HLSiOS/Services/MediaPlayerService.cs封装AVPlayerAVAudioSession处理后台播放、AirPlayWindows/Services/MediaPlayerService.cs调用Windows.Media.Core.MediaSourceMediaPlayerElementWASAPI 低延迟模式MacCatalyst/Services/MediaPlayerService.cs桥接AVFoundation。这样设计避免了MediaElement在 iOS 上无法后台播放、Android 上无法精确 seek 的黑匣子问题。3.3 后台播放与锁屏控制的三端差异化实现平台后台播放启用方式锁屏控制支持通知栏进度条AndroidAndroidManifest.xml中声明service android:name.Services.BackgroundAudioService android:exportedfalse /StartForeground()需MediaSessionCompatMediaStyle通知NotificationCompat.BuildersetProgress()iOSInfo.plist中启用audiobackground mode AVAudioSession.setActive(true)MPRemoteCommandCenter注册playCommand,pauseCommandMPNowPlayingInfoCenter设置elapsedTime,durationWindowsPackage.appxmanifest中声明backgroundTaskscapability SystemMediaTransportControlsSystemMediaTransportControls自动绑定SystemMediaTransportControls.DisplayUpdater更新Thumbnail,PropertiesPlayerService.cs中StartBackgroundPlayback()方法会根据DeviceInfo.Platform分支调用对应平台服务而非统一逻辑——这是跨平台音视频最易翻车的点也是本项目已踩坑并修复的关键。4. 避坑五个让开发者凌晨三点还在查日志的真实问题4.1 现象Android 真机安装后点击图标闪退Logcat 显示Java.Lang.ClassNotFoundException: androidx.media.session.MediaSessionCompat原因AndroidManifest.xml中未正确声明androidx.media依赖或csproj中PackageReference版本冲突如Xamarin.Essentials与Microsoft.Maui.Controls的androidx子模块版本不一致。解决确保ListenTogether-main/ListenTogether.csproj包含PackageReference IncludeMicrosoft.Maui.Controls Version7.0.99 / PackageReference IncludeMicrosoft.Maui.Controls.Compatibility Version7.0.99 / PackageReference IncludeXamarin.Essentials Version1.8.1 /在AndroidManifest.xmlapplication内添加meta-data android:nameandroidx.media.session.MediaSessionCompat android:valuetrue /清理dotnet clean -f net7.0-android 删除bin/obj目录后重试。4.2 现象iOS 真机上搜索正常但点击播放按钮无声音Xcode Console 输出AVAudioSession is not active原因AVAudioSession未在AppDelegate.cs中正确激活或未设置AVAudioSessionCategoryPlayback类别。解决修改ListenTogether-main/Platforms/iOS/AppDelegate.cspublic override bool FinishedLaunching(UIApplication app, NSDictionary options) { // 在 base.FinishedLaunching 之后立即设置 var session AVAudioSession.SharedInstance(); NSError error; session.SetCategory(AVAudioSessionCategory.Playback, AVAudioSessionMode.Default, out error); session.SetActive(true, out error); // ⚠️ 必须 setActive(true) return base.FinishedLaunching(app, options); }4.3 现象Windows 上播放 30 秒后自动暂停Event Viewer 记录Audio Endpoint Manager: Device was unplugged原因WASAPI 设备在播放期间被系统判定为闲置如用户切换焦点、休眠唤醒MediaPlayerElement未监听MediaFailed事件重连。解决在Windows/Player.xaml.cs中添加mediaPlayerElement.MediaFailed (s, e) { // 捕获 WASAPI 设备丢失错误 if (e.Exception.HResult unchecked((int)0x80040265)) // AUDCLNT_E_DEVICE_INVALIDATED { // 重建 MediaPlayerElement var newPlayer new MediaPlayerElement(); newPlayer.Source mediaPlayerElement.Source; newPlayer.AutoPlay true; // 替换 UI 中的旧控件 Content newPlayer; } };4.4 现象macOS 上首次播放正常第二次播放卡在缓冲Console 显示AVPlayerItemStatusFailed原因AVPlayerItem缓存未清理重复使用同一AVPlayerItem实例导致状态机混乱。解决MacCatalyst/Services/MediaPlayerService.cs中PlayAsync()改为public async Task PlayAsync(string url, AudioMetadata metadata) { // 每次播放前销毁旧 playerItem playerItem?.CancelPendingSeeks(); playerItem?.StatusChanged - OnPlayerItemStatusChanged; playerItem?.Dispose(); playerItem new AVPlayerItem(NSUrl.FromString(url)); playerItem.StatusChanged OnPlayerItemStatusChanged; player.ReplaceCurrentItemWithPlayerItem(playerItem); }4.5 现象所有平台搜索结果为空Fiddler 抓包发现请求返回403 Forbidden原因酷我/网易云/咪咕均校验Referer和User-Agent而HttpClient默认不带这些 Header。解决统一在BaseMusicProvider.cs所有 Provider 的基类中强制设置protected virtual HttpClient CreateHttpClient() { var client new HttpClient(); client.DefaultRequestHeaders.Referrer new Uri(https://www.example.com/); // 按平台设不同 Referrer client.DefaultRequestHeaders.UserAgent.ParseAdd(Mozilla/5.0 (compatible; ListenTogether/1.0)); return client; }并在各 Provider 中覆写CreateHttpClient()设置平台专属Referer如酷我设为https://www.kuwo.cn/网易云设为https://music.163.com/。5. 深度定制替换默认音频引擎为 LibVLCSharp 实现 HLS/DASH 流支持5.1 为什么原生 MediaPlayerService 不够用MediaPlayerService基于各平台原生 API对 HLS.m3u8和 DASH.mpd支持有限AndroidExoPlayer支持但需手动集成ExoPlayer.ExtensionsiOSAVPlayer支持 HLS但不支持自定义 DRM如 WidevineWindowsMediaPlayerElement对 HLS 支持不稳定常卡在Buffering状态。而LibVLCSharp是 VLC 官方维护的跨平台 .NET 绑定支持全格式HLS/DASH/RTMP/FLV、硬件加速、自定义解码器、DRM 插件且 C# API 与MediaPlayerService接口兼容。5.2 替换步骤四平台同步接入 LibVLCSharp第一步添加 NuGet 包所有平台修改ListenTogether-main/ListenTogether.csproj添加PackageReference IncludeLibVLCSharp Version4.0.0 / PackageReference IncludeLibVLCSharp.WinForms Version4.0.0 Condition$(TargetFramework) net7.0-windows10.0.19041 / PackageReference IncludeLibVLCSharp.Android Version4.0.0 Condition$(TargetFramework) net7.0-android / PackageReference IncludeLibVLCSharp.iOS Version4.0.0 Condition$(TargetFramework) net7.0-ios / PackageReference IncludeLibVLCSharp.Mac Version4.0.0 Condition$(TargetFramework) net7.0-maccatalyst /第二步重写 MediaPlayerService以 Android 为例创建ListenTogether-main/Platforms/Android/Services/VLCAudioPlayerService.cspublic class VLCAudioPlayerService : MediaPlayerService { private LibVLC _libVLC; private MediaPlayer _mediaPlayer; public override async Task InitializeAsync() { // 初始化 LibVLC传入 Android Context var context Android.App.Application.Context; _libVLC new LibVLC(new[] { --no-video, --no-osd, --no-snapshot, --no-subtitle }); _mediaPlayer new MediaPlayer(_libVLC); // 绑定事件 _mediaPlayer.MediaPlayerEndReached (sender, e) OnPlaybackCompleted(); _mediaPlayer.MediaPlayerTimeChanged (sender, e) OnPositionChanged(e.Time); _mediaPlayer.MediaPlayerLengthChanged (sender, e) OnDurationChanged(e.Length); } public override async Task PlayAsync(string url, AudioMetadata metadata) { var media new Media(_libVLC, new Uri(url)); _mediaPlayer.Play(media); } public override void Pause() _mediaPlayer.Pause(); public override void Resume() _mediaPlayer.Play(); public override void Stop() _mediaPlayer.Stop(); public override void Seek(TimeSpan position) _mediaPlayer.Position (float)(position.TotalMilliseconds / 1000.0); }第三步DI 容器切换实现在App.xaml.cs中根据平台动态注册#if ANDROID serviceCollection.AddSingletonMediaPlayerService, VLCAudioPlayerService(); #elif IOS serviceCollection.AddSingletonMediaPlayerService, VLCAudioPlayerService(); #elif WINDOWS serviceCollection.AddSingletonMediaPlayerService, VLCAudioPlayerService(); #elif MACCATALYST serviceCollection.AddSingletonMediaPlayerService, VLCAudioPlayerService(); #else serviceCollection.AddSingletonMediaPlayerService, DefaultMediaPlayerService(); #endif第四步验证 HLS 流播放准备一个测试.m3u8地址如https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8在SearchResultPageViewModel.cs中临时硬编码// 测试用跳过搜索直接播 HLS var hlsItem new MusicItem { Id hls-test, Title HLS Test Stream, Url https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8 }; await playerService.PlayAsync(hlsItem);运行后四平台均应流畅播放且Player.xaml.cs中Position更新频率达 10Hz原生 MediaPlayer 仅 1–2Hz拖拽 seek 响应 200ms。从那以后我每次接手音视频跨平台项目都会先检查MediaPlayerService是否可插拔——哪怕不用 LibVLCSharp也预留IMediaPlayerEngine接口把ExoPlayer、AVPlayer、Windows.Media封装成策略模式。因为音频流的协议演进比 UI 框架快十倍今天还是 MP3明天就全是 HLSDRM硬编码等于给自己埋雷。希望帮到你。本文还有配套的精品资源点击获取