Utility.ets 39 KB

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