search
开发文档
开发文档/应用开发/服务能力/动态照片/客户端接入指南
客户端接入指南更新时间:2026-02-05 16:49:00

一、使用说明

  • 小米动态照片SDK 基于安卓平台,提供了小米动态照片相关的接口能力,开发者可以根据接口能力,实现自己的动态照片相关的业务功能
  • 小米动态照片是由一个Jpeg文件和一个MP4文件合成的特殊结构的文件

二、接入方式

  • 开发者于开放平台发起申请->审核通过后咨询客服获取接入所需账号、密码->开发者接入SDK->联调测试->功能上线
  • 申请通过后,请咨询客服获取maven或AAR文件的账号及密码

三、调用方法说明

1、maven依赖

repositories {
maven{
url"https://repos.xiaomi.com/maven"
credentials{
username="*************"
password="**************************"
}
}
}

// 正式版本 
implementation 'com.xiaomi.camera:livephoto:1.0.3'

2、AAR文件下载到本地引入

AAR下载地址:

https://repos.xiaomi.com/maven/com/xiaomi/camera/livephoto/1.0.3/livephoto-1.0.3.aar
username="*************"
password="**************************"

SDK需要依赖com.adobe.xmp:xmpcore包:

implementation("com.adobe.xmp:xmpcore:5.1.3")

3、权限依赖

<uses-permission 
android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE" />

4、调用方法说明

动态照片SDK 采用静态方式调用,无需初始化。请在调用SDK前确保已获取对应的读写权限

4.1 判断本机设备是否支持动态照片

/**
* 返回本机设备是否支持LivePhoto
*
* @return true=支持,fasle=不支持
*/
public static boolean isDeviceSupportLivePhoto();

调用示例

boolean isSupport = MiLivePhotoTools.isDeviceSupportLivePhoto();

4.2 识别一个文件是否是动态照片

动态照片SDK提供2种方式来识别单个文件是否是Livephoto:
1、通过查询媒体数据库识别。会占用较低的内存,提供更快的识别速度,但是会被系统照明弹记录到访问媒体库的次数

2、通过读取文件头信息识别。会占用IO资源,识别速度取决的设备的IO速度,但是不会被系统照明弹记录到

4.2.1 通过查询媒体数据库识别

/**
* 识别文件是否属于livephoto照片
*
* @param context
* @param path 文件路径
* @return 是否是livephoto类型的照片
*/
public static boolean isLivePhoto(Context context, String path);

/**
* 识别文件是否属于livephoto照片
*
* @param context
* @param 文件Uri
* @return 是否是livephoto类型的照片
*/
public static boolean isLivePhoto(Context context, Uri uri);

调用示例

// path类型参数
boolean isLivephoto = MiLivePhotoTools.isLivePhoto(getBaseContext(), imagePath);
// uri类型参数
boolean isLivephoto = MiLivePhotoTools.isLivePhoto(getBaseContext(), imageUri);

4.2.2 通过文件IO方式,读取文件头信息识别

/**  
* 识别是否是Livephoto文件
* 内部通过IO访问文件,寻找文件头中Livephoto相关的xmp信息。
* @param path
* @return
*/
public static boolean isLivePhotoFile(String path);

/**  
* 识别是否是Livephoto文件
* 内部通过IO访问文件,寻找文件头中Livephoto相关的xmp信息。
* @param context
* @param uri
* @return
*/
public static boolean isLivePhotoFile(Context context, Uri uri);

调用示例

// path类型参数 
boolean isLivephoto = MiLivePhotoTools.isLivePhotoFile(imagePath);
// uri类型参数
boolean isLivephoto = MiLivePhotoTools.isLivePhotoFile(getBaseContext(), imageUri);

4.3 拆解视频和图片部分

/**
* 将动态照片的Jpeg部分和Video拆出来
* 如果jpeg或者Video输入为Null,则不拆解对应的文件。
*
* @param livePhotoPath 输入的Livephoto文件
* @param jpegOutPath 输出的jpeg文件,传Null不输出
* @param videoOutPath 输出的Video部分的文件,传Null不输出
* @return 解码后的Livephoto信息,解码失败时返回Null
*/
public static LivePhotoInfo decodeLivephoto(String livePhotoPath, String jpegOutPath, String videoOutPath);

/**
* 将动态照片的Jpeg部分和Video拆出来
* 如果jpeg或者Video输入为Null,则不拆解对应的文件。
*
* @param context
* @param livePhotoUri 输入的Livephoto文件
* @param jpegOutUri 输出的jpeg文件,传Null不输出
* @param videoOutUri 输出的Video部分的文件,传Null不输出
* @return 解码后的Livephoto信息,解码失败时返回Null
*/
public static LivePhotoInfo decodeLivephoto(Context context, Uri livePhotoUri, Uri jpegOutUri, Uri videoOutUri);

返回一个MiLivePhotoInfo对象记录了一些解码信息,如下:

/**
* LivePhoto照片的封装对象,包含Livephoto的一些信息。
*/
public class MiLivePhotoInfo {
/**
* 类型
* 1 = 动态照片
* 0 = 非动态照片
*/
private int type;
/**
* 版本
*/
private int version;
/**
* 视频部分相对于图片数据尾部的偏移量,等于视频部分的字节大小
*/
private int videoOffset;
/**
* 表示图片在视频中对应帧的时间戳(单位:微秒)
*/
private long videoPresentationTimestampUs;

public MiLivePhotoInfo(){

}
public MiLivePhotoInfo(int type,int version, int videoOffset, long videoPresentationTimestampUs) {
this.type = type;
this.version = version;
this.videoOffset = videoOffset;
this.videoPresentationTimestampUs = videoPresentationTimestampUs;
}

public int getType() {
return type;
}

public void setType(int type) {
this.type = type;
}

public int getVersion() {
return version;
}

public void setVersion(int version) {
this.version = version;
}

public int getVideoOffset() {
return videoOffset;
}

public void setVideoOffset(int videoOffset) {
this.videoOffset = videoOffset;
}

public long getVideoPresentationTimestampUs() {
return videoPresentationTimestampUs;
}

public void setVideoPresentationTimestampUs(long videoPresentationTimestampUs) {
this.videoPresentationTimestampUs = videoPresentationTimestampUs;
}
}

调用示例

// path类型参数
LivePhotoInfo info = MiLivePhotoTools.decodeLivephoto(srcPath,decodeImagePath,decodeVideoPath);
// uri类型参数
LivePhotoInfo info = MiLivePhotoTools.decodeLivephoto(getBaseContext(),srcUri,decodeImageUri,decodeVideoUri);

4.4 合成动态照片

提供合成动态照片的能力,接口描述如下
其中videoPresentationTimestampUs参数是必传的参数,表示与jpeg对应的视频帧的显示时间戳。如果不确定该值,开发者可以传入0,但是不能保证合成后的照片在相册中正常播放和编辑。如果开发者希望合成后的图能够在小米相册中正确播放和编辑,那么开发者必须传入正确的值

/**  
* 根据输入的jpeg图像和Video文件,生成一张Livephoto格式的jpeg文件,输出到outFilePath对应的File中
* 并返回合成是否成功
*
* @param jpegPath 要合成的Jpeg文件
* @param videoPath 要合成的Video文件
* @param videoPresentationTimestampUs jpeg在Video中对应帧的时间戳
* @param outFilePath 合成后的文件路径
* @param insertMediaStore (可选参数)是否插入媒体库,默认true
* @return 合成是否成功
*/
public static boolean muxLivephoto(String jpegPath, String videoPath, long videoPresentationTimestampUs, String outFilePath, boolean insertMediaStore);

/**
* 根据输入的jpeg图像和Video文件,生成一张Livephoto格式的jpeg文件,输出到outFileUri描述的File中
* 并返回合成是否成功
*
* @param context
* @param jpegUri 要合成的Jpeg文件
* @param videoUri 要合成的Video文件
* @param videoPresentationTimestampUs jpeg在Video中对应帧的时间戳
* @param outFileUri 合成后的文件Uri
* @param insertMediaStore (可选参数)是否插入媒体库,默认true
* @return 合成是否成功
*/
public static boolean muxLivephoto(Context context, Uri jpegUri, Uri videoUri, long videoPresentationTimestampUs, Uri outFileUri, boolean insertMediaStore);

调用示例

// path类型参数
boolean succ = MiLivePhotoTools.muxLivephoto(getBaseContext(),jpegPath,videoPath,videoPresentationTimestampUs,imageOutPath);
// uri类型参数
boolean succ = MiLivePhotoTools.muxLivephoto(getBaseContext(),jpegUri,videoUri,videoPresentationTimestampUs,imageOutUri);

4.4.1 合成动图的设备兼容性

  • 仅Xiaomi HyperOS系统支持

当前仅Xiaomi HyperOS版本的系统,支持播放三方合成的动图,MIUI系统不识别三方合成的动图,建议三方开发者,先判断本机设备的系统是否满足条件

Xiaomi HyperOS判断方法:

1. 读取系统属性[ro.miui.ui.version.code],取值是816的就是Xiaomi HyperOS

2. 取值小于816的就是MIUI14及之前的版本

  • 系统相册的versionCode大于407621,才能支持播放三方合成的动图

包名:com.miui.gallery

versionCode:> 407621

/**
* 判断当前设备是否是Xiaomi HyperOS系统
*
* @return
*/
public static boolean isHyperOs() {
try {
String code = com.xiaomi.exif.SystemProperties.get("ro.miui.ui.version.code");
if (Integer.valueOf(codeStr) >= 816) {
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}

/**
* 返回相册是否支持播放三方的动图
* @param context
* @return
*/
public static boolean isMiuiGallerySupport(Context context) {
try {
PackageInfo packageInfo = context.getPackageManager()
.getPackageInfo("com.miui.gallery", 0);
if (packageInfo != null) {
return packageInfo.versionCode > 407621;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}

4.5 获取设备的全部动图

这里仅提供示例代码,开发者根据自己需求,选择增加分页或者查询更多的字段信息

/**
* 分页查询动图记录
*
* @param context 上下文
* @param page 页码,从 1 开始
* @param pageSize 每页数量
* @return 动图路径列表
*/
public static List<String> listAllLivePhoto(Context context, int page, int pageSize) {
// 参数校验
if (page < 1) page = 1;
if (pageSize < 1) pageSize = 20;
int offset = (page - 1) * pageSize;

// 1. 定义查询列
String[] projection = {
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DATA
};

// 2. 构造查询条件
String selection = MediaStore.Images.Media.MIME_TYPE + " IN (?, ?) AND (" +
MediaStore.Images.Media.XMP + " LIKE ? OR " +
MediaStore.Images.Media.XMP + " LIKE ?)";

String[] selectionArgs = new String[]{
"image/jpeg",
"image/heic",
"%MicroVideo%",
"%MotionPhoto%"
};

// 默认按添加时间降序,确保分页数据连续
String sortOrder = MediaStore.Images.Media.DATE_ADDED + " DESC";

Cursor cursor = null;
try {
// 3. 根据版本选择分页策略
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// API 26+ 使用 Bundle 进行真正的数据库分页 (性能最佳)
Bundle queryArgs = new Bundle();
queryArgs.putString(ContentResolver.QUERY_ARG_SQL_SELECTION, selection);
queryArgs.putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, selectionArgs);
queryArgs.putString(ContentResolver.QUERY_ARG_SORT_COLUMNS, MediaStore.Images.Media.DATE_ADDED);
queryArgs.putInt(ContentResolver.QUERY_ARG_SORT_DIRECTION, ContentResolver.QUERY_SORT_DIRECTION_DESCENDING);
queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, pageSize);
queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, offset);

cursor = context.getContentResolver().query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
projection,
queryArgs,
null);
} else {
// API < 26 使用传统查询 + 游标位移
cursor = context.getContentResolver().query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
projection,
selection,
selectionArgs,
sortOrder);
}

if (cursor == null) {
return Collections.emptyList();
}

// 4. 解析结果
List<String> resultList = new ArrayList<>();
// 如果是 API 26+,Cursor 里只有当前页的数据,count 就是 pageSize (或更少)
// 如果是旧版本,Cursor 是全量的,这里预分配 pageSize 即可
int expectedSize = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) ? cursor.getCount() : pageSize;
resultList = new ArrayList<>(expectedSize);

int dataColumnIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA);
if (dataColumnIndex == -1) {
return Collections.emptyList();
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// API 26+: 已经是分页结果,直接遍历
if (cursor.moveToFirst()) {
do {
String path = cursor.getString(dataColumnIndex);
if (path != null && !path.isEmpty()) {
resultList.add(path);
}
} while (cursor.moveToNext());
}
} else {
// API < 26: 手动跳过前面的数据 (游标分页)
if (cursor.moveToPosition(offset)) {
int count = 0;
do {
String path = cursor.getString(dataColumnIndex);
if (path != null && !path.isEmpty()) {
resultList.add(path);
}
count++;
} while (count < pageSize && cursor.moveToNext());
}
}

return resultList;

} catch (IllegalArgumentException | android.database.sqlite.SQLiteException e) {
Log.e(TAG, "Device does not support querying XMP from MediaStore: " + e.getMessage());
return Collections.emptyList();
} catch (Exception e) {
Log.e(TAG, "listAllLivePhoto err: " + e);
return Collections.emptyList();
} finally {
if (cursor != null) {
cursor.close();
}
}
}

4.6 动态照片的展示

当前大部分机型的动态照片封面和视频部分都支持水印,因此封面图片和视频的比例是一致的。

但部分机型的动态照片,仅封面支持水印,在添加图外水印后,封面图片和视频的比例不一致,其中视频部分的比例跟封面图片去掉水印部分后的比例是一致的。为了保证播放的封面到视频部分的展示体验,开发者需要自行适配,可参考小米系统相册的展示效果。

上一篇:开发准备
下一篇:能力介绍
文档内容是否有帮助?
有帮助
无帮助