chore: 优化检索效率

This commit is contained in:
Junhui Chen 2025-07-16 19:26:10 +08:00
parent 5abb5a6836
commit 2505df0182
3 changed files with 36 additions and 231 deletions

View File

@ -50,85 +50,67 @@ const MediaStatsScreen = () => {
return; return;
} }
// 2. 设置时间范围 // 2. 设置时间范围直接使用Date对象
const dateRange = getDateRange(timeRange); const dateRange = getDateRange(timeRange);
const createdAfter = dateRange ? Math.floor(dateRange.start.getTime() / 1000) : 0;
const endTime = dateRange?.end ? Math.floor(dateRange.end.getTime() / 1000) : undefined;
// 3. 分页获取媒体资源每次10条 // 3. 分页获取媒体资源
let hasNextPage = true;
let after = undefined;
let allAssets: MediaLibrary.Asset[] = []; let allAssets: MediaLibrary.Asset[] = [];
const pageSize = 10; // 每次获取10条 let hasNextPage = true;
let after: MediaLibrary.AssetRef | undefined = undefined;
const pageSize = 100; // 增加每次获取的数量以提高效率
while (hasNextPage) { while (hasNextPage) {
const media = await MediaLibrary.getAssetsAsync({ const media = await MediaLibrary.getAssetsAsync({
first: pageSize, first: pageSize,
after, after,
mediaType: ['photo', 'video', 'audio', 'unknown'], sortBy: ['creationTime'],
sortBy: 'creationTime', // 按创建时间降序,最新的在前面 mediaType: ['photo', 'video', 'audio'],
createdAfter: Date.now() - 24 * 30 * 12 * 60 * 60 * 1000, // 时间戳(毫秒) createdAfter: dateRange?.start,
createdBefore: Date.now(), // 时间戳(毫秒) createdBefore: dateRange?.end,
}); });
// 如果没有数据,直接退出 if (media.assets.length > 0) {
if (media.assets.length === 0) { allAssets.push(...media.assets);
break;
} }
// 检查每条记录是否在时间范围内 hasNextPage = media.hasNextPage;
for (const asset of media.assets) {
const assetTime = asset.creationTime ? new Date(asset.creationTime).getTime() / 1000 : 0;
// 如果设置了结束时间,并且当前记录的时间早于开始时间,则停止
if (endTime && assetTime > endTime) {
continue; // 跳过这条记录
}
// 如果设置了开始时间,并且当前记录的时间早于开始时间,则停止
if (createdAfter && assetTime < createdAfter) {
hasNextPage = false;
break;
}
allAssets.push(asset);
}
// 更新游标和是否还有下一页
hasNextPage = media.hasNextPage && media.assets.length === pageSize;
after = media.endCursor; after = media.endCursor;
// 如果没有更多数据或者已经获取了足够的数据 // 可选:增加一个最大获取上限,防止无限循环
if (!hasNextPage || allAssets.length >= 1000) { if (allAssets.length > 2000) {
console.warn('已达到2000个媒体文件的上限');
break; break;
} }
} }
console.log(`总共获取到 ${allAssets.length} 个媒体文件`); console.log(`总共获取到 ${allAssets.length} 个媒体文件`);
// 4. 统计不同类型媒体的数量 // 4. 使用 reduce 进行统计,更高效
const stats: MediaStats = { const stats = allAssets.reduce<MediaStats>((acc, asset) => {
total: allAssets.length, acc.total++;
photos: 0, switch (asset.mediaType) {
videos: 0, case 'photo':
audios: 0, acc.photos++;
others: 0, break;
byMonth: {}, case 'video':
}; acc.videos++;
break;
case 'audio':
acc.audios++;
break;
default:
acc.others++;
break;
}
allAssets.forEach(asset => {
// 统计类型
if (asset.mediaType === 'photo') stats.photos++;
else if (asset.mediaType === 'video') stats.videos++;
else if (asset.mediaType === 'audio') stats.audios++;
else stats.others++;
// 按月份统计
if (asset.creationTime) { if (asset.creationTime) {
const date = new Date(asset.creationTime); const date = new Date(asset.creationTime);
const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
stats.byMonth[monthKey] = (stats.byMonth[monthKey] || 0) + 1; acc.byMonth[monthKey] = (acc.byMonth[monthKey] || 0) + 1;
} }
return acc;
}, {
total: 0, photos: 0, videos: 0, audios: 0, others: 0, byMonth: {},
}); });
setStats(stats); setStats(stats);

View File

@ -1,39 +0,0 @@
// import * as ImageManipulator from 'expo-image-manipulator';
// import * as VideoThumbnail from 'expo-video-thumbnails';
// export const extractVideoThumbnail = async (videoUri: string): Promise<{ uri: string; file: File }> => {
// try {
// // 获取视频的第一帧
// const { uri: thumbnailUri } = await VideoThumbnail.getThumbnailAsync(
// videoUri,
// {
// time: 1000, // 1秒的位置
// quality: 0.8,
// }
// );
// // 转换为 WebP 格式
// const manipResult = await ImageManipulator.manipulateAsync(
// thumbnailUri,
// [{ resize: { width: 800 } }], // 调整大小以提高性能
// {
// compress: 0.8,
// format: ImageManipulator.SaveFormat.WEBP
// }
// );
// // 转换为 File 对象
// const response = await fetch(manipResult.uri);
// const blob = await response.blob();
// const file = new File(
// [blob],
// `thumb_${Date.now()}.webp`,
// { type: 'image/webp' }
// );
// return { uri: manipResult.uri, file };
// } catch (error) {
// console.error('Error generating video thumbnail:', error);
// throw new Error('无法生成视频缩略图: ' + (error instanceof Error ? error.message : String(error)));
// }
// };

View File

@ -1,138 +0,0 @@
// /**
// * 从视频文件中提取第一帧并返回为File对象
// * @param videoFile 视频文件
// * @returns 包含视频第一帧的File对象
// */
// export const extractVideoFirstFrame = (videoFile: File): Promise<File> => {
// return new Promise((resolve, reject) => {
// const videoUrl = URL.createObjectURL(videoFile);
// const video = document.createElement('video');
// video.src = videoUrl;
// video.crossOrigin = 'anonymous';
// video.muted = true;
// video.preload = 'metadata';
// video.onloadeddata = () => {
// try {
// // 设置视频时间到第一帧
// video.currentTime = 0.1;
// } catch (e) {
// URL.revokeObjectURL(videoUrl);
// reject(e);
// }
// };
// video.onseeked = () => {
// try {
// const canvas = document.createElement('canvas');
// canvas.width = video.videoWidth;
// canvas.height = video.videoHeight;
// const ctx = canvas.getContext('2d');
// if (!ctx) {
// throw new Error('无法获取canvas上下文');
// }
// // 绘制视频帧到canvas
// ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
// // 将canvas转换为DataURL
// const dataUrl = canvas.toDataURL('image/jpeg');
// // 将DataURL转换为Blob
// const byteString = atob(dataUrl.split(',')[1]);
// const mimeString = dataUrl.split(',')[0].split(':')[1].split(';')[0];
// const ab = new ArrayBuffer(byteString.length);
// const ia = new Uint8Array(ab);
// for (let i = 0; i < byteString.length; i++) {
// ia[i] = byteString.charCodeAt(i);
// }
// const blob = new Blob([ab], { type: mimeString });
// // 创建File对象
// const frameFile = new File(
// [blob],
// `${videoFile.name.replace(/\.[^/.]+$/, '')}_frame.jpg`,
// { type: 'image/jpeg' }
// );
// // 清理URL对象
// URL.revokeObjectURL(videoUrl);
// resolve(frameFile);
// } catch (e) {
// URL.revokeObjectURL(videoUrl);
// reject(e);
// }
// };
// video.onerror = () => {
// URL.revokeObjectURL(videoUrl);
// reject(new Error('视频加载失败'));
// };
// });
// };
// // 获取视频时长
// export const getVideoDuration = (file: File): Promise<number> => {
// return new Promise((resolve) => {
// const video = document.createElement('video');
// video.preload = 'metadata';
// video.onloadedmetadata = () => {
// URL.revokeObjectURL(video.src);
// resolve(video.duration);
// };
// video.onerror = () => {
// URL.revokeObjectURL(video.src);
// resolve(0); // Return 0 if we can't get the duration
// };
// video.src = URL.createObjectURL(file);
// });
// };
// // 根据 mp4 的url来获取视频时长
// /**
// * 根据视频URL获取视频时长
// * @param videoUrl 视频的URL
// * @returns 返回一个Promise解析为视频时长
// */
// export const getVideoDurationFromUrl = async (videoUrl: string): Promise<number> => {
// return await new Promise((resolve, reject) => {
// // 创建临时的video元素
// const video = document.createElement('video');
// // 设置为只加载元数据,不加载整个视频
// video.preload = 'metadata';
// // 处理加载成功
// video.onloadedmetadata = () => {
// // 释放URL对象
// URL.revokeObjectURL(video.src);
// // 返回视频时长(秒)
// resolve(video.duration);
// };
// // 处理加载错误
// video.onerror = () => {
// URL.revokeObjectURL(video.src);
// reject(new Error('无法加载视频'));
// };
// // 处理网络错误
// video.onabort = () => {
// URL.revokeObjectURL(video.src);
// reject(new Error('视频加载被中止'));
// };
// // 设置视频源
// video.src = videoUrl;
// // 添加跨域属性(如果需要)
// video.setAttribute('crossOrigin', 'anonymous');
// // 开始加载元数据
// video.load();
// });
// };