更新记录

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-androidutssdk/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 的前后页数据源中首尾衔接。
  • currentonChange.currentonClose.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

当前没有真正处于展示状态的预览,或页面已经开始关闭。调用方可以维护自己的打开状态,并以 successfailonClose 为准。

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

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

许可协议

MIT协议

暂无用户评论。