更新记录
1.0.0(2026-08-27) 下载此版本
初始化
平台兼容性
uni-app x(5.24)
| Chrome | Safari | Android | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|
| - | - | 5.0 | 12 | - | - |
dyl-media-preview
dyl-media-preview 是面向 uni-app x 的 Android/iOS 原生混合媒体预览插件。调用一次 previewMedia(...),即可在同一个全屏预览器中左右滑动查看图片和视频。
previewMedia({
sources: [
{ type: 'image', url: imageUrl },
{ type: 'video', url: videoUrl, poster: coverUrl }
],
current: 0
})
插件目录可独立复制到其他 uni-app x 项目,不依赖当前项目的业务页面,也不依赖 dyl-media-picker。
主要能力
- 图片、视频可以任意交错排列并左右滑动。
- 支持指定初始下标、数字页码、隐藏页码和首尾循环。
- 图片支持双指缩放、双击缩放、缩放后拖动以及边界切页。
- 视频支持 MP4、HLS、封面、系统播放控制、自动播放和静音。
- 支持 HTTPS、签名 URL、uni 临时路径、
file://和本地绝对路径。 - Android 额外支持
content://。 - 支持打开、切页、单项错误、关闭和主动关闭等完整回调。
- 加载超过约 300ms 才显示加载指示器,加载失败后保留预览页并提供“重新加载”。
- Android 同时只保留一个 ExoPlayer;切页、退后台和关闭时释放播放器。
兼容范围
| 项目 | 支持范围 |
|---|---|
| 项目类型 | uni-app x |
| HBuilderX / uni-app x | 5.24 及以上 |
| Android | API 21 及以上 |
| iOS | iOS 12.0 及以上 |
| App Android | 支持 |
| App iOS | 支持 |
| Web、小程序、HarmonyOS | 首版不支持,调用时返回 9301003 |
| 图片 | JPEG、PNG、WebP 等静态图片,以系统和图片库实际解码能力为准 |
| 视频 | MP4、HLS;不支持 DRM |
目录结构
uni_modules/dyl-media-preview/
├─ package.json
├─ README.md
├─ CHANGELOG.md
├─ TESTING.md
└─ utssdk/
├─ interface.uts # 公共类型定义
├─ shared.uts # 参数校验与默认值
├─ unierror.uts # UniError 与错误码
├─ index.uts # 非 App 平台兜底
├─ app-android/ # Android UTS、Kotlin、Manifest 和资源
└─ app-ios/ # iOS UTS 与 Swift
安装与迁移
复制插件
将整个插件目录复制到目标项目:
目标项目/
└─ uni_modules/
└─ dyl-media-preview/
插件所需文件全部包含在该目录中。项目内的调试页面和 pages.json 注册不是插件运行依赖,无需一起复制。
重新制作自定义基座
插件包含 Kotlin、Swift 和 Android 原生依赖,以下情况都必须重新制作并安装自定义调试基座:
- 第一次复制插件到项目。
- 修改
utssdk/app-android或utssdk/app-ios下的文件。 - 修改 Android
config.json、Manifest 或原生依赖版本。 - 升级插件版本。
普通热更新只更新业务资源,不会替换基座内的原生代码。若设备仍安装旧基座,可能继续运行旧 Kotlin/Swift 实现。遇到原生类缺失或旧堆栈时,应卸载旧基座,再安装新基座。
Android JDK 配置
Android 依赖解析使用 AGP 8.2.2,建议使用 JDK 17。在 PowerShell 中检查当前版本:
java -version
$env:JAVA_HOME
& "$env:JAVA_HOME\bin\java.exe" -version
HBuilderX 中打开“工具 → 设置 → 运行配置”,将 Android JDK 路径设置为 JDK 17 的根目录,不要包含 bin。例如 HBuilderX 自带的 Amazon Corretto:
D:\Develop\HBuilderX\plugins\amazon-corretto
JDK 26 可能导致旧版 Gradle/Groovy 报错:
Unsupported class file major version 70
这是电脑上的构建环境问题,与手机 Android 版本和插件的 minSdkVersion 21 无关。
快速开始
最简调用
import { previewMedia } from '@/uni_modules/dyl-media-preview'
previewMedia({
sources: [
{ type: 'image', url: 'https://example.com/photo.jpg' },
{
type: 'video',
url: 'https://example.com/video.mp4',
poster: 'https://example.com/cover.jpg'
}
],
current: 0
})
type 必须显式填写。插件不会根据扩展名猜测媒体类型,因为签名 URL、临时路径和无扩展名地址无法可靠推断类型。
完整调用
import {
previewMedia,
type PreviewMediaChangeResult,
type PreviewMediaCloseResult,
type PreviewMediaFail,
type PreviewMediaItemError,
type PreviewMediaOpenResult,
type PreviewMediaSource
} from '@/uni_modules/dyl-media-preview'
const imageSource : PreviewMediaSource = {
type: 'image',
url: 'https://example.com/photo.jpg'
}
const videoSource : PreviewMediaSource = {
type: 'video',
url: 'https://example.com/video.m3u8',
poster: 'https://example.com/cover.jpg'
}
const sources : PreviewMediaSource[] = [imageSource, videoSource]
previewMedia({
sources,
current: 1,
indicator: 'number',
loop: true,
autoplay: false,
muted: false,
success: (result : PreviewMediaOpenResult) : void => {
console.log('预览已展示,当前下标:' + result.current.toString())
},
fail: (error : PreviewMediaFail) : void => {
console.log('打开失败:' + error.errCode.toString() + ' ' + error.errMsg)
},
complete: () : void => {
console.log('打开尝试结束')
},
onChange: (result : PreviewMediaChangeResult) : void => {
console.log('切换到:' + result.current.toString() + ' ' + result.source.type)
},
onError: (result : PreviewMediaItemError) : void => {
console.log('媒体加载失败:' + result.current.toString() + ' ' + result.errMsg)
},
onClose: (result : PreviewMediaCloseResult) : void => {
console.log('预览关闭:' + result.current.toString() + ' ' + result.reason)
}
})
导出 API
| API | 说明 |
|---|---|
previewMedia(options) |
打开全屏混合媒体预览 |
closePreviewMedia(options) |
主动关闭当前预览 |
公共类型
export type PreviewMediaType = 'image' | 'video'
export type PreviewMediaIndicator = 'number' | 'none'
export type PreviewMediaCloseReason = 'button' | 'back' | 'api'
export type PreviewMediaSource = {
type : PreviewMediaType
url : string
poster ?: string
}
export type PreviewMediaOpenResult = {
current : number
}
export type PreviewMediaChangeResult = {
current : number
source : PreviewMediaSource
}
export type PreviewMediaCloseResult = {
current : number
reason : PreviewMediaCloseReason
}
export type PreviewMediaItemError = {
current : number
source : PreviewMediaSource
errCode : PreviewMediaErrorCode
errMsg : string
}
export type ClosePreviewMediaResult = {
current : number
}
完整类型定义以 utssdk/interface.uts 为准。
PreviewMediaSource
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
type |
'image' \| 'video' |
是 | 媒体类型,必须显式指定 |
url |
string |
是 | 远程地址、本地地址或临时路径;去除首尾空格后不能为空 |
poster |
string |
否 | 视频封面地址;图片项会忽略该字段 |
视频未提供 poster 时仍可播放,只是进入视频页前没有自定义封面图。
previewMedia(options)
参数
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
sources |
PreviewMediaSource[] |
是 | 无 | 至少包含一项;顺序就是左右滑动顺序 |
current |
number |
否 | 0 |
初始下标;负数、越界或非整数会回退为 0 |
indicator |
'number' \| 'none' |
否 | 'number' |
显示 1/3 数字页码或隐藏页码 |
loop |
boolean |
否 | false |
多项媒体时是否允许首尾循环 |
autoplay |
boolean |
否 | false |
进入视频页后是否自动播放 |
muted |
boolean |
否 | false |
视频播放器是否静音 |
success |
(result) => void |
否 | 无 | 原生全屏页面真正展示后触发一次 |
fail |
(error) => void |
否 | 无 | 参数校验或打开原生页面失败时触发 |
complete |
() => void |
否 | 无 | 打开尝试成功或失败后触发,不等待预览关闭 |
onChange |
(result) => void |
否 | 无 | 分页停稳且真实下标发生变化时触发 |
onClose |
(result) => void |
否 | 无 | 原生预览页面真正关闭后触发一次 |
onError |
(result) => void |
否 | 无 | 某个媒体项加载失败时触发,预览保持打开 |
回调时序
打开成功:
previewMedia()
→ success
→ complete
→ onChange / onError(预览使用期间可能触发多次)
→ onClose(页面真正关闭)
打开失败:
previewMedia()
→ fail
→ complete
需要特别注意:
complete表示“打开尝试结束”,不是“预览使用结束”。- 需要等待用户关闭页面时,应使用
onClose。 - 单项加载失败不会调用打开阶段的
fail,而是调用onError。 - 用户点击“重新加载”后再次失败,
onError可能再次触发。 - 循环模式内部的首尾镜像归位不会产生虚假的媒体下标。
重复打开
同一时间只允许存在一个活动预览。在已有预览尚未关闭时再次调用 previewMedia():
fail.errCode = 9301002
原生页面真正关闭后,活动状态才会解除。
closePreviewMedia(options)
主动关闭当前预览,行为类似 uni.closePreviewImage。
import {
closePreviewMedia,
type ClosePreviewMediaResult,
type PreviewMediaFail
} from '@/uni_modules/dyl-media-preview'
closePreviewMedia({
success: (result : ClosePreviewMediaResult) : void => {
console.log('主动关闭成功,最终下标:' + result.current.toString())
},
fail: (error : PreviewMediaFail) : void => {
console.log('主动关闭失败:' + error.errCode.toString())
},
complete: () : void => {
console.log('主动关闭尝试结束')
}
})
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
success |
(result: ClosePreviewMediaResult) => void |
否 | 页面关闭完成后触发,返回最终下标 |
fail |
(error: PreviewMediaFail) => void |
否 | 没有活动预览等情况下触发 |
complete |
() => void |
否 | 主动关闭成功或失败后触发 |
主动关闭成功时,原 previewMedia() 的 onClose 会收到:
{
current: 最终下标,
reason: 'api'
}
当前实现中的触发顺序为:原预览的 onClose → 主动关闭的 success → 主动关闭的 complete。
如果没有活动预览,返回 9301005。
关闭原因
reason |
说明 |
|---|---|
'button' |
用户点击顶部关闭按钮 |
'back' |
Android 系统返回,或系统交互导致预览返回关闭 |
'api' |
调用了 closePreviewMedia() |
URL 与本地路径
| 地址类型 | Android | iOS | 说明 |
|---|---|---|---|
https:// |
支持 | 支持 | 推荐使用 |
| 带查询参数的签名 URL | 支持 | 支持 | 查询字符串会原样保留 |
http:// |
取决于宿主 cleartext 配置 | 取决于 ATS 配置 | 插件不会修改宿主安全策略 |
| uni 临时路径 | 支持 | 支持 | 平台入口会转换为原生绝对路径 |
| 普通绝对路径 | 支持 | 支持 | 直接作为本地文件使用 |
file:// |
支持 | 支持 | 路径中的特殊字符应正确编码 |
content:// |
支持 | 不支持 | Android 内容 URI |
临时文件的生命周期由宿主应用管理。不要在预览页面关闭前删除对应文件。
插件不会:
- 注入自定义 Header。
- 注入 Cookie 或登录态。
- 自动刷新过期签名 URL。
- 自动绕过证书、ATS 或 Android cleartext 限制。
需要鉴权的媒体应使用公开地址、有效签名地址,或者先下载成本地临时文件。
UTS 强类型注意事项
动态构造数组
UTS 在 Array.push() 参数位置可能把对象字面量推断为 UTSJSONObject,从而产生:
实际类型为 UTSJSONObject,预期类型为 PreviewMediaSource
错误写法:
const sources : PreviewMediaSource[] = []
sources.push({ type: 'image', url: imageUrl })
推荐先显式声明类型:
const sources : PreviewMediaSource[] = []
const imageSource : PreviewMediaSource = {
type: 'image',
url: imageUrl
}
sources.push(imageSource)
视频同理:
const videoSource : PreviewMediaSource = {
type: 'video',
url: videoUrl,
poster: coverUrl
}
sources.push(videoSource)
从媒体选择器映射
dyl-media-preview 不依赖任何媒体选择器。若媒体选择器的 type 使用了另一个类型别名,建议通过分支转换成明确的字面量类型,不要直接把整个对象传入。
import {
previewMedia,
type PreviewMediaSource
} from '@/uni_modules/dyl-media-preview'
const sources : PreviewMediaSource[] = []
for (let index = 0; index < result.tempFiles.length; index++) {
const file = result.tempFiles[index]
if (file.type == 'video') {
const videoSource : PreviewMediaSource = {
type: 'video',
url: file.tempFilePath,
poster: file.thumbnailPath
}
sources.push(videoSource)
} else {
const imageSource : PreviewMediaSource = {
type: 'image',
url: file.tempFilePath
}
sources.push(imageSource)
}
}
if (sources.length > 0) {
previewMedia({ sources, current: 0 })
}
错误模型
所有打开或主动关闭失败均返回 PreviewMediaFail,它继承 IUniError:
export interface PreviewMediaFail extends IUniError {
errCode : PreviewMediaErrorCode
}
公共字段:
| 字段 | 说明 |
|---|---|
errSubject |
固定为 dyl-media-preview |
errCode |
稳定数字错误码 |
errMsg |
可读错误信息,参数错误时通常包含具体字段 |
错误码
| 错误码 | 默认含义 | 常见原因 | 建议处理 |
|---|---|---|---|
9301001 |
参数不合法 | sources 为空、URL 为空、类型或 indicator 不支持 |
修正调用参数 |
9301002 |
媒体预览已打开 | 上一个预览尚未真正关闭 | 等待 onClose 后再打开 |
9301003 |
当前平台不支持 | 在 Web、小程序或其他未支持平台调用 | 使用条件编译或平台替代方案 |
9301004 |
媒体预览初始化失败 | 无法取得当前窗口、配置解析失败、原生 Activity/VC 打开失败 | 检查自定义基座和调用时机 |
9301005 |
当前没有打开的媒体预览 | 无活动预览时调用 closePreviewMedia() |
可忽略或维护页面状态 |
9301006 |
媒体加载失败 | 404、断网、文件损坏、格式不支持、签名过期 | 在 onError 中提示,并允许用户重试 |
9301006 主要通过 onError 返回。单项失败时预览页面不会自动关闭。
图片行为
- 默认按完整可见方式适配黑色背景。
- 双指手势缩放。
- 双击在原始比例与放大比例之间切换。
- 放大后可拖动图片。
- 到达横向边界后继续拖动,可把手势交给分页容器。
- 单击图片可显示或隐藏顶部工具栏。
- 图片请求按页面懒加载并使用缓存。
视频行为
autoplay: false时显示封面和播放按钮,点击后开始播放。autoplay: true时进入当前视频页后自动播放。muted: true时视频播放器静音。- 离开视频页立即暂停并释放当前播放器。
- App 进入后台时暂停并释放播放器。
- 关闭预览时释放播放器和页面引用。
- 播放位置会在当前预览会话中记录;重新进入已播放视频页时从记录位置继续。
- 播放结束后记录位置重置为开头。
- Android 使用 Media3 ExoPlayer/PlayerView;iOS 使用 AVPlayerViewController 系统控制栏。
首版不支持后台播放、画中画、DRM、自定义 Header/Cookie 和自定义视频控制栏主题。
循环模式
previewMedia({
sources,
loop: true
})
- 只有媒体数量大于 1 时循环才有实际效果。
- Android 使用首尾镜像页并在滑动停稳后无动画归位。
- iOS 在 UIPageViewController 的前后页数据源中首尾衔接。
current、onChange.current和onClose.current始终返回真实sources下标,不返回内部镜像页下标。
生命周期与资源释放
- 预览页面展示期间,插件拒绝重复打开。
- 图片只保留当前页及邻近页所需资源。
- Android 同时只创建一个活动 ExoPlayer。
- iOS 仅激活当前视频页,离页后释放 AVPlayer。
- 切页、进入后台、关闭页面时都会停止活动视频。
- 页面真正销毁后触发一次
onClose并清除活动状态。
业务层如果需要连续打开两个预览,应在第一个预览的 onClose 中打开第二个,而不是在调用 closePreviewMedia() 后立即打开。
原生依赖
Android
| 依赖 | 版本 | 用途 |
|---|---|---|
androidx.viewpager2:viewpager2 |
1.1.0 |
左右分页 |
androidx.media3:media3-common |
1.8.1 |
Media3 公共接口,显式锁定版本 |
androidx.media3:media3-exoplayer |
1.8.1 |
视频播放 |
androidx.media3:media3-exoplayer-hls |
1.8.1 |
HLS 播放 |
androidx.media3:media3-ui |
1.8.1 |
PlayerView 控制栏 |
com.github.bumptech.glide:glide |
4.16.0 |
图片加载与缓存 |
Media3 固定为 1.8.1,是因为 1.9.0 起最低 Android 版本提高到 API 23,而本插件仍支持 API 21。
iOS
- UIKit / UIPageViewController
- UIScrollView / UIImageView
- URLSession / NSCache
- AVFoundation / AVKit / AVPlayerViewController
iOS 不需要额外 CocoaPods 依赖。
不支持的能力
首版明确不包含:
- GIF 动画播放。
- DRM 视频。
- 画中画和后台播放。
- 保存、下载、分享和长按菜单。
- 自定义请求 Header/Cookie。
- 深度主题定制。
- 自定义视频控制栏。
- Web、小程序和 HarmonyOS 实现。
常见问题
1. UTSJSONObject 不能传给 PreviewMediaSource
原因是 UTS 对 Array.push() 中对象字面量的上下文类型推断有限。请先声明:
const source : PreviewMediaSource = { type: 'image', url: imageUrl }
sources.push(source)
2. Unsupported class file major version 70
当前 Android 构建使用了 JDK 26,而 Gradle/Groovy 构建链不兼容。把 HBuilderX Android JDK 切换到 JDK 17,再重新解析依赖。
3. 播放视频出现 Media3 AbstractMethodError
当前插件已移除容易受 D8 默认接口方法影响的自定义 Player.Listener,并显式锁定 media3-common:1.8.1。升级后必须重新制作自定义基座;仅热更新无法替换旧 Kotlin 和旧依赖。建议卸载设备上的旧基座后再安装。
4. ClassNotFoundException 或点击无反应
通常是仍在使用不包含插件原生代码的标准基座或旧自定义基座。重新制作并安装自定义基座。
5. HTTPS 图片或视频加载失败
检查:
- URL 是否仍有效,签名是否过期。
- 地址能否在设备网络中直接访问。
- 服务端是否支持 Range 请求和正确 MIME 类型。
- HTTPS 证书链是否受设备信任。
- 是否依赖了插件不会注入的 Header/Cookie。
失败详情可从 onError.errMsg 获取。
6. HTTP 地址无法加载
插件不会修改宿主安全策略。Android 检查 cleartext 配置,iOS 检查 ATS 配置。生产环境建议使用 HTTPS。
7. 主动关闭返回 9301005
当前没有真正处于展示状态的预览,或页面已经开始关闭。调用方可以维护自己的打开状态,并以 success、fail、onClose 为准。
8. 第二次调用返回 9301002
上一个原生页面尚未执行完关闭动画和销毁流程。等待 onClose 后再打开。
条件编译建议
首版只支持 App Android/iOS。如果共享代码也会编译到其他平台,可使用条件编译:
// #ifdef APP-ANDROID || APP-IOS
previewMedia({ sources })
// #endif
如果不做条件编译,其他平台会执行公共兜底入口并返回 9301003。
独立性说明
插件没有 uni_modules 级外部插件依赖。复制以下目录即可迁移:
uni_modules/dyl-media-preview
目标项目只需要满足版本、平台、自定义基座和 Android JDK 要求。业务代码、调试页面、dyl-media-picker、当前项目静态资源均不是运行依赖。
版本
当前版本:1.0.0。
变更记录见 CHANGELOG.md。

收藏人数:
下载插件并导入HBuilderX
下载示例项目ZIP
赞赏(0)
下载 97
赞赏 0
下载 12537444
赞赏 1947
赞赏
京公网安备:11010802035340号