ScanFilePage.ets 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. import TitleBar from '../view/TitleBar'
  2. import { router } from '@kit.ArkUI'
  3. import { FileUtil, LogUtil, PreferencesUtil, ToastUtil } from '@pura/harmony-utils'
  4. import lottie from '@ohos/lottie'
  5. import { AnimationItem } from '@ohos/lottie'
  6. import { MessageEvents, taskpool, worker } from '@kit.ArkTS'
  7. import { fileUri, picker } from '@kit.CoreFileKit'
  8. import { LocalMusic } from '../view/LocalMusic'
  9. import { Utility } from '../common/util/Utility'
  10. import MediaTable from '../common/util/MediaTable'
  11. import { VideoItem } from '../viewmodel/VideoItem'
  12. import Logger from '../common/util/Logger'
  13. import { CommonConstants } from '../common/constants/CommonConstants'
  14. import { emitter } from '@kit.BasicServicesKit'
  15. @Preview
  16. @Entry
  17. @Component
  18. export struct ScanFilePage{
  19. @State rootPath:string = '' //音频根目录
  20. @State lockPath:string = ''
  21. @State strText:string = ''
  22. @State isStart:boolean = false
  23. @State appName:string = ''
  24. //lottie动画构建渲染上下文
  25. private mainRenderingSettings: RenderingContextSettings = new RenderingContextSettings(true)
  26. private path:string = "common/lottie/leida.json"
  27. private successpath:string = "common/lottie/success2.json"
  28. private mainCanvasRenderingContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.mainRenderingSettings)
  29. private animateItem: AnimationItem | null = null;
  30. initLottie(lpath:string,isLoop:boolean){
  31. lottie.destroy(); //加载动画前先销毁之前加载的动画
  32. this.animateItem = lottie.loadAnimation({
  33. container: this.mainCanvasRenderingContext, // 渲染上下文
  34. renderer: 'canvas', // 渲染方式
  35. loop: isLoop, // 是否循环播放,默认true
  36. autoplay: true, // 是否自动播放,默认true
  37. name: '2024', // 动画名称
  38. contentMode: 'Contain', // 填充的模式
  39. frameRate: 30, //设置animator的刷帧率为30
  40. imagePath: 'lottie/images/', // 加载读取指定路径下的图片资源
  41. path: lpath, // json路径
  42. // initialSegment: [0,70]
  43. })
  44. lottie.setSpeed(0.7)
  45. }
  46. // 组件生命周期
  47. async aboutToAppear() {
  48. Utility.getAppName(getContext(this)).then((appName:string)=>{
  49. this.appName = appName
  50. })
  51. const documentViewPicker = new picker.DocumentViewPicker()
  52. let documentSaveResult = await documentViewPicker.save({ pickerMode: picker.DocumentPickerMode.DOWNLOAD })
  53. this.rootPath = new fileUri.FileUri(documentSaveResult[0]).path
  54. this.lockPath = this.rootPath +'/'+ LocalMusic.STR_LOCK_VIDEO
  55. LogUtil.info('onecold 文件扫描 aboutToAppear')
  56. }
  57. endScan(){
  58. const eventData: emitter.EventData = {};
  59. emitter.emit({ eventId: 101 }, eventData); // 发送视频打开广播事件
  60. ToastUtil.showToast('扫描文件入库成功!')
  61. this.isStart = false
  62. this.strText = ''
  63. //结束动画
  64. lottie.pause()
  65. lottie.stop()
  66. this.initLottie(this.successpath,false)
  67. lottie.play()
  68. }
  69. // 组件生命周期
  70. aboutToDisappear() {
  71. lottie.destroy();
  72. }
  73. @State titleBarModel: TitleBar.Model = new TitleBar.Model()
  74. .setTitleTextStyle(FontStyle.Normal)
  75. .setTitleBarStyle(TitleBar.BarStyle.TRANSPARENT)
  76. .setLeftIcon($r('app.media.left_back_white'))
  77. .setTitleName("文件扫描")
  78. .setTitleFontColor(Color.White)
  79. .setTitleBarBackground($r('app.color.title_bar_bg'))
  80. .setLeftTitleBackground($r('app.color.title_bar_bg'))
  81. .setTitleBarBottomLineColor($r('app.color.title_bar_bg'))
  82. .setOnLeftClickListener(() => {
  83. router.back()
  84. })
  85. initState(){
  86. this.initLottie(this.path,true)
  87. this.isStart = true
  88. this.strText = Utility.resourceToString(getContext(this),$r('app.string.scaning'))
  89. lottie.play()
  90. }
  91. doOptimize(){
  92. this.initState()
  93. const task = new taskpool.Task(scanDirectoryTask, getContext(this), this.rootPath,
  94. this.lockPath,PreferencesUtil.getStringSync('COVER_API',''));
  95. taskpool.execute(task, taskpool.Priority.HIGH).then(()=>{
  96. this.endScan()
  97. }).catch((e:object)=>{
  98. console.info("task1 catch e: " + e);
  99. })
  100. }
  101. build() {
  102. Column() {
  103. TitleBar({ model: $titleBarModel })
  104. Scroll(){
  105. Column() {
  106. Stack(){
  107. Image($r('app.media.file_search'))
  108. .width(80)
  109. .height(80)
  110. .alignSelf(ItemAlign.Center)
  111. //lottie动画
  112. Canvas(this.mainCanvasRenderingContext)
  113. .width(160)
  114. .height(160)
  115. .onReady(()=>{
  116. //抗锯齿的设置
  117. this.mainCanvasRenderingContext.imageSmoothingEnabled = true;
  118. this.mainCanvasRenderingContext.imageSmoothingQuality = 'medium'
  119. })
  120. }
  121. Text(this.strText)
  122. .margin({ right: 20,top:20,bottom:20 })
  123. .fontSize(16)
  124. .fontColor(Color.Black)
  125. .fontWeight(480)
  126. Column() {
  127. Row() {
  128. Image($r('app.media.selected'))
  129. .width(16)
  130. .height(16)
  131. .margin({ left:10})
  132. .alignSelf(ItemAlign.Center)
  133. Text(Utility.resourceToString(getContext(this),$r('app.string.file_scan_tip_one'))
  134. +`${this.appName} `+
  135. Utility.resourceToString(getContext(this),$r('app.string.file_scan_tip_one_1')))
  136. .margin({ left: 10, right: 20 })
  137. .fontSize(11)
  138. .fontColor(Color.Gray)
  139. .fontWeight(480)
  140. .layoutWeight(1)
  141. }
  142. .width('100%')
  143. .margin({ left:25,right: 10 ,top:20 })
  144. Row() {
  145. Image($r('app.media.selected'))
  146. .width(16)
  147. .height(16)
  148. .margin({ left:10})
  149. .alignSelf(ItemAlign.Center)
  150. Text($r('app.string.file_scan_tip_two'))
  151. .margin({ left: 10, right: 20 })
  152. .fontSize(11)
  153. .fontColor(Color.Gray)
  154. .fontWeight(480)
  155. .layoutWeight(1)
  156. }
  157. .width('100%')
  158. .margin({ left:25,right: 10 ,top:20 })
  159. Column() {
  160. Button($r('app.string.start_scan'), { type: ButtonType.Capsule, stateEffect: false })
  161. .width('80%')
  162. .height(55)
  163. .margin({ top: 50, bottom: 20 })
  164. .linearGradient({
  165. direction: GradientDirection.Right,
  166. colors: [['#ff37a0fc', 0.0], ['#67e667', 0.5], ['#f5856e', 1.0]]
  167. })
  168. .visibility(this.isStart ? Visibility.Hidden : Visibility.Visible)
  169. .onClick(() => {
  170. this.doOptimize()
  171. })
  172. .alignSelf(ItemAlign.Center)
  173. }
  174. }
  175. }
  176. .height('100%')
  177. .width('100%')
  178. .align(Alignment.Top)
  179. }
  180. .height(1000)
  181. }
  182. }
  183. }
  184. // async function scanDirectoryTask(context: Context, curPath: string, lockPath: string) {
  185. // const table: MediaTable = new MediaTable(context);
  186. //
  187. //
  188. // try {
  189. // // 使用 Promise 包装数据库操作
  190. // await new Promise<void>((resolve, reject) => {
  191. // table.getRdbStore(context, async (err:Error) => {
  192. // if (err) {
  193. // reject(err);
  194. // return;
  195. // }
  196. //
  197. // const files = FileUtil.listFileSync(curPath);
  198. // const pendingTasks: Promise<void>[] = [];
  199. //
  200. // for (const file of files) {
  201. // const fPath = `${curPath}/${file}`;
  202. // if (fPath === lockPath) continue;
  203. //
  204. // if (FileUtil.isDirectory(fPath)) {
  205. // // 用立即执行函数处理异步递归
  206. // pendingTasks.push((async () => {
  207. // await scanDirectoryTask(context, fPath, lockPath);
  208. // })());
  209. // } else {
  210. // if(!fPath.endsWith('.lrc')&&Utility.isMeidaByExtension(fPath)&&!fPath.endsWith('.srt')){
  211. // // 用 Promise 包装媒体处理逻辑
  212. // pendingTasks.push((async () => {
  213. // let mediaItem:VideoItem = await Utility.uriGetMusicAssetsFromFile(context, fPath, CommonConstants.TYPE_LOCAL, true);
  214. // ;
  215. // await new Promise<void>((resolve: (value: void) => void) => {
  216. // table.insert(mediaItem, (id: number) => {
  217. // resolve(); // 明确调用 resolve 且无返回值
  218. // });
  219. // });
  220. // })());
  221. // }
  222. // }
  223. // }
  224. //
  225. // // 关键点:等待所有异步操作完成
  226. // await Promise.all(pendingTasks);
  227. // resolve();
  228. // });
  229. // });
  230. //
  231. // // Logger.info('onecold scanDirectory 全部扫描完成后执行 = ')
  232. //
  233. // } catch (err) {
  234. // Logger.error(`Scan failed: ${err.code} - ${err.message}`);
  235. // }
  236. //
  237. //
  238. // }
  239. @Concurrent
  240. async function scanDirectoryTask(context: Context, dirPath: string, lockPath: string,cover_api:string) {
  241. const stack: string[] = [dirPath];
  242. const table: MediaTable = new MediaTable(context);
  243. while (stack.length > 0) {
  244. const currentPath = stack.pop()!;
  245. const files = FileUtil.listFileSync(currentPath);
  246. await Promise.all(files.map(async (file) => {
  247. const fPath = `${currentPath}/${file}`;
  248. if (fPath === lockPath) return;
  249. if (FileUtil.isDirectory(fPath)) {
  250. stack.push(fPath); // 使用栈结构代替递归
  251. } else {
  252. if (fPath.endsWith('.lrc') || fPath.endsWith('.srt')) return;
  253. if (Utility.isMeidaByExtension(fPath)) {
  254. const mediaItem = await Utility.uriGetMusicAssetsFromFile(
  255. context, fPath, CommonConstants.TYPE_LOCAL, true
  256. );
  257. table.insert(mediaItem, (id: number) => {
  258. },cover_api);
  259. }
  260. }
  261. }));
  262. }
  263. }