expo-live-photo 完全指南:在 Expo / React Native 中渲染 iOS 实况照片
【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo
导读
expo-live-photo是 Expo 生态中专用于渲染 Live Photo(实况照片)的模块,支持 iOS 与 Web 平台,底层以 Swift 封装 ApplePhotosUI框架的PHLivePhotoView实现。本文以该包在仓库中的 CHANGELOG.md 为骨架,结合 TypeScript API 定义、React 组件实现 与 iOS 原生源码,完整讲解安装方式、LivePhotoView全部 props 与命令式方法、底层加载/播放原理,以及从 0.0.1 到 57.0.1 的版本演进脉络。读完后你将能在自己的 Expo / React Native 应用中直接接入并控制实况照片的展示与播放。
一、版本演进与里程碑
包的完整变更历史集中在 CHANGELOG.md,从 2024 年 10 月的首个发布至今经历了若干次里程碑式变更:
| 版本 | 时间 | 类型 | 变更要点 |
|---|---|---|---|
0.0.1 | 2024-10-22 | 🎉 新功能 | 初始发布(PR #31193) |
0.1.0 | 2025-04-04 | 💡 其他 | 迁移expo-module.config.json到统一平台语法;修复 Swift 6 下会升级为错误的警告(PR #34445) |
1.0.0 | 2025-08-13 | 💡 其他 | 迁移到 React 19(PR #37303) |
56.0.0 | 2026-05-05 | 🛠 破坏性变更 | 最低 iOS/tvOS 版本提升至 16.4,macOS 提升至 13.4 |
57.0.1 | 2026-07-15 | — | 当前版本,无面向用户变更 |
解读几个关键节点
- 从
0.x到1.0.0的跃升:1.0.0的核心变更(依据 CHANGELOG)是将包迁移到 React 19,这标志着该库在 Expo SDK 生态中正式进入稳定 API 阶段;此后的55.0.0、56.0.0、57.0.1等版本则跟随 Expo SDK 的版本号体系进行对齐发布,多数为无用户可见变更的内部维护。 - 系统版本门槛:CHANGELOG 明确记录
56.0.0将最低 iOS/tvOS 版本提升到 16.4、macOS 到 13.4。这意味着在当前仓库主分支版本下,使用实况照片功能需要设备运行 iOS 16.4+。 - 平台适配:
0.1.0将模块配置迁移到统一平台语法,与当前仓库中 expo-module.config.json 的形态一致——该文件声明"platforms": ["apple"],并注册原生模块LivePhotoModule,这是理解该包“仅面向 Apple 平台原生实现、其余平台走降级路径”的起点。
二、安装与工程配置
2.1 安装 npm 包
在 bare React Native 工程中,需要先确保已安装并配置好expo包,然后执行:
npm install expo-live-photo从 package.json 可以看出:
- 该包没有运行时
dependencies,只依赖expo、react、react-native三个peerDependencies; main指向build/index.js,类型声明在build/index.d.ts,而exports字段在支持expo-source条件时直接指向 TypeScript 源码src/index.ts,便于 Expo 开发服务器按源码形态打包。
2.2 iOS 配置
iOS 需要安装 CocoaPods 依赖:
npx pod-install原生侧由 ExpoLivePhoto.podspec 描述依赖关系,编译时依赖PhotosUI与Photos框架(见下文原生实现)。
2.3 Android / Web 的降级行为
README 中仅提供 iOS 配置步骤,没有 Android 配置章节,这与仓库源码一致:模块配置声明平台为["apple"],因此 Android 上并没有原生实现。从 LivePhotoView.tsx 的源码看:
const NativeView: React.ComponentType<NativeLivePhotoViewProps> | null = isAvailable() ? requireNativeView('ExpoLivePhoto') : null; function isAvailable() { return process.env.EXPO_OS === 'ios'; }在非 iOS 平台上组件会打印expo-live-photo is not available on ${process.env.EXPO_OS}警告并渲染null;调用命令式方法(如startPlayback)则会抛出UnavailabilityError。也就是说,该库在非 iOS 平台上是“安全降级”而非“模拟实现”,这一点对跨平台工程很重要。
三、核心 API:LivePhotoView
包入口 src/index.ts 只导出两样东西:
export { default as LivePhotoView } from './LivePhotoView'; export * from './LivePhoto.types';即一个 React 组件LivePhotoView和全部类型定义。
3.1 数据模型:LivePhotoAsset
实况照片本质上是"静态照片 + 配对视频"的组合,因此source需要同时提供两个文件的 URI:
export type LivePhotoAsset = { photoUri: string; // 实况照片的静态图片部分 pairedVideoUri: string; // 与之配对的视频部分 };类型注释(见 LivePhoto.types.ts)特别强调了一个重要约束:
由于原生限制,照片和视频必须来自一个合法的实况照片文件且不能改动。拍摄时照片通过与视频的元数据配对,一旦配对关系被破坏,就无法将它们重新组合成实况照片。
3.2 Props 完整参考
LivePhotoViewProps继承ViewProps,核心 props 如下(均来自源码中的 JSDoc):
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
source | LivePhotoAsset \| null | — | 要展示的实况照片资源 |
isMuted | boolean | true | 播放时是否静音 |
contentFit | 'contain' \| 'cover' | 'contain' | 图片如何缩放适配容器 |
useDefaultGestureRecognizer | boolean | true | 是否启用 iOS 默认手势识别器:为true时用户长按LivePhotoView即开始播放 |
onPlaybackStart | () => void | — | 播放开始时回调 |
onPlaybackStop | () => void | — | 播放停止时回调 |
onLoadStart | () => void | — | 实况照片开始加载时回调 |
onPreviewPhotoLoad | () => void | — | 预览照片(低质量占位图)加载完成时回调 |
onLoadComplete | () => void | — | 实况照片加载完成、可播放时回调 |
onLoadError | (error: LivePhotoLoadError) => void | — | 加载出错时回调 |
LivePhotoLoadError只包含一个字段:
export type LivePhotoLoadError = { message: string; // 加载失败的原因 };3.3 命令式方法与静态属性
通过ref可以拿到LivePhotoViewType,它提供两个命令式方法:
export type LivePhotoViewType = { startPlayback: (playbackStyle?: PlaybackStyle) => void; stopPlayback: () => void; };PlaybackStyle决定播放方式:
'hint'—— 只播放视频的一小段,用于提示"这里是一个实况照片";'full'—— 播放完整视频。
此外组件还挂载了静态方法LivePhotoView.isAvailable(),用于在渲染前判断当前设备是否支持展示实况照片。
四、基础用法与实战示例
下面给出一个完整的最小示例:加载实况照片、支持长按播放,并监听加载与播放状态。
import { useRef } from 'react'; import { StyleSheet, View } from 'react-native'; import { LivePhotoView, type LivePhotoAsset, type LivePhotoViewType, } from 'expo-live-photo'; const source: LivePhotoAsset = { photoUri: 'file:///path/to/live-photo.jpg', pairedVideoUri: 'file:///path/to/live-photo.mov', }; export default function LivePhotoScreen() { const ref = useRef<LivePhotoViewType | null>(null); return ( <View style={styles.container}> <LivePhotoView ref={ref} source={source} isMuted={false} contentFit="cover" useDefaultGestureRecognizer onLoadStart={() => console.log('开始加载实况照片')} onPreviewPhotoLoad={() => console.log('预览照片已就绪')} onLoadComplete={() => console.log('实况照片可播放')} onLoadError={({ message }) => console.error('加载失败:', message)} onPlaybackStart={() => console.log('播放开始')} onPlaybackStop={() => console.log('播放结束')} style={styles.livePhoto} /> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center' }, livePhoto: { width: 320, height: 240 }, });4.1 手动控制播放
如果希望由自己的交互逻辑触发播放(而不是依赖 iOS 默认的长按手势),可以关闭默认手势识别器并通过ref手动控制:
<LivePhotoView ref={ref} source={source} useDefaultGestureRecognizer={false} style={styles.livePhoto} /> // 播放完整视频 ref.current?.startPlayback('full'); // 或仅播放提示片段 ref.current?.startPlayback('hint'); // 停止播放 ref.current?.stopPlayback();注意源码实现(LivePhotoView.tsx)在调用时做了两层保护:非 iOS 平台直接抛UnavailabilityError;startPlayback未传参时默认以'full'播放。
4.2 加载流程的状态机
通过组合事件回调,可以构建典型的加载状态机:onLoadStart(开始)→onPreviewPhotoLoad(低质量占位图就绪)→onLoadComplete(完整可播放)→ 任一步失败走onLoadError。这与原生加载流程一一对应(见下节)。
五、底层原理:iOS 原生实现解析
该库在 iOS 侧由三个核心文件协作,全部位于 packages/expo-live-photo/ios 目录。
5.1 模块注册与 Prop 映射
LivePhotoModule.swift 是 Expo Modules 体系下的模块定义:
public class LivePhotoModule: Module { public func definition() -> ModuleDefinition { Name("ExpoLivePhoto") View(LivePhotoView.self) { Events("onLoadStart", "onPreviewPhotoLoad", "onLoadComplete", "onLoadError", "onPlaybackStart", "onPlaybackStop") Prop("source") { (view: LivePhotoView, source: LivePhotoAsset) in view.source = source } Prop("isMuted") { (view: LivePhotoView, isMuted: Bool?) in view.livePhotoView.isMuted = isMuted ?? true } // ...contentFit、useDefaultGestureRecognizer 同理 } } }从这里可以看到:
- 六个事件名与 JS 侧回调一一对应,事件分发由原生
EventDispatcher完成; - JS 层传参在原生侧均为可空值,并用
??提供与文档一致的默认值(isMuted默认true、contentFit默认.contain、手势默认开启),从源码层面验证了 API 文档中的默认值约定。
5.2 视图封装与手势处理
LivePhotoView.swift 是核心视图类,内部持有一个PHLivePhotoView(Apple PhotosUI 的原生视图),并实现PHLivePhotoViewDelegate:
- 加载流程(
loadLivePhoto()):source或contentFit变化时异步触发重新加载;通过PHLivePhoto.requestSequence流式获取实况照片,先拿到低质量占位图(触发onPreviewPhotoLoad),再拿到高质量结果(触发onLoadComplete);任一步抛错则触发onLoadError。 - 手势:
useDefaultGestureRecognizer属性变化时,会从PHLivePhotoView添加或移除其内置的playbackGestureRecognizer。 - 播放回调:实现代理方法
willBeginPlaybackWith/didEndPlaybackWith,把原生播放开始/结束翻译为 JS 事件。
5.3 加载管线与配对校验
PHLivePhoto+Async.swift 用AsyncThrowingStream封装了PHLivePhoto.request(withResourceFileURLs:placeholderImage:targetSize:contentMode:):
let isLowQuality = loadInfo[PHLivePhotoInfoIsDegradedKey] as? Bool ?? false let error = loadInfo[PHLivePhotoInfoErrorKey] as? Error if let error { continuation.finish(throwing: error) return } if let livePhoto { continuation.yield((isLowQuality, livePhoto)) if !isLowQuality { continuation.finish() } return } // 没有返回任何有效数据,说明照片和视频 URL 并未配对 continuation.finish(throwing: InvalidSourceException("Provided photo and video urls are not paired"))这段代码直接印证了类型定义中的警告:当照片与视频未正确配对时,系统不会返回有效PHLivePhoto,库会以InvalidSourceException("Provided photo and video urls are not paired")结束加载流,最终通过onLoadError暴露给 JS 层。这也解释了为什么photoUri与pairedVideoUri必须来自同一个合法实况照片文件。
5.4 枚举映射
LivePhotoEnums.swift 定义了枚举到原生 API 的映射:
ContentFit.contain → PHImageContentMode.aspectFit,ContentFit.cover → PHImageContentMode.aspectFill;PlaybackStyle.full → PHLivePhotoViewPlaybackStyle.full,PlaybackStyle.hint → PHLivePhotoViewPlaybackStyle.hint。
JS 层的字符串枚举与 Swift 枚举一一对应,保持了 API 的跨层一致性。
六、从源码角度理解"可用性"与平台边界
把 LivePhotoView.tsx、expo-module.config.json 与 CHANGELOG 中的破坏性变更串起来,可以得到一张完整的平台能力图:
- 平台范围:模块配置声明
["apple"],覆盖 iOS(含 tvOS)与 macOS;CHANGELOG 中56.0.0将最低版本分别提升至 iOS/tvOS 16.4 与 macOS 13.4。 - JS 侧判定:
isAvailable()通过process.env.EXPO_OS === 'ios'判定——注意源码当前仅判定 iOS,macOS 上组件同样走"不可用"分支。 - 非 iOS 降级:组件渲染
null+ 控制台警告,命令式方法抛UnavailabilityError,保证应用不会崩溃。
这一边界设计意味着:如果你需要 Android 上展示实况照片,不应依赖本库,而应在业务层用LivePhotoView.isAvailable()先行分流。
结语
expo-live-photo用很小的 API 表面积(一个组件、六类事件、两个命令式方法)封装了 Apple PhotosUI 中最复杂的资源模型之一——照片与视频的配对加载。本文从 CHANGELOG 的版本脉络出发,结合仓库内的类型定义、React 组件与 Swift 原生实现,完整还原了它的安装配置、API 用法、加载状态机与底层原理。需要深入源码的读者,建议按 src → ios 的顺序阅读,并结合 CHANGELOG.md 追踪各版本的破坏性变更与平台门槛。
【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考