Utility.ets 38 KB

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