Blazor+PWA技术打造全平台音乐播放器-从音频缓存到离线播放的实践之路
开局三张图…
0.起源
主要是自己现在用的是苹果手机,虽然手机很高级,但是想听自己喜欢的歌曲确是不容易,在线app都要付费,免费的本地播放器都不太好用(收费的也不太行),基础功能都不满足。此外苹果的限制众多,传音乐也是比较麻烦的事。就想着做一款理想的音乐播放器:
- 跨平台运行,在Windows、iOS甚至Linux上保持一致体验
- 界面简洁直观,没有花哨但无用的功能
- 支持将在线音乐缓存到本地,实现离线播放
- 无需经过繁琐的应用商店审核和安装流程
首先考虑的是原生应用开发。Flutter、React Native等跨平台框架挺流行的,但是并不容易,需要花费大量时间学习新的技术栈,更关键的是,在iOS平台上发布应用仍然需要经过App Store的审核流程,还需要支付年费。 MD,我就想安静地听个音乐,至于这么麻烦吗? 请教了万能的gpt后,发现了pwa这种技术,想想之前也是学习了blazor相关的技术,就想着用blazor做一下了。
1.PWA:跨平台音乐应用的理想选择
PWA(渐进式Web应用)是一种结合Web和原生应用优势的技术,旨在提供类似原生应用的用户体验。PWA可以在任何支持现代浏览器的设备上运行,实现跨平台兼容性。通过Service Worker,PWA支持离线访问,允许用户在无网络连接时继续使用应用。用户可以将PWA安装到设备上,享受全屏体验,并通过推送通知保持互动。PWA采用响应式设计,确保在不同设备上提供一致的用户体验。由于通过HTTPS提供服务,PWA还具备较高的安全性。对于希望快速构建跨平台应用的开发者来说,PWA是一个理想的选择。
1.1.音频流API与离线缓存:PWA的核心能力
PWA(渐进式Web应用)技术通过Service Worker实现离线运行,使得应用在无网络连接时依然能够正常使用。这对于音乐播放器尤为重要,因为用户可以在离线状态下继续播放已缓存的音乐。IndexedDB则提供了强大的本地存储能力,允许应用将音频数据和相关元数据保存到用户设备中。通过IndexedDB,音乐播放器可以高效地管理和检索本地存储的数据,确保用户在离线时也能访问完整的音乐库。这种结合使得PWA成为构建跨平台音乐应用的理想选择,提供了流畅的用户体验和可靠的数据存储。
在实现PWA音乐播放器时,服务端的音频流API是基础设施:
[HttpGet("{id}")]
public IActionResult GetMusicStream(string id)
{
try
{
// 安全处理文件路径
string safeId = Path.GetFileNameWithoutExtension(id);
string filePath = Path.Combine("D:\\music", $"{safeId}.mp3");
// 返回文件流,启用范围处理
return PhysicalFile(filePath, "audio/mpeg", enableRangeProcessing: true);
}
catch (Exception ex)
{
_logger.LogError(ex, "流式播放失败");
return StatusCode(500, "处理请求时发生错误");
}
}
1.2.通过Service Worker,我们可以拦截网络请求,实现复杂的缓存逻辑:
// service-worker.js
self.addEventListener('fetch', event => {
// 检查请求是否为音频文件
if (event.request.url.includes('/api/music/')) {
event.respondWith(
caches.match(event.request).then(cachedResponse => {
// 返回缓存响应或从网络获取
return cachedResponse || fetch(event.request).then(response => {
// 克隆响应并缓存
const responseToCache = response.clone();
caches.open('audio-cache').then(cache => {
cache.put(event.request, responseToCache);
});
return response;
});
})
);
}
});
1.3.IndexedDB:增强PWA的离线音乐库
PWA的一个核心特性是能够在完全离线状态下工作。我们使用IndexedDB来存储音频数据和元数据,这比简单的Cache API提供了更强大的功能:
export async function cacheAudioWithMetadata(key, url, metadataJson) {
try {
// 检查是否已缓存
if (await isAudioCached(key)) {
return await getCachedAudio(key);
}
// 获取并存储音频数据
const response = await fetch(url);
const arrayBuffer = await response.arrayBuffer();
const base64Data = arrayBufferToBase64(arrayBuffer);
const dataUrl = `data:audio/mpeg;base64,${base64Data}`;
// 存储音频和元数据
await cacheAudioData(key, dataUrl, JSON.parse(metadataJson));
return dataUrl;
} catch (error) {
// 网络错误时尝试返回缓存数据
return await getCachedAudio(key);
}
}
1.4.核心功能
- 在设备首次访问时下载并缓存用户喜爱的音乐
- 在离线环境(如飞行模式或无信号区域)继续播放
- 智能管理存储空间,避免缓存过度占用设备存储
- 保存用户的播放列表和偏好设置
2.自适应UI:真正的全平台体验
PWA的另一个强大特性是响应式设计。通过CSS媒体查询和弹性布局,我们能够创建适应从手机到桌面各种设备的用户界面:
/* 移动设备优先的样式 */
.music-player {
display: flex;
flex-direction: column;
}
/* 平板和桌面布局 */
@media (min-width: 768px) {
.music-player {
flex-direction: row;
}
.playlist {
width: 300px;
}
.player-controls {
flex: 1;
}
}
/* 大屏幕设备优化 */
@media (min-width: 1200px) {
.playlist {
width: 350px;
}
}
3.安装体验优化:从网页到应用
PWA的一大亮点是可安装性。通过配置Web App Manifest,我们可以创建一个能够像原生应用一样被安装的音乐播放器:
{
"name": "全平台音乐播放器",
"short_name": "音乐播放器",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#4285f4",
"icons": [
{
"src": "icons/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
这使得用户可以:
- 在手机主屏幕上添加我们的应用图标
- 在桌面浏览器中点击"安装"按钮将其添加到开始菜单或桌面
- 在没有地址栏的全屏模式下使用应用
- 通过操作系统任务切换器访问应用