Expo 小程序媒体库功能设计与实现记录

Expo 小程序媒体库功能设计与实现记录

这篇文章记录一个基于 Expo、React Native 和 Expo Router 的媒体库演示功能。当前阶段重点不是完整后端上传链路,而是先把前端交互、媒体选择、录音、预览、缓存、列表展示和删除流程跑通,为后续接入后端和数据库做功能验证。

项目背景

项目基于 Expo 搭建,使用 Expo Router 管理页面路由。当前应用主要有两个页面:

  • Home:选择图片、视频、音频,或录制音频,并预览最后一个选择的媒体。
  • About:展示已保存的媒体列表,支持轮播预览、缩略图切换和删除。

这个功能更接近一个"媒体素材管理"的雏形。用户可以先把本地图片、视频、音频放进应用,再在媒体库里查看和管理。

当前核心功能

1. 图片和视频多选

首页通过 expo-image-picker 打开系统媒体选择器:

  • 支持图片
  • 支持视频
  • 支持多选
  • 选择完成后,首页预览最后一个选中的文件
  • 点击保存后,把本次选择的所有媒体写入本地缓存

这里的重点是"选择"和"保存"分离。用户选择媒体后,可以先在首页确认预览,再决定是否保存到媒体库。

2. 音频文件选择

演示阶段 Web 端使用原生 input[type=file] 选择音频文件:

  • 支持 audio/*
  • 支持多个音频文件
  • 选择完成后,最后一个音频会显示在首页预览区
  • 保存逻辑和图片、视频保持一致

当前项目还没有安装原生端音频文件选择依赖,所以 iOS / Android 端会先提示需要补充 expo-document-picker。这样做是为了避免演示阶段引入太多依赖,先把主流程跑通。

3. Web 端音频录制

录音功能在 Web 端使用浏览器的 MediaRecordernavigator.mediaDevices.getUserMedia

  • 点击 Record audio 请求麦克风权限
  • 开始录音后按钮变为 Stop recording
  • 停止录音后生成音频文件
  • 录音结果自动进入待保存媒体列表
  • 首页预览区显示录音音频

原生 iOS / Android 端录音后续可以接入 expo-audio,当前演示阶段先保留提示。

4. 本地缓存

当前媒体数据统一保存到 LocalStorage。保存的不是完整后端数据,也不是数据库记录,而是一个前端演示用的媒体元数据数组。

核心数据结构如下:

ts 复制代码
export type StoredMedia = {
  id: string;
  uri: string;
  type: "image" | "video" | "audio";
  fileName?: string | null;
  width?: number;
  height?: number;
  duration?: number | null;
  createdAt: string;
};

这样设计的好处是后续替换成后端接口比较自然。以后后端返回的媒体记录也大概率会包含:

  • 媒体 ID
  • 文件 URL
  • 媒体类型
  • 文件名
  • 宽高、时长等元信息
  • 创建时间

5. 媒体库轮播展示

About 页面读取缓存里的媒体列表,并提供一个简单的媒体库视图:

  • 顶部显示媒体数量和当前位置
  • 中间展示当前媒体
  • 左右按钮切换上一条 / 下一条
  • 底部缩略图列表快速切换
  • 删除按钮移除当前媒体

图片直接使用 expo-image 展示。Web 端视频使用 <video controls>,音频使用 <audio controls>。原生端当前对视频和音频先展示类型占位,后续可以接入专门播放器。

核心模块划分

app/(tabs)/index.tsx

首页负责媒体入口和临时预览。

主要职责:

  • 打开图片 / 视频选择器
  • 打开音频文件选择器
  • 触发 Web 端录音
  • 维护本次待保存媒体数组
  • 预览最后一个待保存媒体
  • 点击保存后写入缓存

这个页面不直接关心媒体库如何展示,只负责生成 StoredMedia[] 并保存。

app/(tabs)/about.tsx

About 页面负责媒体库展示。

主要职责:

  • 从缓存读取媒体列表
  • 展示当前媒体
  • 处理上一条 / 下一条切换
  • 展示缩略图
  • 删除当前媒体

这个页面不负责选择文件,也不负责录音,只消费已经保存好的媒体数据。

components/ImageViewer.tsx

这是首页预览组件。

它根据媒体类型做不同展示:

  • image:展示图片
  • video:Web 端使用视频播放器,原生端显示视频占位
  • audio:Web 端使用音频播放器,原生端显示音频占位

这样首页不用写太多媒体类型判断,页面逻辑更清晰。

lib/mediaStore.ts

这是当前演示阶段最关键的一层。

主要职责:

  • 定义统一媒体类型 StoredMedia
  • 读取本地缓存
  • 写入本地缓存
  • 批量新增媒体
  • 删除媒体

后续接入后端时,最适合优先替换的就是这一层。比如把 getStoredMedia() 改成请求列表接口,把 addStoredMediaItems() 改成上传文件并保存数据库记录。

数据流

当前完整流程如下:

  1. 用户在首页选择图片 / 视频 / 音频,或录制音频。
  2. 前端把文件转换成统一的 StoredMedia 结构。
  3. 首页展示本次选择列表中的最后一个媒体。
  4. 用户点击保存。
  5. 应用把本次媒体数组写入 LocalStorage
  6. 用户进入 About 页面。
  7. About 页面读取缓存并展示轮播列表。
  8. 用户可以切换媒体或删除媒体。

当前阶段的取舍

为什么先用 LocalStorage

因为现在目标是演示功能,而不是正式文件存储。LocalStorage 的优点是:

  • 实现快
  • 不依赖后端
  • 方便验证交互流程
  • 数据结构后续容易迁移

但它也有明显限制:

  • 容量有限
  • 不适合保存大视频
  • 原生端不能完全等价使用
  • 浏览器清缓存后数据会丢
  • 不适合作为正式文件存储方案

所以当前的缓存更准确地说是"演示缓存",不是生产级存储。

为什么音频原生端暂时只提示

图片和视频可以直接用 expo-image-picker。但音频文件选择和录音在原生端需要额外能力:

  • 选择音频文件通常需要 expo-document-picker
  • 录音需要 expo-audio 或类似音频模块
  • 长期保存录音文件还需要 expo-file-system

这些能力后续可以加,但演示阶段先用 Web 标准 API 把交互闭环跑通,避免过早扩大实现范围。

为什么保存的是 uri 和元数据

当前保存的是媒体引用和元数据,不是上传后的永久 URL。正式接后端后,uri 会逐步变成服务器返回的资源地址,例如:

ts 复制代码
{
  id: "media_001",
  uri: "https://cdn.example.com/media/xxx.mp4",
  type: "video",
  fileName: "demo.mp4",
  duration: 120000,
  createdAt: "2026-05-06T10:00:00.000Z"
}

这样前端展示层几乎不用大改。

后续接入后端的演进方向

后续如果要做成正式功能,可以按下面顺序升级。

1. 文件上传接口

新增上传接口:

txt 复制代码
POST /api/media/upload

前端提交文件,后端返回媒体记录:

json 复制代码
{
  "id": "media_001",
  "url": "https://cdn.example.com/media/demo.mp4",
  "type": "video",
  "fileName": "demo.mp4",
  "duration": 120000,
  "createdAt": "2026-05-06T10:00:00.000Z"
}

2. 媒体列表接口

txt 复制代码
GET /api/media

About 页面从接口读取列表,不再从 LocalStorage 读取。

3. 删除接口

txt 复制代码
DELETE /api/media/:id

删除时既删除数据库记录,也根据业务需要清理对象存储中的文件。

4. 对象存储

图片、视频、音频文件不建议直接存进数据库。更常见的方案是:

  • 文件放对象存储
  • 数据库保存文件 URL 和元数据
  • 前端通过 URL 播放或展示

5. 原生端能力补全

如果要支持 iOS / Android:

  • expo-document-picker 选择音频文件
  • expo-audio 实现录音
  • expo-file-system 做临时文件复制和缓存管理
  • 用视频 / 音频播放器组件补齐原生播放体验

注意点

1. 大文件不要长期放 LocalStorage

视频和音频可能很大,正式项目不能依赖 LocalStorage 保存大文件。演示阶段可以接受,但生产环境一定要上传到后端或对象存储。

2. Web 和原生平台能力不完全一致

Web 可以直接使用 <audio><video>MediaRecorder。React Native 原生端没有这些 DOM API,需要 Expo 或原生模块提供对应能力。

3. 预览和保存要分离

当前设计里,选择媒体后只是放到待保存状态,用户点击 Save 才会进入媒体库。这样可以避免用户误选文件后立即污染列表。

4. 媒体类型要统一抽象

图片、视频、音频虽然展示方式不同,但都可以抽象成同一种业务数据:

ts 复制代码
type MediaType = "image" | "video" | "audio";

统一抽象后,列表、删除、缓存、后端接口都能复用同一套逻辑。

5. 后续需要考虑权限

正式做原生端时,需要处理:

  • 相册权限
  • 麦克风权限
  • 文件访问权限
  • 用户拒绝权限后的提示

权限处理不能只靠默认报错,否则用户体验会很差。

小结

这个媒体库功能目前完成的是前端演示闭环:

  • 多选图片 / 视频
  • 选择音频文件
  • Web 端录音
  • 统一媒体数据结构
  • 本地缓存
  • 轮播展示
  • 删除管理

它不是最终的生产架构,但已经把核心交互和数据模型跑通了。后续接入后端时,可以优先替换 mediaStore.ts,再逐步补齐上传接口、数据库、对象存储和原生端音视频能力。

相关推荐
经济元宇宙5 小时前
2026混合开发工具选型:小程序生态适配测评
小程序
lpfasd12318 小时前
微信小程序虚拟支付(道具直购)踩坑全记录:从-15005到支付成功
微信小程序·小程序
crazy_wsp21 小时前
使用AI从0到1上线微信小程序
人工智能·微信小程序·小程序
小宋的踩坑日记1 天前
全网最全!Tailwind/Unocss 类名速查表,前端开发必备神器!
css·小程序·前端框架
低代码布道师1 天前
健身房私教课小程序需求规格说明书
小程序·规格说明书
浩冉学编程2 天前
微信小程序中基于java后端实现官方的文本内容安全识别msgSecCheck
java·前端·安全·微信小程序·小程序·微信公众平台·内容安全审核
程序鉴定师3 天前
如何选择合适的深圳小程序开发公司?
大数据·小程序
代码不加糖3 天前
从零手写简易 Taro:20 行 JSX 如何变成小程序?(硬核实战)
小程序·taro
云云只是个程序马喽4 天前
AI漫剧创作系统开发定制指南
人工智能·小程序·php