news 2026/9/10 15:36:17

expo-live-photo 完全指南:在 Expo / React Native 中渲染 iOS 实况照片

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
expo-live-photo 完全指南:在 Expo / React Native 中渲染 iOS 实况照片

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.12024-10-22🎉 新功能初始发布(PR #31193)
0.1.02025-04-04💡 其他迁移expo-module.config.json到统一平台语法;修复 Swift 6 下会升级为错误的警告(PR #34445)
1.0.02025-08-13💡 其他迁移到 React 19(PR #37303)
56.0.02026-05-05🛠 破坏性变更最低 iOS/tvOS 版本提升至 16.4,macOS 提升至 13.4
57.0.12026-07-15当前版本,无面向用户变更

解读几个关键节点

  • 0.x1.0.0的跃升1.0.0的核心变更(依据 CHANGELOG)是将包迁移到 React 19,这标志着该库在 Expo SDK 生态中正式进入稳定 API 阶段;此后的55.0.056.0.057.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,只依赖exporeactreact-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 描述依赖关系,编译时依赖PhotosUIPhotos框架(见下文原生实现)。

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类型默认值说明
sourceLivePhotoAsset \| null要展示的实况照片资源
isMutedbooleantrue播放时是否静音
contentFit'contain' \| 'cover''contain'图片如何缩放适配容器
useDefaultGestureRecognizerbooleantrue是否启用 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 平台直接抛UnavailabilityErrorstartPlayback未传参时默认以'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默认truecontentFit默认.contain、手势默认开启),从源码层面验证了 API 文档中的默认值约定。

5.2 视图封装与手势处理

LivePhotoView.swift 是核心视图类,内部持有一个PHLivePhotoView(Apple PhotosUI 的原生视图),并实现PHLivePhotoViewDelegate

  • 加载流程loadLivePhoto()):sourcecontentFit变化时异步触发重新加载;通过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 层。这也解释了为什么photoUripairedVideoUri必须来自同一个合法实况照片文件。

5.4 枚举映射

LivePhotoEnums.swift 定义了枚举到原生 API 的映射:

  • ContentFit.contain → PHImageContentMode.aspectFitContentFit.cover → PHImageContentMode.aspectFill
  • PlaybackStyle.full → PHLivePhotoViewPlaybackStyle.fullPlaybackStyle.hint → PHLivePhotoViewPlaybackStyle.hint

JS 层的字符串枚举与 Swift 枚举一一对应,保持了 API 的跨层一致性。

六、从源码角度理解"可用性"与平台边界

把 LivePhotoView.tsx、expo-module.config.json 与 CHANGELOG 中的破坏性变更串起来,可以得到一张完整的平台能力图:

  1. 平台范围:模块配置声明["apple"],覆盖 iOS(含 tvOS)与 macOS;CHANGELOG 中56.0.0将最低版本分别提升至 iOS/tvOS 16.4 与 macOS 13.4。
  2. JS 侧判定isAvailable()通过process.env.EXPO_OS === 'ios'判定——注意源码当前仅判定 iOS,macOS 上组件同样走"不可用"分支。
  3. 非 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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 15:33:56

Telegram Bot API中间件开发终极指南:扩展机器人功能的10个技巧

Telegram Bot API中间件开发终极指南&#xff1a;扩展机器人功能的10个技巧 Telegram Bot API中间件是扩展机器人功能的强大工具&#xff0c;让开发者能够轻松实现消息处理、用户认证、日志记录等核心功能。在当今即时通讯应用蓬勃发展的时代&#xff0c;掌握Telegram Bot中间…

作者头像 李华
网站建设 2026/9/10 15:29:30

Telethon项目中的实体(Entities)概念详解

Telethon项目中的实体(Entities)概念详解 什么是实体(Entities) 在Telethon项目中&#xff0c;"实体"是一个核心概念&#xff0c;它指的是即时通讯API可能返回的任何用户(User)、聊天(Chat)或频道(Channel)对象。这些对象通常作为API方法的响应返回&#xff0c;比如G…

作者头像 李华
网站建设 2026/9/10 15:28:48

CANN/ge CBLAS矩阵乘法接口

aclblasGemmEx 【免费下载链接】ge GE&#xff08;Graph Engine&#xff09;是面向昇腾的图编译器和执行器&#xff0c;提供了计算图优化、多流并行、内存复用和模型下沉等技术手段&#xff0c;加速模型执行效率&#xff0c;减少模型内存占用。 GE 提供对 PyTorch、TensorFlow …

作者头像 李华