Utility.ets 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210
  1. import { BusinessError, pasteboard } from '@kit.BasicServicesKit';
  2. import {
  3. AppUtil,
  4. ArrayUtil,
  5. Base64Util,
  6. DateUtil, FileUtil, ImageUtil, LogUtil,
  7. MD5,
  8. PreferencesUtil,
  9. RandomUtil, StrUtil, ToastUtil } from '@pura/harmony-utils';
  10. import { media } from '@kit.MediaKit';
  11. import fs from '@ohos.file.fs';
  12. import { image } from '@kit.ImageKit';
  13. import fileIo from '@ohos.file.fs';
  14. import { VideoItem } from '../../viewmodel/VideoItem';
  15. import { photoAccessHelper } from '@kit.MediaLibraryKit';
  16. import { dataSharePredicates, uniformTypeDescriptor } from '@kit.ArkData';
  17. import { systemShare } from '@kit.ShareKit';
  18. import { fileUri } from '@kit.CoreFileKit';
  19. import { common, UIAbility, Want } from '@kit.AbilityKit';
  20. import { CommonConstants } from '../constants/CommonConstants';
  21. import { window } from '@kit.ArkUI';
  22. import { bundleManager } from '@kit.AbilityKit'
  23. import { VipPage } from '../../pages/VipPage';
  24. import NetAxiosUtil from './NetAxiosUtil';
  25. // import userFileManager from '@ohos.filemanagement.userFileManager';
  26. export class Utility {
  27. private constructor() {}
  28. static isOpenTime():boolean{
  29. const currentDate = new Date();
  30. const targetDate = new Date(CommonConstants.OPEN_DATE);
  31. if (currentDate > targetDate) {
  32. return true
  33. }
  34. return false
  35. }
  36. //是否赞助
  37. static isNoble():boolean{
  38. if(!Utility.isOpenTime())//提交审核的审核,刚刚开始可以全部是赞助用户。
  39. return true
  40. return PreferencesUtil.getBooleanSync('isNoble',false)
  41. }
  42. //判断用户安装app是否超过2天,超过2天(2800分钟)才展示广告 显示广告 24*60=1440 1440*2=2800
  43. static isPassInstallTime(day:number):boolean{
  44. //获取当前时间
  45. let currentTime = new Date().getTime();
  46. // 尝试从存储中获取安装时间
  47. let installTime = getInstallTime();
  48. if (installTime===0||installTime === null|| installTime===undefined) {
  49. // 如果没有存储安装时间,则存储当前时间为安装时间
  50. setInstallTime(currentTime);
  51. return false
  52. } else {
  53. // 计算时间差(以分钟为单位)
  54. const timeDifference = (currentTime - installTime) / (1000 * 60);
  55. LogUtil.debug("onecold timeDifference=" + timeDifference)
  56. // 如果超过5分钟,显示广告 24*60=1440 1440*2=2800
  57. if (timeDifference > 24*60*day) {
  58. return true
  59. }else{
  60. return false
  61. }
  62. }
  63. }
  64. static optimizedFormat(speed: number): string {
  65. if (speed) {
  66. const str = speed.toFixed(2);
  67. let end = str.length;
  68. while (end > 0 && (str[end - 1] === '0' || str[end - 1] === '.')) {
  69. end--;
  70. if (str[end] === '.') {
  71. break;
  72. }
  73. }
  74. return str.slice(0, end || 1) + 'x';
  75. } else {
  76. return '1x'
  77. }
  78. }
  79. static isHaoOpenTime():boolean{
  80. const currentDate = new Date();
  81. const targetDate = new Date('2025-04-20');
  82. if (currentDate < targetDate) {
  83. return true
  84. }
  85. return false
  86. }
  87. static setNoble(expireDate:string){
  88. let isNoble = false
  89. if(StrUtil.isNotEmpty(expireDate)){
  90. if(expireDate===VipPage.FOEVEER_DATE){
  91. isNoble = true
  92. }else{
  93. const currentDate = new Date();
  94. const targetDate = DateUtil.getFormatDate(expireDate)
  95. if (currentDate > targetDate) {
  96. isNoble = false
  97. }else{
  98. isNoble = true
  99. }
  100. }
  101. }
  102. PreferencesUtil.putSync('isNoble',isNoble)
  103. }
  104. static addDays(date: Date, days: number): Date {
  105. const result = new Date(date);
  106. result.setDate(result.getDate() + days);
  107. return result;
  108. }
  109. static convertToKHz(sampleRateHz: string|undefined): string {
  110. if(StrUtil.isEmpty(sampleRateHz)||sampleRateHz ===undefined)
  111. return '0KHz'
  112. const sampleRateKHz = Number(sampleRateHz) / 1000;
  113. return `${sampleRateKHz} KHz`;
  114. }
  115. //根据字节获取大小
  116. static formatFileSize(bytes:number) {
  117. const units = ['Bytes', 'Kbps', 'Mbps'];
  118. let size = bytes;
  119. let unitIndex = 0;
  120. while (size >= 1024 && unitIndex < units.length - 1) {
  121. size /= 1024;
  122. unitIndex++;
  123. }
  124. if(size > 1024&&unitIndex==2){
  125. size = size*0.1
  126. }
  127. if(size > 1024&&unitIndex==2){
  128. size = size*0.1
  129. }
  130. if(size > 480&&unitIndex==2){
  131. size = size*0.5
  132. }
  133. // 保留两位小数, 四舍五入
  134. size = Math.round(size * 10) / 10;
  135. return size + units[unitIndex]
  136. }
  137. //根据字节获取大小无单位
  138. static formatFileSizeWithout(bytes:number) {
  139. const units = ['Bytes', 'Kbps', 'Mbps'];
  140. let size = bytes;
  141. let unitIndex = 0;
  142. while (size >= 1024 && unitIndex < units.length - 1) {
  143. size /= 1024;
  144. unitIndex++;
  145. }
  146. if(size > 1024&&unitIndex==2){
  147. size = size*0.1
  148. }
  149. if(size > 1024&&unitIndex==2){
  150. size = size*0.1
  151. }
  152. if(size > 480&&unitIndex==2){
  153. size = size*0.5
  154. }
  155. // 保留两位小数, 四舍五入
  156. size = Math.round(size * 10) / 10;
  157. return size
  158. }
  159. static copyText(text:string):boolean{
  160. const pasteboardData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN,text)
  161. const systemPasteboard = pasteboard.getSystemPasteboard()
  162. systemPasteboard.setData(pasteboardData)
  163. systemPasteboard.getData().then((data)=>{
  164. return data
  165. })
  166. return false
  167. }
  168. static getDbm(rssi:number):number{
  169. return Math.round((rssi / 2) + 100)
  170. }
  171. //根据数字形式的IP地址获取字符串形式的IP地址
  172. static getIpAddrFromNum(ipNum: number): string {
  173. return (ipNum >>> 24) + '.' + (ipNum >> 16 & 0xFF) + '.' + (ipNum >> 8 & 0xFF) + '.' + (ipNum & 0xFF);
  174. }
  175. static resolveIP(ip:number) {
  176. let address: string = ip.toString()
  177. if (address === '0') {
  178. return '00:00:000:000'
  179. }
  180. address.substring(0, 2)
  181. return `${address.substring(0, 2)}:${address.substring(2, 4)}:${address.substring(4, 7)}:${address.substring(7, 10)}`
  182. }
  183. static isFreeTime():boolean{
  184. if(DateUtil.isWeekend()){
  185. return true
  186. }
  187. let str = DateUtil.getFormatDateStr(new Date(), 'HH')
  188. let a = Number(str);
  189. if(isNaN(a))
  190. return false
  191. // ToastUtil.showToast('a='+a)
  192. if (a >= 7 && a <13) {//白天
  193. return false;
  194. }
  195. if (a >= 13 && a <20) {//白天
  196. return false;
  197. }
  198. if (a >= 0 && a < 7) {//凌晨
  199. return true;
  200. }
  201. if (a >= 20 && a <= 24) {//晚上
  202. return true;
  203. }
  204. return false
  205. }
  206. //判断是否是音乐文件
  207. static isMusicByExtension(filename:string) {
  208. const extensions = CommonConstants.REAL_MUSIC_FORMAT
  209. const lastIndex = filename.lastIndexOf('.');
  210. if (lastIndex!== -1) {
  211. const fileExtension = filename.slice(lastIndex).toLowerCase();
  212. return extensions.includes(fileExtension);
  213. }
  214. return false;
  215. }
  216. //判断是否是媒体文件 音乐和视频都可以
  217. static isMeidaByExtension(filename:string) {
  218. const extensions = CommonConstants.MEDIA_FORMAT
  219. const lastIndex = filename.lastIndexOf('.');
  220. if (lastIndex!== -1) {
  221. const fileExtension = filename.slice(lastIndex).toLowerCase();
  222. return extensions.includes(fileExtension);
  223. }
  224. return false;
  225. }
  226. //判断是否是视频文件
  227. static isVideoByExtension(filename:string) {
  228. const extensions = CommonConstants.VIDEO_FORMAT
  229. const lastIndex = filename.lastIndexOf('.');
  230. if (lastIndex!== -1) {
  231. const fileExtension = filename.slice(lastIndex).toLowerCase();
  232. return extensions.includes(fileExtension);
  233. }
  234. return false;
  235. }
  236. // 获取缩略图
  237. static async getFetchFrameByTime(filePath: string) {
  238. if(Utility.isMusicByExtension(filePath)){
  239. return undefined
  240. }
  241. let pixelMap:image.PixelMap|undefined =undefined;
  242. try{
  243. // 创建AVImageGenerator对象
  244. let avImageGenerator: media.AVImageGenerator = await media.createAVImageGenerator()
  245. let file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
  246. let avFileDescriptor: media.AVFileDescriptor = { fd: file.fd };
  247. avImageGenerator.fdSrc = avFileDescriptor;
  248. // 初始化入参
  249. let timeUs = 0
  250. let queryOption = media.AVImageQueryOptions.AV_IMAGE_QUERY_NEXT_SYNC
  251. let param: media.PixelMapParams = {
  252. width : 300,
  253. height : 400,
  254. }
  255. // 获取缩略图(promise模式)
  256. pixelMap = await avImageGenerator.fetchFrameByTime(timeUs, queryOption, param)
  257. // 释放资源(promise模式)
  258. avImageGenerator.release()
  259. console.info(`release success.`)
  260. fs.closeSync(file);
  261. }catch (error) {
  262. console.error('uriGetAssets failed with err: ' + JSON.stringify(error));
  263. }
  264. return pixelMap
  265. }
  266. // 获取fd文件路径
  267. static async getFdDir(path:string){
  268. let fdPath = 'fd://';
  269. let file = await fileIo.open(path)
  270. fdPath = fdPath + '' + file.fd;
  271. return fdPath
  272. }
  273. // 根据uri获取文件名称
  274. static getMediaNameByUri(myUri: string) {
  275. let myFileName = (myUri.split('/').pop()) as string;
  276. return decodeURIComponent(myFileName)
  277. }
  278. static msToStandardTime(ms:string):string{
  279. let date = new Date(ms);
  280. let hours = date.getHours();
  281. let minutes = date.getMinutes();
  282. let seconds = date.getSeconds();
  283. return hours + ':' + minutes + ':' + seconds;
  284. }
  285. //获取资源的属性值
  286. static async uriGetAssets(context:Context,uri:string,type:number): Promise<VideoItem> {
  287. let isFromFileMan:boolean = false
  288. if(uri.startsWith('file://media')){//图库的视频
  289. isFromFileMan = false
  290. }else{//从文件管理器
  291. isFromFileMan = true
  292. }
  293. if(isFromFileMan){
  294. return await Utility.uriGetAssetsFromFile(context,uri,type);
  295. }
  296. try {
  297. let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
  298. let predicates: dataSharePredicates.DataSharePredicates = new dataSharePredicates.DataSharePredicates();
  299. // 配置查询条件,使用PhotoViewPicker选择图片返回的uri进行查询
  300. predicates.equalTo('uri', uri);
  301. let fetchOption: photoAccessHelper.FetchOptions = {
  302. fetchColumns: [photoAccessHelper.PhotoKeys.WIDTH, photoAccessHelper.PhotoKeys.HEIGHT,
  303. photoAccessHelper.PhotoKeys.TITLE, photoAccessHelper.PhotoKeys.SIZE, photoAccessHelper.PhotoKeys.DURATION],
  304. predicates: predicates
  305. };
  306. let fetchResult: photoAccessHelper.FetchResult<photoAccessHelper.PhotoAsset> =
  307. await phAccessHelper.getAssets(fetchOption);
  308. // 得到uri对应的PhotoAsset对象,读取文件的部分信息
  309. const asset: photoAccessHelper.PhotoAsset = await fetchResult.getFirstObject();
  310. let fd = await Utility.getFdDir(uri)
  311. let videoSize = asset.get(photoAccessHelper.PhotoKeys.SIZE).toString()
  312. let videoTime = asset.get(photoAccessHelper.PhotoKeys.DATE_ADDED_MS).toString()
  313. let fileSize = Utility.formatFSize(Number(videoSize))
  314. let cTime = Utility.getFormatDateStr(videoTime,'yyyy-MM-dd HH:mm')
  315. // let modifyDate = asset.get(photoAccessHelper.PhotoKeys.DATE_MODIFIED_MS).toString()
  316. let item:VideoItem = new VideoItem( asset.displayName, fd,uri,type,Number(videoSize),cTime,
  317. await Utility.getFetchFrameByTime(uri),fileSize)
  318. console.info('asset displayName: ', asset.displayName);
  319. console.info('asset uri: ', asset.uri);
  320. console.info('asset photoType: ', asset.photoType);
  321. console.info('asset width: ', asset.get(photoAccessHelper.PhotoKeys.WIDTH));
  322. console.info('asset height: ', asset.get(photoAccessHelper.PhotoKeys.HEIGHT));
  323. console.info('asset SIZE: ' + asset.get(photoAccessHelper.PhotoKeys.SIZE));
  324. console.info('asset DURATION: ' + asset.get(photoAccessHelper.PhotoKeys.DURATION));
  325. // 获取缩略图
  326. // asset.getThumbnail((err, pixelMap) => {
  327. // if (err == undefined) {
  328. // console.info('getThumbnail successful ' + JSON.stringify(pixelMap));
  329. // } else {
  330. // console.error('getThumbnail fail', err);
  331. // }
  332. // });
  333. return item
  334. } catch (error) {
  335. console.error('uriGetAssets failed with err: ' + JSON.stringify(error));
  336. return new VideoItem('','','',0,0,'')
  337. }
  338. }
  339. static getTimestampFromDateStr(dateStr: string, format: string = 'yyyy-MM-dd HH:mm'): number {
  340. // 定义正则表达式以匹配日期字符串中的各部分
  341. const regex = format
  342. .replace('yyyy', '(\\d{4})')
  343. .replace('MM', '(\\d{2})')
  344. .replace('dd', '(\\d{2})')
  345. .replace('HH', '(\\d{2})')
  346. .replace('mm', '(\\d{2})');
  347. const match = new RegExp(regex).exec(dateStr);
  348. if (!match) {
  349. throw new Error('Invalid date string or format');
  350. }
  351. // 解析匹配结果
  352. const year = Number(match[1]);
  353. const month = Number(match[2]);
  354. const day = Number(match[3]);
  355. const hours = Number(match[4]);
  356. const minutes = Number(match[5]);
  357. // 创建 Date 对象
  358. const dateObj = new Date(year, month - 1, day, hours, minutes);
  359. // 返回时间戳(以秒为单位)
  360. return Math.floor(dateObj.getTime() / 1000);
  361. }
  362. static getFormatDateStr(date: number | string | Date, format: string = 'yyyy-MM-dd HH:mm:ss'): string {
  363. // 将输入转换为 Date 对象
  364. let dateObj: Date;
  365. if (typeof date === 'number') {
  366. // 如果是十位时间戳,转换为毫秒数
  367. dateObj = new Date(date.toString().length === 10 ? date * 1000 : date);
  368. } else if (typeof date === 'string') {
  369. // 如果是字符串,尝试解析为 Date 对象
  370. dateObj = new Date(date);
  371. } else {
  372. // 如果是 Date 对象,直接使用
  373. dateObj = date;
  374. }
  375. // 定义替换规则
  376. const replacements = new Map<string, string>([
  377. ['yyyy', String(dateObj.getFullYear())],
  378. ['MM', String(dateObj.getMonth() + 1).padStart(2, '0')],
  379. ['dd', String(dateObj.getDate()).padStart(2, '0')],
  380. ['HH', String(dateObj.getHours()).padStart(2, '0')],
  381. ['mm', String(dateObj.getMinutes()).padStart(2, '0')],
  382. ['ss', String(dateObj.getSeconds()).padStart(2, '0')]
  383. ]);
  384. // 替换格式字符串中的占位符
  385. replacements.forEach((value, key) => {
  386. format = format.replace(new RegExp(key, 'g'), value);
  387. });
  388. return format;
  389. }
  390. //获取从文件管理器获得的视频资源的属性值
  391. static async uriGetAssetsFromFile(context:Context,uri:string,type:number,isLoadPixelMap?:boolean): Promise<VideoItem> {
  392. let item:VideoItem = new VideoItem('',uri,uri,type,0,'')
  393. try {
  394. console.info('asset file.uri: ', uri);
  395. let file = fs.openSync(uri, fs.OpenMode.READ_ONLY | fs.OpenMode.CREATE)
  396. console.info("file.fd " + file.fd);
  397. let fdfd = 'fd://' + file.fd
  398. //3、通过fs.stat方法获取stat对象
  399. console.info('asset file.name: ', file.name);
  400. console.info('asset file.uri: ', uri);
  401. console.info('asset file.fd: ', file.fd);
  402. console.info('asset file.path: ', file.path);
  403. item = new VideoItem(file.name,uri,uri,type,0,'')
  404. await fs.stat(file.fd).then(async (stat: fs.Stat) => {
  405. console.info("get file info succeed, the size of file is " + stat.size);
  406. let videoSize = stat.size
  407. // let videoTime = stat.ctime
  408. let fileSize = Utility.formatFSize(videoSize)
  409. let cTime = Utility.getFormatDateStr(stat.mtime,'yyyy-MM-dd HH:mm')
  410. // console.info('asset stat.ino: ', stat.ino);
  411. // console.info('asset stat.mode: ', stat.mode);
  412. // console.info('asset stat.uid: ', stat.uid);
  413. // console.info('asset stat.ino: ', stat.gid);
  414. // console.info('asset stat.size: ', stat.size);
  415. console.info('asset stat.ctime: ', stat.ctime);
  416. // console.info('asset stat.mtime: ', stat.mtime);
  417. // console.info('asset stat.duration: ', duration);
  418. let pixelMap:image.PixelMap|undefined = undefined
  419. if(isLoadPixelMap){
  420. //获取缩略图
  421. if(Utility.isVideoByExtension(uri)){
  422. pixelMap = await Utility.getFetchFrameByTime(uri)
  423. }else{
  424. pixelMap = await Utility.getFetchMetadataFromFdSrcByPromise(uri)
  425. }
  426. }
  427. item = new VideoItem( file.name,uri ,uri,type,videoSize,cTime,pixelMap,fileSize,
  428. await ImageUtil.pixelMapToBase64Str(pixelMap))
  429. })
  430. } catch (error) {
  431. console.error('uriGetAssetsFromFile failed with err: ' + JSON.stringify(error));
  432. }
  433. return item
  434. }
  435. static async getFilePixelMapBig(uri:string){
  436. let pixelMap:image.PixelMap|undefined = undefined
  437. if(Utility.isMusicByExtension(uri)){
  438. pixelMap = await Utility.getFetchMetadataFromFdSrcByPromise(uri)
  439. }else{
  440. pixelMap = await Utility.getFetchFrameByTime(uri)
  441. }
  442. return ImageUtil.pixelMapToBase64StrBig(pixelMap)
  443. }
  444. static async getFilePixelMap(uri:string){
  445. let pixelMap:image.PixelMap|undefined = undefined
  446. if(Utility.isMusicByExtension(uri)){
  447. pixelMap = await Utility.getFetchMetadataFromFdSrcByPromise(uri)
  448. }else{
  449. pixelMap = await Utility.getFetchFrameByTime(uri)
  450. }
  451. return ImageUtil.pixelMapToBase64Str(pixelMap)
  452. }
  453. //根据filePathe获取对应的播放Index
  454. static getIndexFromList(localList:Array<VideoItem>,filePath:string){
  455. let list: Array<string> = [];
  456. for(let i=0;i<localList.length;i++){
  457. if(localList[i].filePath === filePath){
  458. return i
  459. }
  460. }
  461. return 0
  462. }
  463. // 在以下demo中,使用资源管理接口获取打包在HAP内的媒体资源文件,通过设置fdSrc属性,获取音频元数据并打印,
  464. // 获取音频专辑封面并通过Image控件显示在屏幕上。该demo以Promise形式进行异步接口调用
  465. static async getFetchMetadataFromFdSrcByPromise(uri: string): Promise<image.PixelMap | undefined> {
  466. let cover: image.PixelMap | undefined = undefined;
  467. if (canIUse("SystemCapability.Multimedia.Media.AVMetadataExtractor")) {
  468. try {
  469. // 创建AVMetadataExtractor对象
  470. const avMetadataExtractor: media.AVMetadataExtractor = await media.createAVMetadataExtractor();
  471. // 设置fdSrc
  472. const fd = await fs.openSync(uri, fs.OpenMode.READ_ONLY);
  473. avMetadataExtractor.fdSrc = fd;
  474. // 获取元数据(promise模式)
  475. const metadata = await avMetadataExtractor.fetchMetadata();
  476. metadata.author
  477. console.info(`get meta data, hasAudio: ${metadata.hasAudio}`);
  478. // 获取专辑封面(promise模式)
  479. cover = await avMetadataExtractor.fetchAlbumCover();
  480. // 释放资源(promise模式)
  481. await avMetadataExtractor.release();
  482. console.info('release success.');
  483. } catch (error) {
  484. console.error('Error during metadata extraction:', error);
  485. }
  486. } else {
  487. console.warn('AVMetadataExtractor capability is not supported.');
  488. }
  489. return cover;
  490. }
  491. static formatTimestamp(timestamp: number): string {
  492. // 将时间戳转换为 Date 对象
  493. const date = new Date(timestamp);
  494. // 获取各个部分
  495. const year = date.getFullYear();
  496. const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份从0开始,需要加1
  497. const day = String(date.getDate()).padStart(2, '0');
  498. const hours = String(date.getHours()).padStart(2, '0');
  499. const minutes = String(date.getMinutes()).padStart(2, '0');
  500. const seconds = String(date.getSeconds()).padStart(2, '0');
  501. // 拼接成所需的格式
  502. return `${year}-${month}-${day} ${hours}:${minutes}`;
  503. }
  504. //获取音乐资源的属性值,
  505. static async uriGetMusicAssetsFromFile(context:Context,uri:string,type:number,isLoadPixelMap?:boolean): Promise<VideoItem> {
  506. let item:VideoItem = new VideoItem('',uri,uri,type,0,'')
  507. try {
  508. console.info('asset file.uri: ', uri);
  509. let file = fs.openSync(uri, fs.OpenMode.READ_ONLY | fs.OpenMode.CREATE)
  510. console.info("file.fd " + file.fd);
  511. let fdfd = 'fd://' + file.fd
  512. //3、通过fs.stat方法获取stat对象
  513. console.info('asset file.name: ', file.name);
  514. console.info('asset file.uri: ', uri);
  515. console.info('asset file.fd: ', file.fd);
  516. console.info('asset file.path: ', file.path);
  517. item = new VideoItem(file.name,uri,uri,type,0,'')
  518. await fs.stat(file.fd).then(async (stat: fs.Stat) => {
  519. console.info("get file info succeed, the size of file is " + stat.size);
  520. let videoSize = stat.size
  521. // let videoTime = stat.ctime
  522. let fileSize = Utility.formatFSize(videoSize)
  523. //按照添加时间
  524. let addTime = Utility.getFormatDateStr(new Date(),'yyyy-MM-dd HH:mm');
  525. // console.info('onecold asset addTime: ', addTime);
  526. // let cTime = Utility.getFormatDateStr(stat.mtime,'yyyy-MM-dd HH:mm')
  527. // console.info('asset stat.ctime: ', stat.ctime);
  528. let musicName:string | undefined = file.name
  529. let artist:string | undefined = ''
  530. let album:string | undefined = ''
  531. let pixelMap:image.PixelMap|undefined|null = undefined
  532. let imagePath = ''
  533. let duration:string | undefined = ''
  534. let mimeType:string | undefined = ''
  535. let trackCount:string | undefined = ''//轨道数量
  536. let sampleRate:string | undefined = ''//音频的采样率单位为Hz
  537. if (canIUse("SystemCapability.Multimedia.Media.AVMetadataExtractor")) {
  538. try {
  539. // 创建AVMetadataExtractor对象
  540. const avMetadataExtractor: media.AVMetadataExtractor = await media.createAVMetadataExtractor();
  541. // 设置fdSrc
  542. const fd = await fs.openSync(uri, fs.OpenMode.READ_ONLY);
  543. avMetadataExtractor.fdSrc = fd;
  544. // 获取元数据(promise模式)
  545. const metadata = await avMetadataExtractor.fetchMetadata();
  546. if(StrUtil.isNotEmpty(metadata.title)){
  547. musicName = metadata.title
  548. }
  549. if(musicName==undefined)
  550. musicName = file.name
  551. if(StrUtil.isNotEmpty(metadata.artist)){
  552. artist = metadata.artist
  553. }
  554. if(artist==undefined)
  555. artist = ''
  556. if(StrUtil.isNotEmpty(metadata.album)){
  557. album = metadata.album
  558. }
  559. if(StrUtil.isNotEmpty(metadata.duration)){
  560. duration = DateUtil.getFormatDateStr(metadata.duration,'HH:mm:ss')
  561. }
  562. if(StrUtil.isNotEmpty(metadata.mimeType)){
  563. mimeType = metadata.mimeType
  564. }
  565. if(StrUtil.isNotEmpty(metadata.trackCount)){
  566. trackCount = metadata.trackCount
  567. }
  568. if(StrUtil.isNotEmpty(metadata.sampleRate)){
  569. sampleRate = metadata.sampleRate
  570. }
  571. let name = await MD5.digestSync(uri)
  572. if(isLoadPixelMap){
  573. // 获取专辑封面(promise模式)
  574. // pixelMap = await avMetadataExtractor.fetchAlbumCover();
  575. // // 释放资源(promise模式)
  576. // await avMetadataExtractor.release();
  577. pixelMap = await fetchAlbumCover(avMetadataExtractor)
  578. // console.info('onecold release success. name= '+musicName);
  579. if(pixelMap!==undefined&&pixelMap!==null){
  580. console.info('onecold pixelMap is not empty= '+musicName);
  581. imagePath = await ImageUtil.savePixelMap(pixelMap,context.filesDir,name)
  582. imagePath = fileUri.getUriFromPath(imagePath)
  583. }else{
  584. console.info('onecold pixelMap is empty= '+musicName);
  585. imagePath = ''
  586. // if(StrUtil.isNotEmpty(artist))
  587. // imagePath = await NetAxiosUtil.getLyricCover(musicName,artist)
  588. }
  589. }else{
  590. imagePath = context.filesDir + FileUtil.separator + name
  591. imagePath = fileUri.getUriFromPath(imagePath)
  592. }
  593. console.info('onecold release success. imagePath= '+imagePath);
  594. } catch (error) {
  595. console.error('Error during metadata extraction:', error);
  596. }
  597. } else {
  598. console.warn('AVMetadataExtractor capability is not supported.');
  599. }
  600. if(musicName==undefined)
  601. musicName = file.name
  602. item = new VideoItem(musicName,uri ,uri,type,videoSize,addTime,undefined,fileSize,
  603. imagePath,artist,album,file.name)
  604. item.duration = duration+'';
  605. item.mimeType = mimeType;
  606. item.trackCount = trackCount;
  607. item.sampleRate = sampleRate;
  608. item.isFav = 0;
  609. item.playCount = 0;
  610. })
  611. } catch (error) {
  612. console.error('uriGetAssetsFromFile failed with err: ' + JSON.stringify(error));
  613. }
  614. return item
  615. }
  616. //根据字节获取大小
  617. static formatFSize(bytes:number):string {
  618. const units = ['Bytes', 'K', 'M', 'G'];
  619. let size = bytes;
  620. let unitIndex = 0;
  621. while (size >= 1024 && unitIndex < units.length - 1) {
  622. size /= 1024;
  623. unitIndex++;
  624. }
  625. // 保留两位小数, 四舍五入
  626. size = Math.round(size * 10) / 10;
  627. return size + units[unitIndex]
  628. }
  629. static gotoMarket(context:common.UIAbilityContext,bundleName:string) {
  630. const want: Want = {
  631. uri: `store://appgallery.huawei.com/app/detail?id=${bundleName}`
  632. };
  633. context.startAbility(want).then(()=>{
  634. //拉起成功
  635. }).catch(()=>{
  636. // 拉起失败
  637. });
  638. }
  639. static async doShare(item:VideoItem,context:common.UIAbilityContext){
  640. // 生成视频封面图
  641. const imagePackerApi: image.ImagePacker = image.createImagePacker();
  642. const buffer: ArrayBuffer = await (imagePackerApi.packing(await Utility.getFetchFrameByTime(item.filePath), {
  643. format: 'image/jpeg',
  644. quality: 30
  645. }) as Promise<ArrayBuffer>);
  646. // 构造ShareData,需配置一条有效数据信息
  647. let shareData: systemShare.SharedData = new systemShare.SharedData({
  648. utd: uniformTypeDescriptor.UniformDataType.MEDIA,
  649. uri: fileUri.getUriFromPath(item.filePath),
  650. title: item.name, // 不传title字段时,显示视频文件名
  651. // description: '好听的音乐', // 不传description字段时,显示视频大小
  652. thumbnail: new Uint8Array(buffer), // 优先使用传递的缩略图做预览 不传则默认使用视频第一帧画面做预览图
  653. });
  654. // 进行分享面板显示
  655. let controller: systemShare.ShareController = new systemShare.ShareController(shareData);
  656. // let context = getContext(this) as common.UIAbilityContext;
  657. controller.show(context, {
  658. selectionMode: systemShare.SelectionMode.SINGLE,
  659. previewMode: systemShare.SharePreviewMode.DETAIL,
  660. }).then(() => {
  661. console.info('ShareController show success.');
  662. }).catch((error: BusinessError) => {
  663. console.error(`ShareController show error. code: ${error.code}, message: ${error.message}`);
  664. });
  665. }
  666. //分享索引index的视频
  667. static async doShareMusic(item:VideoItem,context:common.UIAbilityContext){
  668. // 构造ShareData,需配置一条有效数据信息
  669. let shareData: systemShare.SharedData = new systemShare.SharedData({
  670. utd: uniformTypeDescriptor.UniformDataType.AUDIO,
  671. uri: fileUri.getUriFromPath(item.filePath),
  672. title: item.name, // 不传title字段时,显示视频文件名
  673. description: '好听的音乐', // 不传description字段时,显示视频大小
  674. });
  675. // 进行分享面板显示
  676. let controller: systemShare.ShareController = new systemShare.ShareController(shareData);
  677. // let context = getContext(this) as common.UIAbilityContext;
  678. controller.show(context, {
  679. selectionMode: systemShare.SelectionMode.SINGLE,
  680. previewMode: systemShare.SharePreviewMode.DETAIL,
  681. }).then(() => {
  682. console.info('ShareController show success.');
  683. }).catch((error: BusinessError) => {
  684. console.error(`ShareController show error. code: ${error.code}, message: ${error.message}`);
  685. });
  686. }
  687. static getNameList(localList:Array<VideoItem>){
  688. let list: Array<string> = [];
  689. for(let i=0;i<localList.length;i++){
  690. if(StrUtil.isNotEmpty(localList[i].name))
  691. list.push(localList[i].name);
  692. }
  693. return list
  694. }
  695. //根据type获取对应的播放列表(CommonConstants.TYPE_LOCAL:本地,CommonConstants.TYPE_Dir:私密视频)
  696. static getGlobalNameList(localList:Array<VideoItem>,type:number){
  697. let list: Array<string> = [];
  698. for(let i=0;i<localList.length;i++){
  699. if(localList[i].type === type){
  700. list.push(localList[i].name);
  701. }
  702. }
  703. return list
  704. }
  705. //根据type获取对应的播放列表(CommonConstants.TYPE_LOCAL:本地,CommonConstants.TYPE_LOCK:私密视频)
  706. static getGlobalList(localList:Array<VideoItem>,type:number){
  707. let list: Array<VideoItem> = [];
  708. for(let i=0;i<localList.length;i++){
  709. if(localList[i].type === type){
  710. list.push(localList[i]);
  711. }
  712. }
  713. return list
  714. }
  715. //根据filePath获取当前索引index
  716. static getCurIndexFromGlobalList(localList:Array<VideoItem>,filePath:string){
  717. let index: number = 0;
  718. for(let i=0;i<localList.length;i++){
  719. if(localList[i].filePath === filePath){
  720. index = i
  721. return index
  722. }
  723. }
  724. return index
  725. }
  726. //新的查询是否收藏的方法
  727. static getIsFav(favList: Array<VideoItem>,item:VideoItem|undefined){
  728. if(ArrayUtil.isEmpty(favList)){
  729. return false
  730. }
  731. if(item===undefined){
  732. return false
  733. }
  734. for(let i=0;i<favList.length;i++){
  735. if(favList[i].filePath === item.filePath&&favList[i].isFav===1){
  736. return true
  737. }
  738. }
  739. return false
  740. }
  741. static getIsFac(facList: Array<String>,item:VideoItem|undefined){
  742. if(ArrayUtil.isEmpty(facList)){
  743. return false
  744. }
  745. if(item===undefined){
  746. return false
  747. }
  748. for(let i=0;i<facList.length;i++){
  749. if(facList[i] === item.name||facList[i]===item.fileName){
  750. return true
  751. }
  752. }
  753. return false
  754. }
  755. static resourceToString(context:Context,resource:Resource){
  756. if(!context||!resource)
  757. return ''
  758. return context.resourceManager.getStringSync(resource).toString()
  759. }
  760. //根据type获取对应的播放列表(CommonConstants.TYPE_LOCAL:本地,CommonConstants.TYPE_LOCK:私密视频)
  761. static getGlobalMusicList(localList:Array<VideoItem>,type:number){
  762. let list: Array<VideoItem> = [];
  763. for(let i=0;i<localList.length;i++){
  764. if(localList[i].type === type){
  765. list.push(localList[i]);
  766. }
  767. }
  768. return list
  769. }
  770. static isHaoFreeTime():boolean{
  771. if(DateUtil.isWeekend()){
  772. return true
  773. }
  774. let str = DateUtil.getFormatDateStr(new Date(), 'HH')
  775. let a = Number(str);
  776. if(isNaN(a))
  777. return false
  778. // ToastUtil.showToast('a='+a)
  779. if (a >= 8 && a <13) {//白天
  780. return false;
  781. }
  782. if (a >= 13 && a <20) {//白天
  783. return false;
  784. }
  785. if (a >= 0 && a < 8) {//凌晨
  786. return true;
  787. }
  788. if (a >= 20 && a <= 24) {//晚上
  789. return true;
  790. }
  791. return false
  792. }
  793. static getMusisBg():Resource {
  794. let index = RandomUtil.randomNumber(0,9)
  795. LogUtil.info('getMusisBg index = '+index)
  796. return CommonConstants.musicBgList[index]
  797. }
  798. static getMusisBg2(index:number):Resource {
  799. const adjustedIndex = index % 10;
  800. LogUtil.info('getMusisBg adjustedIndex = '+adjustedIndex)
  801. return CommonConstants.musicBgList[adjustedIndex]
  802. }
  803. static getParentDirectory(filePath: string): string {
  804. // 使用正则表达式匹配路径分隔符(支持 Unix 和 Windows)
  805. const lastSlashIndex = Math.max(filePath.lastIndexOf('/'), filePath.lastIndexOf('\\'));
  806. if (lastSlashIndex === -1) {
  807. return '';
  808. }
  809. return filePath.substring(0, lastSlashIndex);
  810. }
  811. static getFileDirName(filePath: string,rootPath:string): string{
  812. if(filePath===rootPath){
  813. return '首页'
  814. }
  815. let result = FileUtil.getFileName(filePath)
  816. if(StrUtil.isEmpty(result))
  817. return ''
  818. if(result.startsWith('.'))
  819. result = result.replace(/\./g, '')
  820. return result
  821. }
  822. // 开启沉浸式显示模式
  823. static async enableFullScreen() {
  824. const ctx = getContext()
  825. const win = await window.getLastWindow(ctx)
  826. win.setWindowLayoutFullScreen(true)
  827. const top = win.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM)
  828. AppStorage.setOrCreate('topHeight', px2vp(top.topRect.height))
  829. AppStorage.setOrCreate('bottomHeight', px2vp(top.bottomRect.height))
  830. }
  831. // 关闭沉浸式显示模式
  832. static async disableFullScreen() {
  833. const ctx = getContext()
  834. const win = await window.getLastWindow(ctx)
  835. win.setWindowLayoutFullScreen(false)
  836. AppStorage.setOrCreate('topHeight', 0)
  837. AppStorage.setOrCreate('bottomHeight', 0)
  838. }
  839. // 设置状态栏文字颜色为白色
  840. static async setStatusBarLight() {
  841. const ctx = getContext()
  842. const win = await window.getLastWindow(ctx)
  843. win.setWindowSystemBarProperties({
  844. statusBarContentColor: '#ffffff'
  845. })
  846. }
  847. // 设置状态栏文字颜色为黑色
  848. static async setStatusBarDark() {
  849. const ctx = getContext()
  850. const win = await window.getLastWindow(ctx)
  851. win.setWindowSystemBarProperties({
  852. statusBarContentColor: '#000000'
  853. })
  854. }
  855. // 优化点:完全基于Promise链的异步处理
  856. static async getAppName(context: Context): Promise<string> {
  857. try {
  858. // 1. 同步化BundleInfo获取
  859. const bundleInfo = await bundleManager.getBundleInfoForSelf(
  860. bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION
  861. );
  862. // 2. 安全获取labelId(新增空值校验)
  863. const labelId = bundleInfo.appInfo?.labelId;
  864. if (!labelId || labelId <= 0) {
  865. throw new Error("Invalid labelId: " + labelId);
  866. }
  867. // 3. 异步资源解析(替换危险的getStringSync)
  868. return await context.resourceManager.getStringValue(labelId);
  869. } catch (err) {
  870. console.error(`[${new Date().toISOString()}] AppName Error: CODE=${err.code}, MSG=${err.message}`);
  871. // 4. 多级降级策略
  872. return AppUtil.getBundleName() // 最终兜底
  873. }
  874. }
  875. //按名称升序
  876. static doSortListAscending( list:Array<VideoItem>){
  877. let options: Intl.CollatorOptions = {
  878. localeMatcher: "lookup",
  879. usage: "sort",
  880. sensitivity: "case" // 区分大小写
  881. };
  882. const collator = new Intl.Collator("zh-CN", options);
  883. list.sort((a, b) => {
  884. const typeOrder = getTypeOrder(a.type) - getTypeOrder(b.type);
  885. if (typeOrder !== 0) {
  886. return typeOrder;
  887. }
  888. const partsA = extractParts(a.name);
  889. const partsB = extractParts(b.name);
  890. const nonNumericComparison = collator.compare(partsA.nonNumeric, partsB.nonNumeric);
  891. if (nonNumericComparison !== 0) {
  892. return nonNumericComparison;
  893. }
  894. return partsA.numeric - partsB.numeric;
  895. });
  896. }
  897. //按名称降序
  898. static doSortListDescending(list:Array<VideoItem>){
  899. const options: Intl.CollatorOptions = {
  900. localeMatcher: "lookup",
  901. usage: "sort",
  902. sensitivity: "case" // 区分大小写
  903. };
  904. const collator = new Intl.Collator("zh-CN", options);
  905. list.sort((a, b) => {
  906. const typeOrder = getTypeOrder(a.type) - getTypeOrder(b.type);
  907. if (typeOrder !== 0) {
  908. return typeOrder;
  909. }
  910. const partsA = extractParts(a.name);
  911. const partsB = extractParts(b.name);
  912. const nonNumericComparison = collator.compare(partsB.nonNumeric, partsA.nonNumeric);
  913. if (nonNumericComparison !== 0) {
  914. return nonNumericComparison;
  915. }
  916. return partsB.numeric - partsA.numeric;
  917. });
  918. }
  919. }
  920. interface PathStat {
  921. path: string;
  922. isDirectory: boolean;
  923. }
  924. async function isDirectory(filePath: string): Promise<boolean> {
  925. try {
  926. const stat = await fs.stat(filePath);
  927. return stat.isDirectory();
  928. } catch (error) {
  929. console.error('Error getting file stat:', error);
  930. return false;
  931. }
  932. }
  933. //排序模式参数
  934. interface VideoNameParts {
  935. nonNumeric: string;
  936. numeric: number;
  937. }
  938. function extractParts(name: string): VideoNameParts {
  939. const match = name.match(/^(\D*)(\d*)/);
  940. return {
  941. nonNumeric: match?.[1] || '',
  942. numeric: parseInt(match?.[2] || '0', 10)
  943. };
  944. }
  945. function getInstallTime(): number | null {
  946. // 从本地存储中获取安装时间
  947. return PreferencesUtil.getNumberSync('installTime')
  948. }
  949. function setInstallTime(time: number) {
  950. // 将安装时间存储到本地存储中
  951. PreferencesUtil.putSync('installTime',time)
  952. }
  953. function fetchAlbumCover(avMetadataExtractor: media.AVMetadataExtractor): Promise<image.PixelMap | null> {
  954. return new Promise((resolve, reject) => {
  955. avMetadataExtractor.fetchAlbumCover((error: BusinessError, pixelMap: image.PixelMap) => {
  956. if (error) {
  957. console.error(`Failed to fetch AlbumCover, error = ${JSON.stringify(error)}`);
  958. resolve(null);
  959. } else {
  960. resolve(pixelMap);
  961. }
  962. avMetadataExtractor.release();
  963. });
  964. });
  965. }
  966. //排序类型
  967. function getTypeOrder(type: number) {
  968. switch (type) {
  969. case CommonConstants.TYPE_IS_DIR:
  970. return 1; // First
  971. case CommonConstants.TYPE_IS_CSJAD:
  972. return 2; // Middle
  973. case CommonConstants.TYPE_LOCAL:
  974. return 3; // Last
  975. default:
  976. return 4; // Unknown types, if any, go last
  977. }
  978. }