diff --git a/components/file-upload/getTotal.tsx b/components/file-upload/getTotal.tsx index b5af839..cb2b3d4 100644 --- a/components/file-upload/getTotal.tsx +++ b/components/file-upload/getTotal.tsx @@ -50,85 +50,67 @@ const MediaStatsScreen = () => { return; } - // 2. 设置时间范围 + // 2. 设置时间范围,直接使用Date对象 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条 - let hasNextPage = true; - let after = undefined; + // 3. 分页获取媒体资源 let allAssets: MediaLibrary.Asset[] = []; - const pageSize = 10; // 每次获取10条 + let hasNextPage = true; + let after: MediaLibrary.AssetRef | undefined = undefined; + const pageSize = 100; // 增加每次获取的数量以提高效率 while (hasNextPage) { const media = await MediaLibrary.getAssetsAsync({ first: pageSize, after, - mediaType: ['photo', 'video', 'audio', 'unknown'], - sortBy: 'creationTime', // 按创建时间降序,最新的在前面 - createdAfter: Date.now() - 24 * 30 * 12 * 60 * 60 * 1000, // 时间戳(毫秒) - createdBefore: Date.now(), // 时间戳(毫秒) + sortBy: ['creationTime'], + mediaType: ['photo', 'video', 'audio'], + createdAfter: dateRange?.start, + createdBefore: dateRange?.end, }); - // 如果没有数据,直接退出 - if (media.assets.length === 0) { - break; + if (media.assets.length > 0) { + allAssets.push(...media.assets); } - // 检查每条记录是否在时间范围内 - 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; + hasNextPage = media.hasNextPage; after = media.endCursor; - // 如果没有更多数据或者已经获取了足够的数据 - if (!hasNextPage || allAssets.length >= 1000) { + // 可选:增加一个最大获取上限,防止无限循环 + if (allAssets.length > 2000) { + console.warn('已达到2000个媒体文件的上限'); break; } } console.log(`总共获取到 ${allAssets.length} 个媒体文件`); - // 4. 统计不同类型媒体的数量 - const stats: MediaStats = { - total: allAssets.length, - photos: 0, - videos: 0, - audios: 0, - others: 0, - byMonth: {}, - }; + // 4. 使用 reduce 进行统计,更高效 + const stats = allAssets.reduce((acc, asset) => { + acc.total++; + switch (asset.mediaType) { + case 'photo': + acc.photos++; + break; + 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) { const date = new Date(asset.creationTime); 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); diff --git a/components/file-upload/utils/video-thumbnail.ts b/components/file-upload/utils/video-thumbnail.ts deleted file mode 100644 index c3af2cf..0000000 --- a/components/file-upload/utils/video-thumbnail.ts +++ /dev/null @@ -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))); -// } -// }; \ No newline at end of file diff --git a/components/file-upload/utils/videoUtils.ts b/components/file-upload/utils/videoUtils.ts deleted file mode 100644 index c5ec400..0000000 --- a/components/file-upload/utils/videoUtils.ts +++ /dev/null @@ -1,138 +0,0 @@ -// /** -// * 从视频文件中提取第一帧并返回为File对象 -// * @param videoFile 视频文件 -// * @returns 包含视频第一帧的File对象 -// */ -// export const extractVideoFirstFrame = (videoFile: File): Promise => { -// 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 => { -// 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 => { -// 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(); -// }); -// };