UnifiedPlayerService.ets 126 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781
  1. import { VideoItem } from '../../viewmodel/VideoItem';
  2. import { PlayerManager, IPlayerManager, PlayerStateCallback } from './PlayerManager';
  3. import {
  4. PlayerStateModel,
  5. PlayerState,
  6. PlayerStateListener,
  7. PlayMode,
  8. PlayerError,
  9. PlayerErrorType
  10. } from './PlayerStateModel';
  11. import { PlaylistModel } from './PlaylistModel';
  12. import { IjkMediaPlayer, LogUtils, InterruptEvent, InterruptHintType } from '@ohos/ijkplayer';
  13. import { common } from '@kit.AbilityKit';
  14. import { PreferencesUtil as PuraPreferencesUtil, StrUtil } from '@pura/harmony-utils';
  15. import { PreferencesUtil } from '../utils/PreferencesUtil';
  16. import {
  17. DataPersistenceService,
  18. PlaylistSyncService,
  19. PlaylistSyncListener,
  20. IDataPersistenceService,
  21. PlaylistData,
  22. PlayerStateData
  23. } from './DataPersistenceService';
  24. import {
  25. ErrorRecoveryStrategy,
  26. IErrorRecoveryStrategy,
  27. ErrorContext,
  28. ErrorRecoveryAction
  29. } from './ErrorRecoveryStrategy';
  30. import { AvSessionController } from '../../controller/AvSessionController';
  31. import { avSession } from '@kit.AVSessionKit';
  32. import { formProvider, formBindingData } from '@kit.FormKit';
  33. import { fileIo } from '@kit.CoreFileKit';
  34. import { BusinessError } from '@kit.BasicServicesKit';
  35. import { http } from '@kit.NetworkKit';
  36. import { util } from '@kit.ArkTS';
  37. import json from '@ohos.util.json';
  38. import MediaTable from '../util/MediaTable';
  39. import { Utility } from '../util/Utility';
  40. import { FormRdbHelper } from '../../database/FormRdbHelper';
  41. import { FormInfo } from '../../viewmodel/FormInfo';
  42. /**
  43. * 服务就绪状态详情接口
  44. */
  45. export interface ServiceReadinessDetails {
  46. initialized: boolean;
  47. hasPlayerInstance: boolean;
  48. hasContext: boolean;
  49. dataRestored: boolean;
  50. playlistSize: number;
  51. currentIndex: number;
  52. }
  53. /**
  54. * 服务就绪信息接口
  55. */
  56. export interface ServiceReadinessInfo {
  57. isReady: boolean;
  58. details: ServiceReadinessDetails;
  59. }
  60. /**
  61. * 初始化验证结果接口
  62. */
  63. export interface InitializationValidation {
  64. isValid: boolean;
  65. errors: string[];
  66. warnings: string[];
  67. }
  68. /**
  69. * 卡片播放状态数据接口
  70. */
  71. export interface WidgetPlayerState {
  72. isPlaying: boolean;
  73. isPaused: boolean;
  74. isLoading: boolean;
  75. currentPosition: number;
  76. duration: number;
  77. hasNext: boolean;
  78. hasPrevious: boolean;
  79. playMode: number;
  80. }
  81. /**
  82. * 卡片播放列表信息接口
  83. */
  84. export interface WidgetPlaylistInfo {
  85. currentIndex: number;
  86. totalCount: number;
  87. }
  88. /**
  89. * 卡片时间信息接口
  90. */
  91. export interface WidgetTimeInfo {
  92. currentTimeText: string;
  93. totalTimeText: string;
  94. progressPercentage: number;
  95. }
  96. /**
  97. * 缓存文件信息接口
  98. */
  99. export interface CacheFileInfo {
  100. path: string;
  101. size: number;
  102. mtime: number;
  103. }
  104. /**
  105. * 卡片数据接口
  106. */
  107. export interface WidgetFormData {
  108. // VideoItem数据
  109. id: string;
  110. name: string;
  111. artist: string;
  112. album: string;
  113. pixelMapPath: string;
  114. duration: number;
  115. filePath: string;
  116. // 扩展数据
  117. imgName: string;
  118. imageColorHex: string;
  119. // 状态数据
  120. playerState: WidgetPlayerState;
  121. playlistInfo: WidgetPlaylistInfo;
  122. timeInfo: WidgetTimeInfo;
  123. // 卡片图片显示必填字段
  124. formImages: Record<string, number>;
  125. }
  126. /**
  127. * 统一播放器服务接口
  128. * 提供统一的播放控制接口,替代LocalMusic中的播放器逻辑
  129. */
  130. export interface IPlayerService {
  131. /**
  132. * 启动播放或恢复播放
  133. * @returns
  134. */
  135. startPlayOrResumePlay(): Promise<void>;
  136. /**
  137. * 暂停播放
  138. * @returns
  139. */
  140. pause(): Promise<void>;
  141. /**
  142. * 停止播放
  143. * @returns
  144. */
  145. stop(): Promise<void>;
  146. /**
  147. * 跳转到指定位置
  148. * @param position
  149. * @returns
  150. */
  151. seekTo(position: string): Promise<void>;
  152. // 播放列表控制 - 基于LocalMusic现有方法
  153. /**
  154. * 播放下一首
  155. * @returns
  156. */
  157. playNext(): Promise<void>;
  158. /**
  159. * 播放上一首
  160. */
  161. playPrevious(): Promise<void>;
  162. /**
  163. * 播放指定索引的歌
  164. * @param index
  165. * @returns
  166. */
  167. playSongAtIndex(index: number): Promise<void>;
  168. /**
  169. * 获取播放器状态
  170. * @returns
  171. */
  172. getCurrentState(): PlayerState;
  173. /**
  174. * 获取实际播放状态
  175. * @returns 实际播放状态
  176. */
  177. getActualPlayingState(): boolean;
  178. /**
  179. * 获取当前播放歌曲
  180. * @returns
  181. */
  182. getCurrentSong(): VideoItem | null;
  183. /**
  184. * 获取播放列表
  185. * @returns
  186. */
  187. getPlaylist(): VideoItem[];
  188. /**
  189. * 获取当前播放索引
  190. * @returns
  191. */
  192. getCurrentIndex(): number;
  193. /**
  194. * 获取当前播放进度
  195. * @returns 当前播放进度
  196. */
  197. getCurrentPosition(): number;
  198. /**
  199. * 获取播放器实例
  200. * @returns 播放器实例
  201. */
  202. getIjkPlayer(): IjkMediaPlayer | null;
  203. /**
  204. * 是否数据恢复完成
  205. * @returns 是否数据恢复完成
  206. * */
  207. isDataRestorationCompleted(): boolean;
  208. /**
  209. * 等待数据恢复完成
  210. * @param maxWaitMs 最大等待时间(毫秒),默认为-1(无限等待)
  211. * @returns 是否恢复完成
  212. */
  213. waitForDataRestoration(maxWaitMs?: number): Promise<boolean>;
  214. /**
  215. * 强制数据恢复
  216. * @returns 是否恢复成功
  217. */
  218. forceDataRestoration(): Promise<boolean>;
  219. /**
  220. * 是否就绪
  221. * @returns
  222. */
  223. isAllServicesReady(): boolean;
  224. /**
  225. * 处理音频中断事件
  226. * @param event 音频中断事件
  227. */
  228. handleAudioInterrupt(event: InterruptEvent): Promise<void>;
  229. /**
  230. * 等待服务就绪
  231. * @param maxWaitMs 最大等待时间(毫秒),默认为-1(无限等待)
  232. * @param checkDataRestore 是否检查数据恢复状态,默认为true
  233. * @returns 是否就绪
  234. */
  235. waitForAllServicesReady(maxWaitMs?: number, checkDataRestore?: boolean): Promise<boolean>;
  236. /**
  237. * 获取服务就绪信息
  238. * @returns 服务就绪信息
  239. */
  240. getServiceReadinessInfo(): ServiceReadinessInfo;
  241. /**
  242. * 初始化验证
  243. * @returns 初始化验证结果
  244. */
  245. validateInitialization(): InitializationValidation;
  246. /**
  247. * 设置播放模式
  248. * @param mode
  249. */
  250. setPlayMode(mode: number): void;
  251. /**
  252. * 获取播放模式
  253. * @returns 播放模式
  254. */
  255. getPlayMode(): number;
  256. /**
  257. * 设置音量
  258. * @param volume
  259. */
  260. setVolume(volume: number): void;
  261. /**
  262. * 设置播放速度
  263. * @param speed
  264. */
  265. setPlaybackSpeed(speed: number): void;
  266. // 播放列表管理
  267. /**
  268. * 设置播放列表
  269. * @param songs
  270. * @param currentIndex
  271. */
  272. setPlaylist(songs: VideoItem[], currentIndex?: number): void;
  273. /**
  274. * 添加歌曲到播放列表
  275. * @param song
  276. * @param index
  277. */
  278. addToPlaylist(song: VideoItem, index?: number): void;
  279. /**
  280. * 移除播放列表中的歌曲
  281. * @param index
  282. * @returns 移除的歌曲
  283. */
  284. removeFromPlaylist(index: number): VideoItem | null;
  285. // 事件监听
  286. /**
  287. * 添加播放器状态监听
  288. * @param listener
  289. */
  290. addStateListener(listener: PlayerStateListener): void;
  291. /**
  292. * 移除播放器状态监听
  293. * @param listener
  294. */
  295. removeStateListener(listener: PlayerStateListener): void;
  296. /**
  297. * 初始化
  298. * @param context
  299. * @returns
  300. */
  301. initialize(context: common.UIAbilityContext): Promise<void>;
  302. /**
  303. * 释放资源
  304. */
  305. release(): void;
  306. /**
  307. * 更新收藏列表
  308. */
  309. updateFavoriteList(): Promise<void>;
  310. /**
  311. * 强制刷新收藏状态(用于收藏/取消收藏操作后)
  312. */
  313. refreshFavoriteStatus(): Promise<void>;
  314. /**
  315. * 获取收藏列表
  316. * @returns 收藏列表
  317. */
  318. getFav(): Array<VideoItem>;
  319. syncPlaylistToService(songList: VideoItem[], currentIndex: number, playType: PlayMode): void
  320. }
  321. /**
  322. * 统一播放器服务实现
  323. * 整合PlayerManager、PlayerStateModel和PlaylistModel
  324. */
  325. interface widgeData {
  326. // VideoItem数据平铺
  327. id: string;
  328. name: string;
  329. artist: string;
  330. album: string;
  331. pixelMapPath: string;
  332. duration: number;
  333. filePath: string;
  334. imgName: string;
  335. imageColorHex: string;
  336. isFavorite: boolean;
  337. // PlayerState数据平铺
  338. isPlaying: boolean;
  339. isPaused: boolean;
  340. isLoading: boolean;
  341. currentPosition: number;
  342. hasNext: boolean;
  343. hasPrevious: boolean;
  344. playMode: number;
  345. // 播放列表信息平铺
  346. currentIndex: number;
  347. totalCount: number;
  348. // 时间信息平铺
  349. currentTimeText: string;
  350. totalTimeText: string;
  351. progressPercentage: number;
  352. // 保持嵌套结构兼容性
  353. playerState: WidgetPlayerState;
  354. playlistInfo: WidgetPlaylistInfo;
  355. timeInfo: WidgetTimeInfo;
  356. // 卡片图片显示必填字段
  357. formImages: Record<string, number>;
  358. }
  359. export class UnifiedPlayerService implements IPlayerService, PlaylistSyncListener, PlayerStateCallback {
  360. private static instance: UnifiedPlayerService | null = null;
  361. private playerManager: IPlayerManager;
  362. private stateModel: PlayerStateModel;
  363. private playlistModel: PlaylistModel;
  364. private dataPersistence: IDataPersistenceService;
  365. private playlistSync: PlaylistSyncService;
  366. private errorRecovery: IErrorRecoveryStrategy;
  367. private avSessionController: AvSessionController | null = null;
  368. private context: common.UIAbilityContext | null = null;
  369. // 防重复调用机制
  370. private isUpdatingForms: boolean = false;
  371. private lastUpdateTime: number = 0;
  372. private lastAvSessionUpdateTime: number = 0;
  373. private readonly UPDATE_THROTTLE_MS: number = 100; // 100ms内只允许一次更新
  374. private progressTimer: number = -1;
  375. private isInitialized: boolean = false;
  376. private isDataRestored: boolean = false; // 新增:数据恢复完成标识
  377. private isPlayerPreparedForCurrentSong: boolean = false; // 新增:播放器是否已为当前歌曲准备就绪
  378. private currentRetryCount: number = 0;
  379. private lastAvSessionUpdate: number = 0;
  380. private lastAvMetadataUpdate: number = 0; // 新增:上次元数据更新时间
  381. private avSessionUpdateTimer: number = -1; // 新增:系统播控更新防抖定时器
  382. private avMetadataUpdateTimer: number = -1; // 新增:元数据更新防抖定时器
  383. private favList: VideoItem[] = [];
  384. private table: MediaTable | undefined = undefined;
  385. private lastAutoPlayTime: number = 0; // 新增:上次自动播放时间,用于防抖
  386. private autoPlayDebounceMs: number = 1000; // 自动播放防抖间隔(毫秒)
  387. private isManualSongChange: boolean = false; // 新增:是否正在进行手动歌曲切换
  388. // 图片缓存相关属性
  389. private imageCacheDir: string = ''; // 图片缓存目录
  390. private readonly CACHE_EXPIRY_DAYS: number = 7; // 缓存有效期:7天
  391. private readonly MAX_CACHE_SIZE_MB: number = 50; // 最大缓存大小:50MB
  392. private constructor() {
  393. this.playerManager = PlayerManager.getInstance();
  394. this.stateModel = new PlayerStateModel();
  395. this.playlistModel = new PlaylistModel();
  396. this.dataPersistence = DataPersistenceService.getInstance();
  397. this.playlistSync = PlaylistSyncService.getInstance();
  398. this.errorRecovery = ErrorRecoveryStrategy.getInstance();
  399. LogUtils.getInstance().LOGI('UnifiedPlayerService: Instance created');
  400. }
  401. public static getInstance(): UnifiedPlayerService {
  402. if (!UnifiedPlayerService.instance) {
  403. UnifiedPlayerService.instance = new UnifiedPlayerService();
  404. }
  405. return UnifiedPlayerService.instance;
  406. }
  407. async initialize(context: common.UIAbilityContext): Promise<void> {
  408. if (this.isInitialized && this.context === context) {
  409. LogUtils.getInstance().LOGI('UnifiedPlayerService已经初始化过了');
  410. return;
  411. }
  412. try {
  413. console.log("Heanup2 UnifiedPlayerService: 开始快速初始化服务");
  414. this.context = context;
  415. // 初始化图片缓存目录
  416. await this.initImageCacheDirectory();
  417. // 快速初始化核心组件
  418. this.playerManager.setStateCallback(this);
  419. // 初始化PlaylistModel的MediaTable
  420. this.playlistModel.initializeMediaTable(context);
  421. // 设置音频中断监听
  422. // 音频中断处理已移至LocalMusic中,响应更快
  423. // this.setupAudioInterruptListener();
  424. // 设置同步监听器
  425. this.playlistSync.addSyncListener(this);
  426. // 设置状态模型监听器,自动广播状态变化
  427. this.setupProgressTimer();
  428. this.isInitialized = true;
  429. // 异步初始化耗时组件,避免阻塞
  430. this.initializeHeavyComponentsAsync(context);
  431. // 清理无效的Form ID
  432. this.cleanupInvalidFormIds();
  433. LogUtils.getInstance().LOGI('UnifiedPlayerService: Core initialization completed');
  434. } catch (error) {
  435. LogUtils.getInstance().LOGI(`UnifiedPlayerService initialization error: ${error}`);
  436. throw new Error(`初始化失败: ${error}`);
  437. }
  438. }
  439. /**
  440. * 为卡片进程设置context
  441. * 卡片进程是独立的,需要单独设置context
  442. */
  443. setFormContext(context: common.UIAbilityContext | common.FormExtensionContext): void {
  444. try {
  445. this.context = context as common.UIAbilityContext;
  446. // 同时设置到AppStorage,确保卡片进程中其他地方能访问
  447. AppStorage.setOrCreate('context', context);
  448. LogUtils.getInstance().LOGI('UnifiedPlayerService: Form context set successfully');
  449. } catch (error) {
  450. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Failed to set form context: ${error}`);
  451. }
  452. }
  453. /**
  454. * 获取当前context
  455. */
  456. getContext(): common.UIAbilityContext | null {
  457. // 优先返回实例的context
  458. if (this.context) {
  459. return this.context;
  460. }
  461. // 尝试从AppStorage获取
  462. try {
  463. const appStorageContext = AppStorage.get('context') as common.UIAbilityContext;
  464. if (appStorageContext) {
  465. this.context = appStorageContext;
  466. return appStorageContext;
  467. }
  468. } catch (error) {
  469. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Failed to get context from AppStorage: ${error}`);
  470. }
  471. return null;
  472. }
  473. /**
  474. * 异步初始化耗时组件
  475. */
  476. private initializeHeavyComponentsAsync(context: common.UIAbilityContext): void {
  477. setTimeout(async () => {
  478. try {
  479. console.log("Heanup2 UnifiedPlayerService: 开始异步初始化耗时组件");
  480. // 并行初始化各个服务以提高效率
  481. const initPromises = [
  482. this.playerManager.initialize(context),
  483. this.dataPersistence.initialize(context),
  484. this.playlistSync.initialize(context),
  485. ];
  486. await Promise.all(initPromises);
  487. // 立即尝试快速恢复关键数据(从AppStorage)
  488. await this.fastRestoreFromMemory();
  489. this.initializeAvSession();
  490. // 并行初始化收藏列表和恢复数据,提高效率
  491. const initPromises2 = [
  492. this.restorePersistedState(),
  493. this.initializeFavoriteList()
  494. ];
  495. await Promise.all(initPromises2);
  496. console.log("Heanup2 UnifiedPlayerService: 异步组件初始化完成");
  497. LogUtils.getInstance().LOGI('UnifiedPlayerService: Heavy components initialized');
  498. } catch (error) {
  499. console.log(`Heanup2 UnifiedPlayerService: 异步组件初始化失败: ${error}`);
  500. LogUtils.getInstance().LOGI(`UnifiedPlayerService heavy components init error: ${error}`);
  501. // 即使失败也设置数据恢复完成,避免无限等待
  502. this.isDataRestored = true;
  503. }
  504. }, 10); // 缩短延迟,让数据恢复更快开始
  505. }
  506. // 播放控制方法
  507. async startPlayOrResumePlay(): Promise<void> {
  508. try {
  509. console.log("Heanup2 UnifiedPlayerService: 开始恢复播放");
  510. // 统一的服务就绪检查 - 这是最核心的检查
  511. if (!this.isAllServicesReady()) {
  512. console.log("Heanup2 UnifiedPlayerService: 服务未就绪,等待服务初始化完成");
  513. const isReady = await this.waitForAllServicesReady(3000, false);
  514. if (!isReady) {
  515. const error = new Error('Services not ready for playback');
  516. await this.handlePlaybackError(error, PlayerErrorType.INITIALIZATION_ERROR);
  517. return;
  518. }
  519. }
  520. // 获取当前歌曲 - 如果没有就尝试恢复
  521. let currentSong = this.playlistModel.getCurrentSong();
  522. if (!currentSong) {
  523. console.log("Heanup2 UnifiedPlayerService: 没有可播放的歌曲,尝试恢复持久化状态");
  524. await this.restorePersistedState();
  525. currentSong = this.playlistModel.getCurrentSong();
  526. if (!currentSong) {
  527. console.log("Heanup2 UnifiedPlayerService: 恢复后仍然没有可播放的歌曲");
  528. await this.handlePlaybackError(new Error('No song to play'), PlayerErrorType.PLAYBACK_ERROR);
  529. return;
  530. }
  531. console.log("Heanup2 UnifiedPlayerService: 恢复成功,当前歌曲信息:" + json.stringify(currentSong));
  532. }
  533. this.stateModel.updateLoadingState(true);
  534. // 简化的播放器状态检查 - 检查是否可以从暂停恢复
  535. const ijkPlayer = this.playerManager.getIjkPlayer()!; // 服务就绪后播放器一定存在
  536. const shouldResumeFromPause = this.canResumeFromPause(ijkPlayer);
  537. if (shouldResumeFromPause) {
  538. console.log("Heanup2 UnifiedPlayerService: 从暂停状态恢复播放");
  539. return await this.resumeFromPause();
  540. }
  541. // 新歌播放前的必要检查
  542. await this.validateSongForPlayback(currentSong);
  543. // 设置播放器并开始播放
  544. await this.setupAndStartPlayback(currentSong);
  545. await this.doUpdateAvSessionMetadata(currentSong)
  546. } catch (error) {
  547. this.stateModel.updateLoadingState(false);
  548. this.stateModel.updatePlayingState(false);
  549. await this.handlePlaybackError(error as Error, PlayerErrorType.PLAYBACK_ERROR);
  550. }
  551. }
  552. /**
  553. * 检查是否可以从暂停状态恢复播放
  554. */
  555. private canResumeFromPause(ijkPlayer: IjkMediaPlayer): boolean {
  556. const isPausedByManager = this.playerManager.isPausedState();
  557. const isCurrentlyPlaying = ijkPlayer.isPlaying();
  558. const playerDuration = ijkPlayer.getDuration();
  559. const hasValidMedia = playerDuration > 0;
  560. // 如果认为是暂停但没有有效媒体,清除假暂停状态
  561. if (isPausedByManager && !hasValidMedia) {
  562. console.log("Heanup2 UnifiedPlayerService: 检测到假暂停状态,清除暂停标记");
  563. this.playerManager.clearPausedState();
  564. this.isPlayerPreparedForCurrentSong = false;
  565. return false;
  566. }
  567. // 如果播放器正在播放,同步状态
  568. if (isCurrentlyPlaying) {
  569. this.syncPlayingState();
  570. return false; // 不需要恢复,已经在播放
  571. }
  572. // 检查是否可以从暂停恢复
  573. const canResume = isPausedByManager &&
  574. !isCurrentlyPlaying &&
  575. hasValidMedia &&
  576. this.isPlayerPreparedForCurrentSong;
  577. console.log(`Heanup2 UnifiedPlayerService: 暂停恢复检查 - canResume: ${canResume}`);
  578. return canResume;
  579. }
  580. /**
  581. * 同步播放状态
  582. */
  583. private syncPlayingState(): void {
  584. console.log("Heanup2 UnifiedPlayerService: 播放器正在播放,同步状态");
  585. this.stateModel.updateLoadingState(false);
  586. this.stateModel.updatePlayingState(true);
  587. this.playerManager.clearPausedState();
  588. this.startProgressTimer();
  589. this.updateSessionPlayState();
  590. this.updateWidgetsForStateChange();
  591. // 同步更新所有桌面卡片状态
  592. this.updateAllForms();
  593. }
  594. public async toggleFavorite(): Promise<void> {
  595. let isFav = 0
  596. let item = this.getCurrentSong() as VideoItem;
  597. if (Utility.getIsFav(this.getFav(), item)) {
  598. isFav = 0
  599. } else {
  600. isFav = 1
  601. }
  602. return new Promise<void>((resolve) => {
  603. this.getTable().updateIsFavByFilePath(item.filePath, isFav, async (result: boolean) => {
  604. if (result) {
  605. // 刷新UnifiedPlayerService的收藏状态,这会自动更新AVSession
  606. console.log("Heanup2 UnifiedPlayerService toggleFavorite: 收藏状态切换");
  607. await this.refreshFavoriteStatus();
  608. // 立即更新桌面卡片状态,确保收藏图标正确显示
  609. await this.updateAllForms(true);
  610. console.log("Heanup2 UnifiedPlayerService toggleFavorite: 收藏状态和卡片状态更新完成");
  611. }
  612. resolve();
  613. })
  614. });
  615. }
  616. /**
  617. * 从暂停状态恢复播放
  618. */
  619. private async resumeFromPause(): Promise<void> {
  620. try {
  621. console.log("Heanup2 UnifiedPlayerService: 直接从暂停状态恢复播放");
  622. this.stateModel.updateLoadingState(false);
  623. await this.playerManager.startPlayOrResumePlay();
  624. this.stateModel.updatePlayingState(true);
  625. this.startProgressTimer();
  626. setTimeout(() => this.updateProgress(), 100);
  627. this.saveCurrentState();
  628. this.updateSessionPlayState();
  629. await this.updateWidgetsForPlayStateChange();
  630. // 同步更新所有桌面卡片状态
  631. await this.updateAllForms();
  632. } catch (error) {
  633. throw new Error(`Resume from pause failed: ${error}`);
  634. }
  635. }
  636. /**
  637. * 验证歌曲是否可以播放
  638. */
  639. private async validateSongForPlayback(song: VideoItem): Promise<void> {
  640. // 检查文件是否存在(对于本地文件)
  641. if (!song.filePath.startsWith('http')) {
  642. const fileExists = await this.checkFileExists(song.filePath);
  643. if (!fileExists) {
  644. throw new Error(`File not found: ${song.filePath}`);
  645. }
  646. } else {
  647. // 检查网络连接(对于网络资源)
  648. const networkAvailable = await this.checkNetworkConnection();
  649. if (!networkAvailable) {
  650. throw new Error('Network not available');
  651. }
  652. }
  653. }
  654. /**
  655. * 设置播放器并开始播放
  656. */
  657. private async setupAndStartPlayback(song: VideoItem): Promise<void> {
  658. console.log(`Heanup2 UnifiedPlayerService: 设置播放器并开始播放: ${song.name}`);
  659. // 在随机播放模式下,确保播放历史被正确维护
  660. const playMode = this.stateModel.getState().playMode;
  661. if (playMode === PlayMode.RANDOM) {
  662. await this.playlistModel.ensureCurrentSongInHistory();
  663. }
  664. // 设置播放源和配置
  665. await this.setupPlayerForSong(song);
  666. // 准备播放器(实际播放会在 onPrepared 回调中开始)
  667. const preparedPlayer = this.playerManager.getIjkPlayer();
  668. if (preparedPlayer) {
  669. preparedPlayer.prepareAsync();
  670. }
  671. // 立即更新基础元数据(使用解析的duration),后续在播放器准备好后会用实际duration更新
  672. await this.updateAvSessionMetadata(song);
  673. this.restorePlaybackPosition(song);
  674. this.currentRetryCount = 0;
  675. }
  676. /**
  677. * 直接播放新歌曲,避免恢复逻辑干扰
  678. * 专为 playNext/playPrevious 设计
  679. */
  680. private async playNewSongDirectly(song: VideoItem | null): Promise<void> {
  681. if (!song) {
  682. throw new Error('No song provided for direct play');
  683. }
  684. this.stateModel.updateLoadingState(true);
  685. const playerInstance = this.playerManager.getIjkPlayer();
  686. if (!playerInstance) {
  687. throw new Error('Player not initialized');
  688. }
  689. // 检查文件是否存在(对于本地文件)
  690. if (!song.filePath.startsWith('http')) {
  691. const fileExists = await this.checkFileExists(song.filePath);
  692. if (!fileExists) {
  693. throw new Error(`File not found: ${song.filePath}`);
  694. }
  695. }
  696. // 在随机播放模式下,确保播放历史被正确维护
  697. const playMode = this.stateModel.getState().playMode;
  698. if (playMode === PlayMode.RANDOM) {
  699. await this.playlistModel.ensureCurrentSongInHistory();
  700. }
  701. // 设置播放源和配置
  702. await this.setupPlayerForSong(song);
  703. // 准备播放器
  704. const preparedPlayer = this.playerManager.getIjkPlayer();
  705. if (preparedPlayer) {
  706. preparedPlayer.prepareAsync();
  707. // 注意:实际播放和状态更新会在 onPrepared 回调中开始
  708. }
  709. // 重置重试计数
  710. this.currentRetryCount = 0;
  711. }
  712. async pause(): Promise<void> {
  713. try {
  714. // 检查服务是否已准备就绪
  715. if (!this.isAllServicesReady()) {
  716. LogUtils.getInstance().LOGI('UnifiedPlayerService: pause - 服务未就绪');
  717. // 对于暂停操作,如果服务未就绪,可以尝试简单的状态更新
  718. this.stateModel.updatePlayingState(false);
  719. return;
  720. }
  721. // 保存当前播放位置
  722. this.savePlaybackPosition();
  723. // 暂停播放器 - 确保先暂停播放器
  724. this.playerManager.pausePlayback();
  725. // 立即更新状态模型为暂停状态,确保状态同步
  726. this.stateModel.updatePlayingState(false);
  727. // 停止进度定时器
  728. this.stopProgressTimer();
  729. // 等待一小段时间确保播放器状态稳定
  730. await new Promise<void>(resolve => setTimeout(resolve, 50));
  731. // 保存播放状态变化
  732. this.saveCurrentState();
  733. this.updateSessionPlayState();
  734. // 更新卡片显示暂停状态
  735. await this.updateWidgetsForPlayStateChange();
  736. // 同步更新所有桌面卡片状态
  737. await this.updateAllForms();
  738. } catch (error) {
  739. LogUtils.getInstance().LOGI(`UnifiedPlayerService pause error: ${error}`);
  740. }
  741. }
  742. async stop(): Promise<void> {
  743. try {
  744. this.savePlaybackPosition();
  745. this.playerManager.stopPlayback();
  746. // 重要:停止时应该清除所有播放状态,不是暂停状态
  747. this.stateModel.updatePlayingState(false);
  748. // this.stateModel.state.isPaused = false; // 停止不是暂停
  749. this.stateModel.updateProgress(0, 0);
  750. this.stopProgressTimer();
  751. // 通知状态变化
  752. this.stateModel.notifySongChanged(this.getCurrentSong() as VideoItem);
  753. // 更新卡片显示停止状态
  754. await this.updateWidgetsForPlayStateChange();
  755. LogUtils.getInstance().LOGI('UnifiedPlayerService: Stop completed, all states cleared');
  756. } catch (error) {
  757. LogUtils.getInstance().LOGI(`UnifiedPlayerService stop error: ${error}`);
  758. throw new Error;
  759. }
  760. }
  761. /**
  762. * 静默停止播放,不触发状态变化通知(用于歌曲切换)
  763. */
  764. private async stopSilently(): Promise<void> {
  765. try {
  766. this.savePlaybackPosition();
  767. this.playerManager.stopPlayback();
  768. this.stopProgressTimer();
  769. // 不调用 updatePlayingState,避免触发状态变化通知
  770. } catch (error) {
  771. LogUtils.getInstance().LOGI(`UnifiedPlayerService stopSilently error: ${error}`);
  772. throw new Error;
  773. }
  774. }
  775. async seekTo(position: string): Promise<void> {
  776. try {
  777. const currentSong = this.playlistModel.getCurrentSong();
  778. if (!currentSong) {
  779. throw new Error('No song to seek');
  780. }
  781. // 检查是否支持拖动(wma格式不支持)
  782. if (StrUtil.isNotEmpty(currentSong.filePath) &&
  783. currentSong.filePath.toLowerCase().endsWith('.wma')) {
  784. throw new Error('WMA format does not support seeking');
  785. }
  786. await this.playerManager.seekToPosition(position);
  787. } catch (error) {
  788. throw new Error(`Failed to seek to position: ${error}`);
  789. }
  790. }
  791. // 播放列表控制方法
  792. async playNext(): Promise<void> {
  793. return this.changeTrack('next');
  794. }
  795. async playPrevious(): Promise<void> {
  796. return this.changeTrack('previous');
  797. }
  798. /**
  799. * 切换歌曲(下一首或上一首)
  800. * @param direction - 切换方向,'next' 或 'previous'
  801. */
  802. async changeTrack(direction: 'next' | 'previous'): Promise<void> {
  803. try {
  804. // 1. 设置手动歌曲切换标志,防止自动播放等逻辑干扰
  805. this.isManualSongChange = true;
  806. // 2. 检查服务是否已准备就绪
  807. if (!this.isAllServicesReady()) {
  808. LogUtils.getInstance().LOGI(`UnifiedPlayerService: ${direction} - 服务未就绪,等待服务初始化完成`);
  809. const isReady = await this.waitForAllServicesReady(500, false);
  810. if (!isReady) {
  811. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Services not ready for ${direction}, skipping`);
  812. // 统一处理方式:在服务未就绪时直接返回,避免抛出错误中断用户体验
  813. this.isManualSongChange = false;
  814. return;
  815. }
  816. }
  817. const playMode = this.stateModel.getState().playMode;
  818. let moved = false;
  819. // 3. 根据 direction 参数执行不同的逻辑
  820. if (direction === 'next') {
  821. if (this.playlistModel.hasNext(playMode)) {
  822. moved = this.playlistModel.moveToNext(playMode);
  823. }
  824. } else { // direction === 'previous'
  825. if (await this.playlistModel.hasPrevious(playMode)) {
  826. // 注意:'previous' 相关的操作是异步的,需要 await
  827. moved = await this.playlistModel.moveToPrevious(playMode);
  828. }
  829. }
  830. // 如果没有成功移动(列表到头/尾,或移动失败),则直接返回
  831. if (!moved) {
  832. this.isManualSongChange = false;
  833. return;
  834. }
  835. const newIndex = this.playlistModel.getCurrentIndex();
  836. const newSong = this.playlistModel.getCurrentSong();
  837. this.stateModel.updateCurrentIndex(newIndex);
  838. if (newSong) {
  839. this.stateModel.updateCurrentSong(newSong);
  840. }
  841. // 静默停止当前播放
  842. await this.stopSilently();
  843. // 直接播放新歌曲
  844. await this.playNewSongDirectly(newSong);
  845. // 更新播放列表状态
  846. await this.updatePlaylistStateInModel();
  847. // 播放新歌曲后,等待一段时间确保播放器状态稳定,然后更新卡片
  848. setTimeout(async () => {
  849. try {
  850. // 确保播放列表状态是最新的
  851. this.updatePlaylistStateInModelSync();
  852. // 强制更新所有桌面卡片状态
  853. await this.updateAllForms(true);
  854. if (newSong) {
  855. await this.updateAvSessionMetadata(newSong)
  856. }
  857. LogUtils.getInstance().LOGI('UnifiedPlayerService: Widget state updated after track change');
  858. } catch (error) {
  859. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Failed to update widgets after track change: ${error}`);
  860. }
  861. }, 200); // 延迟200ms确保播放器状态稳定
  862. } catch (error) {
  863. await this.handlePlaybackError(error as Error, PlayerErrorType.PLAYBACK_ERROR);
  864. } finally {
  865. // 5. 在任何情况下都清除手动切换标志
  866. setTimeout(() => {
  867. this.isManualSongChange = false;
  868. }, 1000); // 延迟1秒清除,确保播放器状态稳定
  869. }
  870. }
  871. async playSongAtIndex(index: number): Promise<void> {
  872. try {
  873. // 设置手动歌曲切换标志,防止自动播放干扰
  874. this.isManualSongChange = true;
  875. const success = this.playlistModel.playSongAtIndex(index);
  876. if (!success) {
  877. this.isManualSongChange = false;
  878. throw new Error(`Invalid song index: ${index}`);
  879. }
  880. this.stateModel.updateCurrentIndex(index);
  881. // 静默停止当前播放并开始新歌曲(不触发状态变化通知)
  882. await this.stopSilently();
  883. // 在随机播放模式下,确保当前歌曲被添加到播放历史
  884. const playMode = this.stateModel.getState().playMode;
  885. if (playMode === PlayMode.RANDOM) {
  886. await this.playlistModel.ensureCurrentSongInHistory();
  887. }
  888. await this.startPlayOrResumePlay();
  889. // 更新状态模型中的播放列表状态
  890. await this.updatePlaylistStateInModel();
  891. // 通知歌曲变化
  892. const currentSong = this.playlistModel.getCurrentSong();
  893. if (currentSong) {
  894. this.stateModel.updateCurrentSong(currentSong);
  895. }
  896. // 同步更新所有桌面卡片状态
  897. await this.updateAllForms();
  898. } catch (error) {
  899. throw new Error;
  900. } finally {
  901. // 确保在任何情况下都清除手动切换标志
  902. setTimeout(() => {
  903. this.isManualSongChange = false;
  904. }, 1000); // 延迟1秒清除,确保播放器状态稳定
  905. }
  906. }
  907. // 状态查询方法
  908. getCurrentState(): PlayerState {
  909. return this.stateModel.getState();
  910. }
  911. /**
  912. * 获取播放器实际播放状态(最准确)
  913. * 直接从ijkPlayer获取,不依赖状态模型
  914. */
  915. getActualPlayingState(): boolean {
  916. try {
  917. const ijkPlayer = this.playerManager?.getIjkPlayer();
  918. if (ijkPlayer) {
  919. return ijkPlayer.isPlaying();
  920. }
  921. return false;
  922. } catch (error) {
  923. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Failed to get actual playing state: ${error}`);
  924. return false;
  925. }
  926. }
  927. getCurrentSong(): VideoItem | null {
  928. return this.playlistModel.getCurrentSong();
  929. }
  930. getFav(): Array<VideoItem> {
  931. if (this.favList.length > 0) {
  932. return this.favList;
  933. }
  934. // 如果缓存为空,异步更新但返回空数组,避免阻塞
  935. this.getTable().queryByisFav(1, async (result: VideoItem[]) => {
  936. this.favList = result;
  937. // 更新收藏列表后,重新更新AVSession状态以反映正确的收藏状态
  938. if (result.length > 0) {
  939. setTimeout(() => {
  940. this.updateSessionPlayState();
  941. }, 100);
  942. }
  943. })
  944. return this.favList;
  945. }
  946. /**
  947. * 获取当前歌曲的收藏状态
  948. */
  949. private getCurrentSongFavoriteState(): boolean {
  950. try {
  951. let currentSong = this.playlistModel.getCurrentSong();
  952. if (!currentSong) {
  953. LogUtils.getInstance().LOGI(`UnifiedPlayerService: 当前歌曲为空`);
  954. return false;
  955. }
  956. let favList = this.getFav();
  957. if (!favList || favList.length === 0) {
  958. // 如果收藏列表为空,可能还在加载中,先返回false
  959. // 但不记录为错误,因为这是正常的初始化状态
  960. return false;
  961. }
  962. // 检查当前歌曲是否在收藏列表中
  963. for (let i = 0; i < favList.length; i++) {
  964. if (favList[i].filePath === currentSong.filePath && favList[i].isFav === 1) {
  965. LogUtils.getInstance().LOGI(`UnifiedPlayerService: 收藏歌曲:${currentSong.filePath}`);
  966. return true;
  967. }
  968. }
  969. LogUtils.getInstance().LOGI(`UnifiedPlayerService: 未收藏歌曲:${currentSong.filePath}`);
  970. return false;
  971. } catch (error) {
  972. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Failed to get favorite state: ${error}`);
  973. return false;
  974. }
  975. }
  976. /**
  977. * 初始化收藏列表
  978. */
  979. private async initializeFavoriteList(): Promise<void> {
  980. try {
  981. return new Promise<void>((resolve) => {
  982. this.getTable().queryByisFav(1, (result: VideoItem[]) => {
  983. this.favList = result;
  984. LogUtils.getInstance()
  985. .LOGI(`UnifiedPlayerService: Favorite list initialized with ${this.favList.length} items`);
  986. resolve();
  987. });
  988. });
  989. } catch (error) {
  990. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Failed to initialize favorite list: ${error}`);
  991. }
  992. }
  993. /**
  994. * 更新收藏列表
  995. */
  996. public async updateFavoriteList(): Promise<void> {
  997. try {
  998. return new Promise<void>((resolve) => {
  999. this.getTable().queryByisFav(1, (result: VideoItem[]) => {
  1000. this.favList = result;
  1001. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Favorite list updated with ${this.favList.length} items`);
  1002. // 更新收藏列表后,立即更新AVSession状态以反映正确的收藏状态
  1003. setTimeout(() => {
  1004. this.updateSessionPlayState();
  1005. this.updateAllForms();
  1006. }, 100);
  1007. resolve();
  1008. });
  1009. });
  1010. } catch (error) {
  1011. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Failed to update favorite list: ${error}`);
  1012. }
  1013. }
  1014. /**
  1015. * 强制刷新收藏状态(用于收藏/取消收藏操作后)
  1016. */
  1017. public async refreshFavoriteStatus(): Promise<void> {
  1018. try {
  1019. // 清空缓存,强制重新加载
  1020. this.favList = [];
  1021. await this.updateFavoriteList();
  1022. // 触发状态变化通知,确保LocalMusic UI能收到收藏状态变化
  1023. this.stateModel.forceNotifyStateChanged();
  1024. LogUtils.getInstance().LOGI('UnifiedPlayerService: Favorite status refreshed and state change notified');
  1025. } catch (error) {
  1026. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Failed to refresh favorite status: ${error}`);
  1027. }
  1028. }
  1029. private getTable(): MediaTable {
  1030. if (this.table) {
  1031. return this.table;
  1032. }
  1033. if (this.context) {
  1034. this.table = new MediaTable(this.context);
  1035. return this.table;
  1036. }
  1037. return new MediaTable(getContext(this) as common.UIAbilityContext);
  1038. }
  1039. getPlaylist(): VideoItem[] {
  1040. return this.playlistModel.getSongs();
  1041. }
  1042. getCurrentIndex(): number {
  1043. return this.playlistModel.getCurrentIndex();
  1044. }
  1045. /**
  1046. * 检查数据是否已从持久化存储恢复
  1047. */
  1048. isDataRestorationCompleted(): boolean {
  1049. return this.isDataRestored;
  1050. }
  1051. /**
  1052. * 等待数据恢复完成
  1053. */
  1054. async waitForDataRestoration(maxWaitMs: number = 3000): Promise<boolean> {
  1055. const startTime = Date.now();
  1056. while (!this.isDataRestored && (Date.now() - startTime) < maxWaitMs) {
  1057. await new Promise<void>(resolve => setTimeout(resolve, 100));
  1058. }
  1059. return this.isDataRestored;
  1060. }
  1061. /**
  1062. * 强制触发数据恢复(用于桌面卡片冷启动场景)
  1063. */
  1064. async forceDataRestoration(): Promise<boolean> {
  1065. try {
  1066. LogUtils.getInstance().LOGI('UnifiedPlayerService: Force data restoration started');
  1067. // 如果数据已经恢复,直接返回
  1068. if (this.isDataRestored) {
  1069. LogUtils.getInstance().LOGI('UnifiedPlayerService: Data already restored');
  1070. return true;
  1071. }
  1072. // 强制执行数据恢复
  1073. await this.restorePersistedState();
  1074. // 如果播放列表仍为空,尝试从内存快速恢复
  1075. if (this.playlistModel.getTotalCount() === 0) {
  1076. LogUtils.getInstance().LOGI('UnifiedPlayerService: Playlist empty, trying memory restore');
  1077. await this.fastRestoreFromMemory();
  1078. }
  1079. LogUtils.getInstance()
  1080. .LOGI(`UnifiedPlayerService: Force restoration completed, playlist size: ${this.playlistModel.getTotalCount()}`);
  1081. return this.isDataRestored;
  1082. } catch (error) {
  1083. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Force data restoration failed: ${error}`);
  1084. return false;
  1085. }
  1086. }
  1087. /**
  1088. * 检查所有相关服务是否已准备就绪
  1089. * 用于桌面卡片控制等场景,确保在执行播放控制前服务状态正常
  1090. */
  1091. isAllServicesReady(): boolean {
  1092. try {
  1093. // 检查基本初始化状态
  1094. if (!this.isInitialized) {
  1095. LogUtils.getInstance().LOGI('UnifiedPlayerService: Service not initialized');
  1096. return false;
  1097. }
  1098. // 检查核心组件是否就绪
  1099. if (!this.playerManager || !this.stateModel || !this.playlistModel) {
  1100. LogUtils.getInstance().LOGI('UnifiedPlayerService: Core components not ready');
  1101. return false;
  1102. }
  1103. // 检查 PlayerManager 是否已经初始化
  1104. const playerInstance = this.playerManager.getIjkPlayer();
  1105. if (!playerInstance) {
  1106. LogUtils.getInstance().LOGI('UnifiedPlayerService: Player instance not ready');
  1107. return false;
  1108. }
  1109. // 检查上下文是否可用
  1110. if (!this.context) {
  1111. LogUtils.getInstance().LOGI('UnifiedPlayerService: Context not available');
  1112. return false;
  1113. }
  1114. LogUtils.getInstance().LOGI('UnifiedPlayerService: All services ready');
  1115. return true;
  1116. } catch (error) {
  1117. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Service readiness check failed: ${error}`);
  1118. return false;
  1119. }
  1120. }
  1121. /**
  1122. * 验证初始化完整性
  1123. * 在应用启动后调用,确保所有关键组件都已正确初始化
  1124. */
  1125. validateInitialization(): InitializationValidation {
  1126. const errors: string[] = [];
  1127. const warnings: string[] = [];
  1128. try {
  1129. // 检查基础初始化
  1130. if (!this.isInitialized) {
  1131. errors.push('Service not initialized');
  1132. }
  1133. // 检查核心组件
  1134. if (!this.playerManager) {
  1135. errors.push('PlayerManager not available');
  1136. } else {
  1137. const playerInstance = this.playerManager.getIjkPlayer();
  1138. if (!playerInstance) {
  1139. warnings.push('Player instance not ready (may initialize later)');
  1140. }
  1141. }
  1142. if (!this.stateModel) {
  1143. errors.push('StateModel not available');
  1144. }
  1145. if (!this.playlistModel) {
  1146. errors.push('PlaylistModel not available');
  1147. }
  1148. if (!this.context) {
  1149. warnings.push('Context not set (may be set later)');
  1150. }
  1151. // 检查数据持久化服务
  1152. if (!this.dataPersistence) {
  1153. errors.push('DataPersistence service not available');
  1154. }
  1155. if (!this.playlistSync) {
  1156. errors.push('PlaylistSync service not available');
  1157. }
  1158. // 检查错误恢复策略
  1159. if (!this.errorRecovery) {
  1160. warnings.push('ErrorRecovery strategy not available');
  1161. }
  1162. const isValid = errors.length === 0;
  1163. LogUtils.getInstance()
  1164. .LOGI(`UnifiedPlayerService validation: ${isValid ? 'VALID' :
  1165. 'INVALID'}, errors: ${errors.length}, warnings: ${warnings.length}`);
  1166. const result: InitializationValidation = {
  1167. isValid,
  1168. errors,
  1169. warnings
  1170. };
  1171. return result;
  1172. } catch (error) {
  1173. errors.push(`Validation failed with exception: ${error}`);
  1174. const result: InitializationValidation = {
  1175. isValid: false,
  1176. errors,
  1177. warnings
  1178. };
  1179. return result;
  1180. }
  1181. }
  1182. /**
  1183. * 获取服务就绪状态的详细信息
  1184. * 用于调试和用户反馈
  1185. */
  1186. getServiceReadinessInfo(): ServiceReadinessInfo {
  1187. const playerInstance = this.playerManager?.getIjkPlayer();
  1188. const playlistSize = this.playlistModel?.getTotalCount() || 0;
  1189. const currentIndex = this.playlistModel?.getCurrentIndex() || -1;
  1190. const details: ServiceReadinessDetails = {
  1191. initialized: this.isInitialized,
  1192. hasPlayerInstance: !!playerInstance,
  1193. hasContext: !!this.context,
  1194. dataRestored: this.isDataRestored,
  1195. playlistSize: playlistSize,
  1196. currentIndex: currentIndex
  1197. };
  1198. const isReady = this.isAllServicesReady();
  1199. LogUtils.getInstance().LOGI(`UnifiedPlayerService readiness: ${JSON.stringify(details)}`);
  1200. const result: ServiceReadinessInfo = { isReady, details };
  1201. return result;
  1202. }
  1203. /**
  1204. * 等待所有服务准备就绪
  1205. * @param maxWaitMs 最大等待时间(毫秒)
  1206. * @param checkDataRestore 是否同时等待数据恢复完成
  1207. */
  1208. async waitForAllServicesReady(maxWaitMs: number = 5000, checkDataRestore: boolean = true): Promise<boolean> {
  1209. const startTime = Date.now();
  1210. const checkInterval = 100; // 每100ms检查一次
  1211. LogUtils.getInstance()
  1212. .LOGI(`UnifiedPlayerService: Waiting for services ready (maxWait: ${maxWaitMs}ms, checkDataRestore: ${checkDataRestore})`);
  1213. while ((Date.now() - startTime) < maxWaitMs) {
  1214. // 检查基础服务就绪状态
  1215. if (this.isAllServicesReady()) {
  1216. // 如果不需要检查数据恢复,直接返回成功
  1217. if (!checkDataRestore) {
  1218. LogUtils.getInstance().LOGI('UnifiedPlayerService: All services ready (data restore check skipped)');
  1219. return true;
  1220. }
  1221. // 检查数据恢复状态
  1222. if (this.isDataRestored) {
  1223. LogUtils.getInstance().LOGI('UnifiedPlayerService: All services ready and data restored');
  1224. return true;
  1225. }
  1226. // 检查播放列表是否有数据(即使数据恢复标识未设置)
  1227. const playlistSize = this.playlistModel.getTotalCount();
  1228. if (playlistSize > 0) {
  1229. LogUtils.getInstance()
  1230. .LOGI(`UnifiedPlayerService: Services ready with playlist data (${playlistSize} songs)`);
  1231. return true;
  1232. }
  1233. }
  1234. // 短暂等待后继续检查
  1235. await new Promise<void>(resolve => setTimeout(resolve, checkInterval));
  1236. }
  1237. // 超时检查最终状态
  1238. const finalCheck = this.isAllServicesReady();
  1239. const playlistSize = this.playlistModel?.getTotalCount() || 0;
  1240. LogUtils.getInstance()
  1241. .LOGI(`UnifiedPlayerService: Service readiness timeout - finalCheck: ${finalCheck}, playlistSize: ${playlistSize}, dataRestored: ${this.isDataRestored}`);
  1242. return finalCheck;
  1243. }
  1244. // 播放模式控制
  1245. setPlayMode(mode: number): void {
  1246. const playMode = mode as PlayMode;
  1247. this.stateModel.updatePlayMode(playMode);
  1248. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Play mode set to ${mode}`);
  1249. }
  1250. getPlayMode(): number {
  1251. return this.stateModel.getState().playMode;
  1252. }
  1253. // 音量和速度控制
  1254. setVolume(volume: number): void {
  1255. const volumeStr = volume.toString();
  1256. this.playerManager.setVolume(volumeStr, volumeStr);
  1257. this.stateModel.updateVolume(volume);
  1258. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Volume set to ${volume}`);
  1259. }
  1260. setPlaybackSpeed(speed: number): void {
  1261. const speedStr = speed.toString() + 'f';
  1262. this.playerManager.setPlaybackSpeed(speedStr);
  1263. this.stateModel.updateSpeed(speed);
  1264. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Speed set to ${speed}`);
  1265. }
  1266. // 播放列表管理
  1267. setPlaylist(songs: VideoItem[], currentIndex: number = 0): void {
  1268. // 记录调用栈信息以便调试
  1269. const stack = new Error().stack || 'No stack available';
  1270. const caller = stack.split('\n')[2] || 'Unknown caller';
  1271. this.playlistModel.replaceSongs(songs, currentIndex);
  1272. this.stateModel.updateCurrentIndex(currentIndex);
  1273. // 更新状态模型中的播放列表状态
  1274. this.updatePlaylistStateInModelSync();
  1275. // 更新当前歌曲
  1276. const currentSong = this.playlistModel.getCurrentSong();
  1277. this.stateModel.updateCurrentSong(currentSong);
  1278. // 同步到持久化存储
  1279. this.syncPlaylistToStorage();
  1280. // 更新卡片显示播放列表变化
  1281. this.updateWidgetsForPlaylistChange();
  1282. LogUtils.getInstance()
  1283. .LOGI(`UnifiedPlayerService: Playlist set with ${songs.length} songs, caller: ${caller.trim()}`);
  1284. // 如果设置的是小播放列表,记录更多信息
  1285. if (songs.length <= 20) {
  1286. LogUtils.getInstance()
  1287. .LOGI(`UnifiedPlayerService: Small playlist detected, caller stack: ${stack.substring(0, 500)}`);
  1288. }
  1289. }
  1290. addToPlaylist(song: VideoItem, index?: number): void {
  1291. this.playlistModel.addSong(song, index);
  1292. // 更新状态模型中的播放列表状态
  1293. this.updatePlaylistStateInModelSync();
  1294. // 同步到持久化存储
  1295. this.syncPlaylistToStorage();
  1296. // 更新卡片显示播放列表变化
  1297. this.updateWidgetsForPlaylistChange();
  1298. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Song added to playlist`);
  1299. }
  1300. removeFromPlaylist(index: number): VideoItem | null {
  1301. const removedSong = this.playlistModel.removeSong(index);
  1302. if (removedSong) {
  1303. this.stateModel.updateCurrentIndex(this.playlistModel.getCurrentIndex());
  1304. // 更新状态模型中的播放列表状态
  1305. this.updatePlaylistStateInModelSync();
  1306. // 更新当前歌曲
  1307. const currentSong = this.playlistModel.getCurrentSong();
  1308. this.stateModel.updateCurrentSong(currentSong);
  1309. // 同步到持久化存储
  1310. this.syncPlaylistToStorage();
  1311. // 更新卡片显示播放列表变化
  1312. this.updateWidgetsForPlaylistChange();
  1313. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Song removed from playlist at index ${index}`);
  1314. }
  1315. return removedSong;
  1316. }
  1317. /**
  1318. * 同步播放列表到服务
  1319. * 从LocalMusic迁移的方法,用于统一调用
  1320. */
  1321. syncPlaylistToService(songList: VideoItem[], currentIndex: number, playType: PlayMode): void {
  1322. try {
  1323. if (songList && songList.length > 0) {
  1324. // 记录调用栈信息以便调试
  1325. const stack = new Error().stack || 'No stack available';
  1326. const caller = stack.split('\n')[2] || 'Unknown caller';
  1327. this.setPlaylist(songList, currentIndex);
  1328. // 同步播放模式到UnifiedPlayerService
  1329. this.setPlayMode(playType);
  1330. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Playlist and play mode synced - ${songList.length} songs, mode: ${playType}, caller: ${caller.trim()}`);
  1331. // 如果同步的是小播放列表,记录更多信息
  1332. if (songList.length <= 20) {
  1333. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Small playlist sync detected, first few songs: ${songList.slice(0, 5).map(s => s.name).join(', ')}`);
  1334. }
  1335. } else {
  1336. LogUtils.getInstance().LOGI('UnifiedPlayerService: syncPlaylistToService - No songs to sync');
  1337. }
  1338. } catch (error) {
  1339. LogUtils.getInstance().LOGI(`UnifiedPlayerService syncPlaylistToService error: ${error}`);
  1340. }
  1341. }
  1342. // 事件监听
  1343. addStateListener(listener: PlayerStateListener): void {
  1344. this.stateModel.addStateListener(listener);
  1345. }
  1346. removeStateListener(listener: PlayerStateListener): void {
  1347. this.stateModel.removeStateListener(listener);
  1348. }
  1349. // 清理资源
  1350. release(): void {
  1351. try {
  1352. this.stopProgressTimer();
  1353. // 清理防抖定时器
  1354. if (this.avSessionUpdateTimer !== -1) {
  1355. clearTimeout(this.avSessionUpdateTimer);
  1356. this.avSessionUpdateTimer = -1;
  1357. }
  1358. if (this.avMetadataUpdateTimer !== -1) {
  1359. clearTimeout(this.avMetadataUpdateTimer);
  1360. this.avMetadataUpdateTimer = -1;
  1361. }
  1362. // 重要修复:确保在应用销毁时将播放状态设置为停止
  1363. try {
  1364. // 强制设置为停止状态,防止卡片显示错误的播放状态
  1365. this.stateModel.updatePlayingState(false);
  1366. this.stateModel.updateLoadingState(false);
  1367. // 确保播放器也停止
  1368. if (this.playerManager) {
  1369. this.playerManager.release() ;
  1370. }
  1371. LogUtils.getInstance().LOGI('UnifiedPlayerService: Forced stop state before release');
  1372. } catch (error) {
  1373. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Failed to force stop state: ${error}`);
  1374. }
  1375. // 保存当前状态(现在应该是停止状态)
  1376. this.saveCurrentState();
  1377. if (this.avSessionController) {
  1378. this.avSessionController.unregisterSessionListener();
  1379. this.avSessionController = null;
  1380. }
  1381. // 清理同步监听器
  1382. this.playlistSync.removeSyncListener(this);
  1383. this.playlistSync.release();
  1384. this.playerManager.release();
  1385. this.playlistModel.clear();
  1386. this.isInitialized = false;
  1387. LogUtils.getInstance().LOGI('UnifiedPlayerService: Resources released');
  1388. } catch (error) {
  1389. LogUtils.getInstance().LOGI(`UnifiedPlayerService release error: ${error}`);
  1390. }
  1391. }
  1392. /**
  1393. * 初始化AVSession
  1394. * 按照官方文档的要求:创建 -> 注册控制命令 -> 设置元数据 -> 激活
  1395. */
  1396. private initializeAvSession(): void {
  1397. try {
  1398. this.avSessionController = AvSessionController.getInstance(false);
  1399. LogUtils.getInstance().LOGI('UnifiedPlayerService: AVSession controller created');
  1400. setTimeout(() => {
  1401. this.setAvSessionListener();
  1402. LogUtils.getInstance().LOGI('UnifiedPlayerService: AVSession listeners configured');
  1403. }, 500);
  1404. } catch (error) {
  1405. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Failed to initialize AVSession: ${error}`);
  1406. }
  1407. }
  1408. /**
  1409. * 设置AVSession监听器
  1410. * 注意:控制命令已在AvSessionController中注册,这里设置实际的处理逻辑
  1411. */
  1412. private setAvSessionListener(): void {
  1413. if (!this.avSessionController) {
  1414. return;
  1415. }
  1416. const avSession = this.avSessionController.getAvSession();
  1417. if (!avSession) {
  1418. LogUtils.getInstance().LOGI('UnifiedPlayerService: AVSession not available for listener setup');
  1419. return;
  1420. }
  1421. try {
  1422. // 播放事件监听
  1423. avSession.on('play', () => {
  1424. this.startPlayOrResumePlay().catch((error: Error) => {
  1425. LogUtils.getInstance().LOGI(`AVSession play command failed: ${error}`);
  1426. });
  1427. });
  1428. // 暂停事件监听
  1429. avSession.on('pause', () => {
  1430. LogUtils.getInstance().LOGI('UnifiedPlayerService: AVSession pause command received');
  1431. this.pause().catch((error: Error) => {
  1432. LogUtils.getInstance().LOGI(`AVSession pause command failed: ${error}`);
  1433. });
  1434. });
  1435. // 停止事件监听
  1436. avSession.on('stop', () => {
  1437. LogUtils.getInstance().LOGI('UnifiedPlayerService: AVSession stop command received');
  1438. this.stop().catch((error: Error) => {
  1439. LogUtils.getInstance().LOGI(`AVSession stop command failed: ${error}`);
  1440. });
  1441. });
  1442. // 下一首事件监听
  1443. avSession.on('playNext', () => {
  1444. LogUtils.getInstance().LOGI('UnifiedPlayerService: AVSession playNext command received');
  1445. this.playNext().catch((error: Error) => {
  1446. LogUtils.getInstance().LOGI(`AVSession playNext command failed: ${error}`);
  1447. });
  1448. });
  1449. // 上一首事件监听
  1450. avSession.on('playPrevious', () => {
  1451. LogUtils.getInstance().LOGI('UnifiedPlayerService: AVSession playPrevious command received');
  1452. this.playPrevious().catch((error: Error) => {
  1453. LogUtils.getInstance().LOGI(`AVSession playPrevious command failed: ${error}`);
  1454. });
  1455. });
  1456. // 拖拽进度事件监听
  1457. avSession.on('seek', (time: number) => {
  1458. LogUtils.getInstance().LOGI(`UnifiedPlayerService: AVSession seek command received: ${time}ms`);
  1459. this.seekTo(time.toString()).catch((error: Error) => {
  1460. LogUtils.getInstance().LOGI(`AVSession seek command failed: ${error}`);
  1461. });
  1462. });
  1463. // 播放模式设置监听
  1464. avSession.on('setLoopMode', (mode: number) => {
  1465. LogUtils.getInstance().LOGI(`UnifiedPlayerService: AVSession setLoopMode command received: ${mode}`);
  1466. try {
  1467. this.setPlayMode(mode);
  1468. // 更新AvSession播放状态以反映新的播放模式
  1469. this.updateSessionPlayState();
  1470. // 同步更新桌面卡片
  1471. this.updateAllForms();
  1472. } catch (error) {
  1473. LogUtils.getInstance().LOGI(`AVSession setLoopMode command failed: ${error}`);
  1474. }
  1475. });
  1476. LogUtils.getInstance().LOGI('UnifiedPlayerService: AVSession listeners set up successfully');
  1477. } catch (error) {
  1478. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Failed to set AVSession listeners: ${error}`);
  1479. }
  1480. }
  1481. /**
  1482. * 处理播放错误
  1483. */
  1484. private async handlePlaybackError(error: Error,
  1485. errorType: PlayerErrorType = PlayerErrorType.PLAYBACK_ERROR): Promise<void> {
  1486. try {
  1487. // 创建播放器错误对象
  1488. const playerError = new PlayerError(errorType, error.message);
  1489. playerError.retryCount = this.currentRetryCount;
  1490. // 创建错误上下文
  1491. const context: ErrorContext = {
  1492. currentSong: this.playlistModel.getCurrentSong(),
  1493. playlist: this.playlistModel.getSongs(),
  1494. currentIndex: this.playlistModel.getCurrentIndex(),
  1495. retryCount: this.currentRetryCount,
  1496. isNetworkAvailable: true // 这里可以实际检查网络状态
  1497. };
  1498. // 获取恢复策略
  1499. const recoveryAction = await this.errorRecovery.handleError(playerError, context);
  1500. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Error recovery action: ${recoveryAction}`);
  1501. // 执行恢复动作
  1502. await this.executeRecoveryAction(recoveryAction, playerError, context);
  1503. } catch (recoveryError) {
  1504. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Error recovery failed: ${recoveryError}`);
  1505. this.stateModel.notifyError(new PlayerError(PlayerErrorType.PLAYBACK_ERROR,
  1506. `Recovery failed: ${recoveryError.message}`));
  1507. }
  1508. }
  1509. /**
  1510. * 执行错误恢复动作
  1511. */
  1512. private async executeRecoveryAction(action: ErrorRecoveryAction, error: PlayerError,
  1513. context: ErrorContext): Promise<void> {
  1514. switch (action) {
  1515. case ErrorRecoveryAction.RETRY:
  1516. await this.retryCurrentOperation(error);
  1517. break;
  1518. case ErrorRecoveryAction.SKIP_TO_NEXT:
  1519. await this.skipToNextSong();
  1520. break;
  1521. case ErrorRecoveryAction.STOP_PLAYBACK:
  1522. await this.stop();
  1523. this.stateModel.notifyError(error);
  1524. break;
  1525. case ErrorRecoveryAction.WAIT_AND_RETRY:
  1526. await this.waitAndRetry(error);
  1527. break;
  1528. case ErrorRecoveryAction.SHOW_ERROR:
  1529. this.stateModel.notifyError(error);
  1530. break;
  1531. default:
  1532. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Unknown recovery action: ${action}`);
  1533. this.stateModel.notifyError(error);
  1534. }
  1535. }
  1536. /**
  1537. * 重试当前操作
  1538. */
  1539. private async retryCurrentOperation(error: PlayerError): Promise<void> {
  1540. try {
  1541. this.currentRetryCount++;
  1542. const delay = this.errorRecovery.getRetryDelay(this.currentRetryCount);
  1543. LogUtils.getInstance()
  1544. .LOGI(`UnifiedPlayerService: Retrying operation in ${delay}ms (attempt ${this.currentRetryCount})`);
  1545. // 等待指定时间后重试
  1546. setTimeout(async () => {
  1547. try {
  1548. await this.startPlayOrResumePlay();
  1549. this.currentRetryCount = 0; // 重试成功,重置计数
  1550. } catch (retryError) {
  1551. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Retry failed: ${retryError}`);
  1552. await this.handlePlaybackError(retryError as Error, error.type);
  1553. }
  1554. }, delay);
  1555. } catch (retryError) {
  1556. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Retry setup failed: ${retryError}`);
  1557. await this.skipToNextSong();
  1558. }
  1559. }
  1560. /**
  1561. * 跳到下一首歌曲
  1562. */
  1563. private async skipToNextSong(): Promise<void> {
  1564. try {
  1565. LogUtils.getInstance().LOGI('UnifiedPlayerService: Skipping to next song due to error');
  1566. // 重置重试计数
  1567. this.currentRetryCount = 0;
  1568. // 检查是否有下一首
  1569. const playMode = this.stateModel.getState().playMode;
  1570. if (this.playlistModel.hasNext(playMode)) {
  1571. await this.playNext();
  1572. } else {
  1573. // 没有下一首,停止播放
  1574. await this.stop();
  1575. LogUtils.getInstance().LOGI('UnifiedPlayerService: No next song available, stopping playback');
  1576. }
  1577. } catch (skipError) {
  1578. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Failed to skip to next song: ${skipError}`);
  1579. await this.stop();
  1580. }
  1581. }
  1582. /**
  1583. * 等待后重试
  1584. */
  1585. private async waitAndRetry(error: PlayerError): Promise<void> {
  1586. try {
  1587. this.currentRetryCount++;
  1588. const delay = this.errorRecovery.getRetryDelay(this.currentRetryCount);
  1589. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Waiting ${delay}ms before retry (network error)`);
  1590. // 更新状态为加载中
  1591. this.stateModel.updateLoadingState(true);
  1592. setTimeout(async () => {
  1593. try {
  1594. await this.startPlayOrResumePlay();
  1595. this.currentRetryCount = 0; // 重试成功,重置计数
  1596. } catch (retryError) {
  1597. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Network retry failed: ${retryError}`);
  1598. await this.handlePlaybackError(retryError as Error, PlayerErrorType.NETWORK_ERROR);
  1599. }
  1600. }, delay);
  1601. } catch (waitError) {
  1602. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Wait and retry setup failed: ${waitError}`);
  1603. await this.skipToNextSong();
  1604. }
  1605. }
  1606. /**
  1607. * 检查文件是否存在
  1608. */
  1609. private async checkFileExists(filePath: string): Promise<boolean> {
  1610. try {
  1611. // 这里可以添加文件存在性检查逻辑
  1612. // 暂时返回true,实际实现需要使用文件系统API
  1613. return true;
  1614. } catch (error) {
  1615. LogUtils.getInstance().LOGI(`UnifiedPlayerService: File check failed: ${error}`);
  1616. return false;
  1617. }
  1618. }
  1619. /**
  1620. * 检查网络连接
  1621. */
  1622. private async checkNetworkConnection(): Promise<boolean> {
  1623. try {
  1624. // 这里可以添加网络连接检查逻辑
  1625. // 暂时返回true,实际实现需要使用网络API
  1626. return true;
  1627. } catch (error) {
  1628. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Network check failed: ${error}`);
  1629. return false;
  1630. }
  1631. }
  1632. // 私有辅助方法
  1633. /**
  1634. * 检查播放器是否需要重新设置
  1635. */
  1636. private async needsPlayerSetup(song: VideoItem): Promise<boolean> {
  1637. try {
  1638. const ijkPlayer = this.playerManager.getIjkPlayer();
  1639. if (!ijkPlayer) {
  1640. console.log("Heanup2 UnifiedPlayerService: 播放器不存在,需要设置");
  1641. return true;
  1642. }
  1643. // 检查播放器是否有数据源
  1644. const duration = ijkPlayer.getDuration();
  1645. if (duration <= 0) {
  1646. console.log("Heanup2 UnifiedPlayerService: 播放器无数据源或时长无效,需要设置");
  1647. return true;
  1648. }
  1649. // 检查当前数据源是否匹配(这里我们通过检查当前歌曲信息来判断)
  1650. const currentSong = this.playlistModel.getCurrentSong();
  1651. if (!currentSong || currentSong.filePath !== song.filePath) {
  1652. console.log(`Heanup2 UnifiedPlayerService: 歌曲文件路径不匹配,需要重新设置 (当前: ${currentSong?.filePath}, 目标: ${song.filePath})`);
  1653. return true;
  1654. }
  1655. console.log(`Heanup2 UnifiedPlayerService: 播放器已设置正确的数据源,无需重新设置 (时长: ${duration}ms)`);
  1656. return false;
  1657. } catch (error) {
  1658. console.log(`Heanup2 UnifiedPlayerService: 检查播放器设置状态时出错: ${error},默认需要设置`);
  1659. return true;
  1660. }
  1661. }
  1662. private async setupPlayerForSong(song: VideoItem): Promise<void> {
  1663. const ijkPlayer = this.playerManager.getIjkPlayer();
  1664. if (!ijkPlayer) {
  1665. throw new Error('Player not initialized');
  1666. }
  1667. console.log("Heanup2 UnifiedPlayerService: 开始重新设置播放器,这会重置所有状态");
  1668. // 标记播放器未准备(因为要重新设置)
  1669. this.isPlayerPreparedForCurrentSong = false;
  1670. // 重置播放器 - 这会清除所有状态包括播放位置
  1671. ijkPlayer.reset();
  1672. // 重新设置音频模式 - 重置后需要重新设置
  1673. ijkPlayer.setAudioId('unifiedPlayer');
  1674. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Reset audio mode for ${song.name}`);
  1675. // 重新设置配置
  1676. this.playerManager.setupIjkPlayerOptions();
  1677. // 设置音量(针对dsf格式特殊处理)
  1678. const volume = this.stateModel.getState().volume;
  1679. if (song.filePath.toLowerCase().endsWith('.dsf')) {
  1680. this.playerManager.setVolume('1', '1');
  1681. } else {
  1682. this.playerManager.setVolume(volume.toString(), volume.toString());
  1683. }
  1684. // 设置数据源
  1685. ijkPlayer.setDataSource(song.filePath);
  1686. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Set data source to ${song.filePath}`);
  1687. // 设置HTTP请求头
  1688. const headers = new Map([
  1689. ["user_agent", "Mozilla/5.0 BiliDroid/7.30.0 (bbcallen@gmail.com)"],
  1690. ["referer", "https://www.bilibili.com"]
  1691. ]);
  1692. ijkPlayer.setDataSourceHeader(headers);
  1693. // 设置播放速度
  1694. const speed = this.stateModel.getState().speed;
  1695. this.playerManager.setPlaybackSpeed(speed.toString() + 'f');
  1696. // 设置消息监听器(重要:用于处理播放器内部事件)
  1697. ijkPlayer.setMessageListener();
  1698. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Player setup for song ${song.name}`);
  1699. console.log("Heanup2 UnifiedPlayerService: 播放器重新设置完成,准备开始新的播放");
  1700. }
  1701. private setupProgressTimer(): void {
  1702. this.progressTimer = setInterval(() => {
  1703. this.updateProgress();
  1704. }, 1000); // 每秒更新一次进度
  1705. }
  1706. private startProgressTimer(): void {
  1707. // 先停止现有的定时器,避免重复启动
  1708. this.stopProgressTimer();
  1709. // 启动新的定时器
  1710. this.setupProgressTimer();
  1711. LogUtils.getInstance().LOGI('UnifiedPlayerService: Progress timer started');
  1712. }
  1713. /**
  1714. * 停止进度定时器
  1715. */
  1716. private stopProgressTimer(): void {
  1717. if (this.progressTimer !== -1) {
  1718. clearInterval(this.progressTimer);
  1719. this.progressTimer = -1;
  1720. LogUtils.getInstance().LOGI('UnifiedPlayerService: Progress timer stopped');
  1721. }
  1722. }
  1723. private updateProgress(): void {
  1724. try {
  1725. const ijkPlayer = this.playerManager.getIjkPlayer();
  1726. const currentState = this.stateModel.getState();
  1727. // 只有在播放器存在、正在播放且已准备好时才更新进度
  1728. if (!ijkPlayer || !currentState.isPlaying || !this.isPlayerPreparedForCurrentSong) {
  1729. return;
  1730. }
  1731. const currentPosition = ijkPlayer.getCurrentPosition();
  1732. const duration = ijkPlayer.getDuration();
  1733. // 确保获取到有效的位置信息
  1734. if (currentPosition >= 0 && duration > 0) {
  1735. this.stateModel.updateProgress(currentPosition, duration);
  1736. this.updateSessionPlayState();
  1737. }
  1738. } catch (error) {
  1739. LogUtils.getInstance().LOGI(`UnifiedPlayerService updateProgress error: ${error}`);
  1740. }
  1741. }
  1742. getCurrentPosition(): number {
  1743. try {
  1744. const ijkPlayer = this.playerManager.getIjkPlayer();
  1745. if (!ijkPlayer) {
  1746. return 0;
  1747. }
  1748. return ijkPlayer.getCurrentPosition();
  1749. } catch (error) {
  1750. LogUtils.getInstance().LOGI(`UnifiedPlayerService getCurrentPosition error: ${error}`);
  1751. return 0;
  1752. }
  1753. }
  1754. getIjkPlayer(): IjkMediaPlayer | null {
  1755. try {
  1756. return this.playerManager.getIjkPlayer();
  1757. } catch (error) {
  1758. LogUtils.getInstance().LOGI(`UnifiedPlayerService getIjkPlayer error: ${error}`);
  1759. return null;
  1760. }
  1761. }
  1762. private savePlaybackPosition(): void {
  1763. const ijkPlayer = this.playerManager.getIjkPlayer();
  1764. if (ijkPlayer != null) {
  1765. const position = ijkPlayer.getCurrentPosition();
  1766. // ToastUtil.showToast('保存记忆播放 = ' + position)
  1767. const duration = ijkPlayer.getDuration();
  1768. const threshold = 5000; // 阈值,单位为毫秒(这里设为5秒)
  1769. // 如果播放位置接近视频末尾,则保存 position 为 0
  1770. const playbackPosition = (duration - position < threshold) ? 0 : position;
  1771. const currentSong = this.playlistModel.getCurrentSong();
  1772. if (currentSong != null) {
  1773. PuraPreferencesUtil.putSync(currentSong.filePath, playbackPosition);
  1774. }
  1775. }
  1776. }
  1777. private async restorePlaybackPosition(song: VideoItem): Promise<void> {
  1778. // const position: number = PreferencesUtil.getNumberSync(this.videoUrl, 0);
  1779. // // ToastUtil.showToast('position = ' + position)
  1780. // if (this.mIjkMediaPlayer != null && position > 0) {
  1781. // // ToastUtil.showToast('seekTo = ' + position)
  1782. // this.seekTo(position + "");
  1783. //
  1784. // }
  1785. if (song != null) {
  1786. const position: number = PuraPreferencesUtil.getNumberSync(song.filePath, 0);
  1787. if (position > 0) {
  1788. await this.playerManager.seekToPosition(position.toString());
  1789. }
  1790. }
  1791. // try {
  1792. // // 使用DataPersistenceService恢复播放进度
  1793. // const progressData = await this.dataPersistence.loadPlaybackProgress(song.filePath);
  1794. // if (progressData && progressData.position > 0 && !progressData.completed) {
  1795. // await this.playerManager.seekToPosition(progressData.position.toString());
  1796. // LogUtils.getInstance().LOGI(`UnifiedPlayerService: Restored playback position ${progressData.position}ms for ${song.name}`);
  1797. // }
  1798. // } catch (error) {
  1799. // LogUtils.getInstance().LOGI(`UnifiedPlayerService restorePlaybackPosition error: ${error}`);
  1800. // }
  1801. }
  1802. // ==================== 数据持久化和同步方法 ====================
  1803. /**
  1804. * 恢复持久化的状态
  1805. */
  1806. /**
  1807. * 快速从内存中恢复关键数据(AppStorage)
  1808. */
  1809. private async fastRestoreFromMemory(): Promise<void> {
  1810. try {
  1811. console.log("Heanup2 UnifiedPlayerService: 开始从内存快速恢复关键数据");
  1812. // 从AppStorage快速获取播放列表数据
  1813. const playlistData = AppStorage.get<PlaylistData>('player_playlist');
  1814. if (playlistData && playlistData.songs.length > 0) {
  1815. console.log(`Heanup2 UnifiedPlayerService: 从内存快速恢复播放列表,歌曲数量=${playlistData.songs.length}`);
  1816. this.playlistModel.replaceSongs(playlistData.songs, playlistData.currentIndex);
  1817. this.stateModel.updateCurrentIndex(playlistData.currentIndex);
  1818. this.stateModel.updatePlayMode(playlistData.playMode);
  1819. // 恢复当前歌曲
  1820. const currentSong = this.playlistModel.getCurrentSong();
  1821. if (currentSong) {
  1822. this.stateModel.updateCurrentSong(currentSong);
  1823. console.log(`Heanup2 UnifiedPlayerService: 快速恢复当前歌曲: ${currentSong.name}`);
  1824. }
  1825. }
  1826. // 从AppStorage快速获取播放状态数据
  1827. const stateData = AppStorage.get<PlayerStateData>('player_state') as PlayerStateData;
  1828. if (stateData) {
  1829. console.log(`Heanup2 UnifiedPlayerService: 从内存快速恢复播放状态 - playing: ${stateData.isPlaying}, index: ${stateData.currentIndex}`);
  1830. this.stateModel.updateVolume(stateData.volume);
  1831. this.stateModel.updateSpeed(stateData.speed);
  1832. this.stateModel.updatePlayMode(stateData.playMode);
  1833. this.stateModel.updatePlayingState(stateData.isPlaying || false);
  1834. if (stateData.currentIndex >= 0) {
  1835. this.stateModel.updateCurrentIndex(stateData.currentIndex);
  1836. }
  1837. }
  1838. console.log("Heanup2 UnifiedPlayerService: 内存数据快速恢复完成");
  1839. // 设置一个临时的数据恢复完成标识,允许快速响应
  1840. this.isDataRestored = true;
  1841. // 立即更新播放列表状态,确保上一首/下一首状态正确
  1842. this.updatePlaylistStateInModelSync();
  1843. } catch (error) {
  1844. console.log(`Heanup2 UnifiedPlayerService: 快速恢复内存数据失败: ${error}`);
  1845. }
  1846. }
  1847. private async restorePersistedState(): Promise<void> {
  1848. try {
  1849. console.log("Heanup2 UnifiedPlayerService: 开始快速恢复持久化状态");
  1850. // 并行加载播放列表和播放状态,提高效率
  1851. const loadResults = await Promise.all([
  1852. this.dataPersistence.loadPlaylist().catch(() => null),
  1853. this.dataPersistence.loadPlayerState().catch(() => null)
  1854. ]);
  1855. const playlistData = loadResults[0];
  1856. const stateData = loadResults[1];
  1857. // 恢复播放列表
  1858. if (playlistData && playlistData.songs.length > 0) {
  1859. console.log(`Heanup2 UnifiedPlayerService: 恢复播放列表,歌曲数量=${playlistData.songs.length}, 当前索引=${playlistData.currentIndex}`);
  1860. this.playlistModel.replaceSongs(playlistData.songs, playlistData.currentIndex);
  1861. this.stateModel.updateCurrentIndex(playlistData.currentIndex);
  1862. this.stateModel.updatePlayMode(playlistData.playMode);
  1863. // 恢复当前歌曲到状态模型
  1864. const currentSong = this.playlistModel.getCurrentSong();
  1865. if (currentSong) {
  1866. this.stateModel.updateCurrentSong(currentSong);
  1867. console.log(`Heanup2 UnifiedPlayerService: 恢复当前歌曲: ${currentSong.name}`);
  1868. }
  1869. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Restored playlist with ${playlistData.songs.length} songs`);
  1870. // 立即更新播放列表状态,确保上一首/下一首状态正确
  1871. this.updatePlaylistStateInModelSync();
  1872. } else {
  1873. console.log("Heanup2 UnifiedPlayerService: 没有找到保存的播放列表或播放列表为空");
  1874. }
  1875. // 恢复播放状态
  1876. if (stateData) {
  1877. console.log(`Heanup2 UnifiedPlayerService: 加载到的播放状态 - playing: ${stateData.isPlaying}, paused: ${stateData.isPaused}, index: ${stateData.currentIndex}`);
  1878. // 批量更新状态,减少回调次数
  1879. this.stateModel.updateVolume(stateData.volume);
  1880. this.stateModel.updateSpeed(stateData.speed);
  1881. this.stateModel.updatePlayMode(stateData.playMode);
  1882. // 恢复播放状态(UI会使用实际播放器状态,所以这里可以恢复逻辑状态)
  1883. this.stateModel.updatePlayingState(stateData.isPlaying || false);
  1884. // 恢复当前播放索引
  1885. if (stateData.currentIndex >= 0) {
  1886. this.stateModel.updateCurrentIndex(stateData.currentIndex);
  1887. }
  1888. console.log("Heanup2 UnifiedPlayerService: 播放状态恢复完成");
  1889. LogUtils.getInstance()
  1890. .LOGI(`UnifiedPlayerService: Restored player state - playing: ${stateData.isPlaying}, paused: ${stateData.isPaused}, index: ${stateData.currentIndex}`);
  1891. } else {
  1892. console.log("Heanup2 UnifiedPlayerService: 没有找到保存的播放状态");
  1893. }
  1894. console.log("Heanup2 UnifiedPlayerService: 持久化状态快速恢复完成");
  1895. // 设置数据恢复完成标识
  1896. this.isDataRestored = true;
  1897. // 立即更新播放列表状态,确保上一首/下一首状态正确
  1898. this.updatePlaylistStateInModelSync();
  1899. LogUtils.getInstance().LOGI('UnifiedPlayerService: Data restoration completed');
  1900. } catch (error) {
  1901. console.log(`Heanup2 UnifiedPlayerService: 恢复持久化状态时出错: ${error}`);
  1902. LogUtils.getInstance().LOGI(`UnifiedPlayerService restorePersistedState error: ${error}`);
  1903. // 即使出错也设置为已恢复,避免无限等待
  1904. this.isDataRestored = true;
  1905. }
  1906. }
  1907. /**
  1908. * 保存当前状态
  1909. */
  1910. private saveCurrentState(): void {
  1911. try {
  1912. // 获取当前状态
  1913. const currentState = this.stateModel.getState();
  1914. // 验证播放器实际状态,确保状态模型与实际播放器状态一致
  1915. try {
  1916. const ijkPlayer = this.playerManager.getIjkPlayer();
  1917. const actualPlaying = ijkPlayer ? ijkPlayer.isPlaying() : false;
  1918. // 如果状态不一致,以实际播放器状态为准
  1919. if (currentState.isPlaying !== actualPlaying) {
  1920. LogUtils.getInstance().LOGI(`UnifiedPlayerService: State mismatch detected during save - StateModel: ${currentState.isPlaying}, Actual: ${actualPlaying}`);
  1921. this.stateModel.updatePlayingState(actualPlaying);
  1922. // 重新获取修正后的状态
  1923. const correctedState = this.stateModel.getState();
  1924. this.dataPersistence.savePlayerState(correctedState);
  1925. } else {
  1926. this.dataPersistence.savePlayerState(currentState);
  1927. }
  1928. } catch (playerError) {
  1929. // 如果获取播放器状态失败,默认保存为停止状态
  1930. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Failed to get actual player state, saving as stopped: ${playerError}`);
  1931. this.stateModel.updatePlayingState(false);
  1932. const stoppedState = this.stateModel.getState();
  1933. this.dataPersistence.savePlayerState(stoppedState);
  1934. }
  1935. this.savePlaybackPosition();
  1936. this.syncPlaylistToStorage();
  1937. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Current state saved successfully`);
  1938. } catch (error) {
  1939. LogUtils.getInstance().LOGI(`UnifiedPlayerService saveCurrentState error: ${error}`);
  1940. }
  1941. }
  1942. /**
  1943. * 外部调用保存当前状态的方法(供EntryAbility使用)
  1944. */
  1945. public saveCurrentStateExternal(): void {
  1946. this.saveCurrentState();
  1947. }
  1948. /**
  1949. * 同步播放列表到存储
  1950. */
  1951. private syncPlaylistToStorage(): void {
  1952. try {
  1953. const playlist = this.playlistModel.getSongs();
  1954. const currentIndex = this.playlistModel.getCurrentIndex();
  1955. const playMode = this.stateModel.getState().playMode;
  1956. this.playlistSync.syncPlaylistToStorage(playlist, currentIndex, playMode);
  1957. } catch (error) {
  1958. LogUtils.getInstance().LOGI(`UnifiedPlayerService syncPlaylistToStorage error: ${error}`);
  1959. }
  1960. }
  1961. // ==================== PlaylistSyncListener 接口实现 ====================
  1962. /**
  1963. * 播放列表同步完成回调
  1964. */
  1965. onPlaylistSynced(playlist: VideoItem[], currentIndex: number, playMode: PlayMode): void {
  1966. try {
  1967. LogUtils.getInstance()
  1968. .LOGI(`UnifiedPlayerService: Playlist synced - ${playlist.length} songs, index ${currentIndex}`);
  1969. // 播放列表同步完成,无需额外通知
  1970. } catch (error) {
  1971. LogUtils.getInstance().LOGI(`UnifiedPlayerService onPlaylistSynced error: ${error}`);
  1972. }
  1973. }
  1974. /**
  1975. * 播放列表加载完成回调
  1976. */
  1977. onPlaylistLoaded(playlistData: PlaylistData): void {
  1978. try {
  1979. LogUtils.getInstance()
  1980. .LOGI(`UnifiedPlayerService: Playlist loaded from storage - ${playlistData.songs.length} songs`);
  1981. // 检查是否需要更新本地播放列表
  1982. const currentPlaylist = this.playlistModel.getSongs();
  1983. const currentIndex = this.playlistModel.getCurrentIndex();
  1984. // 如果存储的数据更新,更新本地播放列表
  1985. if (playlistData.songs.length !== currentPlaylist.length ||
  1986. playlistData.currentIndex !== currentIndex) {
  1987. this.playlistModel.replaceSongs(playlistData.songs, playlistData.currentIndex);
  1988. this.stateModel.updateCurrentIndex(playlistData.currentIndex);
  1989. this.stateModel.updatePlayMode(playlistData.playMode);
  1990. LogUtils.getInstance().LOGI('UnifiedPlayerService: Local playlist updated from storage');
  1991. }
  1992. // 更新数据恢复状态(如果还没有恢复的话)
  1993. if (!this.isDataRestored) {
  1994. this.isDataRestored = true;
  1995. LogUtils.getInstance().LOGI('UnifiedPlayerService: Data restoration completed via onPlaylistLoaded');
  1996. }
  1997. } catch (error) {
  1998. LogUtils.getInstance().LOGI(`UnifiedPlayerService onPlaylistLoaded error: ${error}`);
  1999. }
  2000. }
  2001. /**
  2002. * 自动同步执行回调
  2003. */
  2004. onAutoSyncPerformed(): void {
  2005. try {
  2006. // 定期保存当前状态
  2007. this.saveCurrentState();
  2008. } catch (error) {
  2009. LogUtils.getInstance().LOGI(`UnifiedPlayerService onAutoSyncPerformed error: ${error}`);
  2010. }
  2011. }
  2012. /**
  2013. * 同步错误回调
  2014. */
  2015. onSyncError(error: Error): void {
  2016. LogUtils.getInstance().LOGI(`UnifiedPlayerService sync error: ${error.message}`);
  2017. }
  2018. // ==================== 公共数据访问方法 ====================
  2019. /**
  2020. * 获取数据持久化服务实例
  2021. */
  2022. getDataPersistenceService(): IDataPersistenceService {
  2023. return this.dataPersistence;
  2024. }
  2025. /**
  2026. * 获取播放列表同步服务实例
  2027. */
  2028. getPlaylistSyncService(): PlaylistSyncService {
  2029. return this.playlistSync;
  2030. }
  2031. // ==================== StateChangeCallback 接口实现 ====================
  2032. /**
  2033. * 歌曲变化回调(来自StateSyncService的本地通知)
  2034. */
  2035. onSongChanged?(song: VideoItem): void {
  2036. try {
  2037. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Received song change notification - ${song.name}`);
  2038. // 这里可以处理来自其他组件的歌曲变化通知
  2039. } catch (error) {
  2040. LogUtils.getInstance().LOGI(`UnifiedPlayerService onSongChanged error: ${error}`);
  2041. }
  2042. }
  2043. // ==================== WidgetControlCallback 接口实现 ====================
  2044. /**
  2045. * 处理来自卡片的播放/暂停命令
  2046. */
  2047. async onPlayPause?(): Promise<void> {
  2048. try {
  2049. LogUtils.getInstance().LOGI('UnifiedPlayerService: Received play/pause command from widget');
  2050. // 优先检查播放器的实际状态,而不是状态模型
  2051. const ijkPlayer = this.playerManager.getIjkPlayer();
  2052. const isActuallyPlaying = ijkPlayer ? ijkPlayer.isPlaying() : false;
  2053. const stateModelPlaying = this.getCurrentState().isPlaying;
  2054. LogUtils.getInstance()
  2055. .LOGI(`UnifiedPlayerService: Widget playPause - StateModel: ${stateModelPlaying}, ActualPlayer: ${isActuallyPlaying}`);
  2056. // 如果播放器实际在播放,则暂停;否则开始/恢复播放
  2057. if (isActuallyPlaying) {
  2058. LogUtils.getInstance().LOGI('UnifiedPlayerService: Player is actually playing, will pause');
  2059. await this.pause();
  2060. } else {
  2061. LogUtils.getInstance().LOGI('UnifiedPlayerService: Player is not playing, will start/resume play');
  2062. await this.startPlayOrResumePlay();
  2063. }
  2064. LogUtils.getInstance().LOGI('UnifiedPlayerService: Play/pause command executed successfully');
  2065. } catch (error) {
  2066. LogUtils.getInstance().LOGI(`UnifiedPlayerService onPlayPause error: ${error}`);
  2067. throw new Error;
  2068. }
  2069. }
  2070. /**
  2071. * 处理来自卡片的下一首命令
  2072. */
  2073. async onNextSong?(): Promise<void> {
  2074. try {
  2075. LogUtils.getInstance().LOGI('UnifiedPlayerService: Received next song command from widget');
  2076. await this.playNext();
  2077. LogUtils.getInstance().LOGI('UnifiedPlayerService: Next song command executed successfully');
  2078. } catch (error) {
  2079. LogUtils.getInstance().LOGI(`UnifiedPlayerService onNextSong error: ${error}`);
  2080. throw new Error;
  2081. }
  2082. }
  2083. /**
  2084. * 处理来自卡片的上一首命令
  2085. */
  2086. async onPreviousSong?(): Promise<void> {
  2087. try {
  2088. LogUtils.getInstance().LOGI('UnifiedPlayerService: Received previous song command from widget');
  2089. await this.playPrevious();
  2090. LogUtils.getInstance().LOGI('UnifiedPlayerService: Previous song command executed successfully');
  2091. } catch (error) {
  2092. LogUtils.getInstance().LOGI(`UnifiedPlayerService onPreviousSong error: ${error}`);
  2093. throw new Error;
  2094. }
  2095. }
  2096. /**
  2097. * 处理来自卡片的拖动进度命令
  2098. */
  2099. async onSeekTo?(position: number): Promise<void> {
  2100. try {
  2101. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Received seek to command from widget - position: ${position}`);
  2102. await this.seekTo(position.toString());
  2103. LogUtils.getInstance().LOGI('UnifiedPlayerService: Seek to command executed successfully');
  2104. } catch (error) {
  2105. LogUtils.getInstance().LOGI(`UnifiedPlayerService onSeekTo error: ${error}`);
  2106. throw new Error;
  2107. }
  2108. }
  2109. /**
  2110. * 处理来自卡片的状态请求命令
  2111. */
  2112. async onStateRequest?(): Promise<void> {
  2113. try {
  2114. LogUtils.getInstance().LOGI('UnifiedPlayerService: Received state request from widget');
  2115. await this.broadcastCurrentState();
  2116. LogUtils.getInstance().LOGI('UnifiedPlayerService: State request handled successfully');
  2117. } catch (error) {
  2118. LogUtils.getInstance().LOGI(`UnifiedPlayerService onStateRequest error: ${error}`);
  2119. throw new Error;
  2120. }
  2121. }
  2122. /**
  2123. * 手动广播当前状态(用于响应卡片的状态请求)
  2124. */
  2125. async broadcastCurrentState(): Promise<void> {
  2126. try {
  2127. // 确保播放列表状态是最新的
  2128. this.updatePlaylistStateInModelSync();
  2129. // 触发状态监听器通知
  2130. this.stateModel.forceNotifyStateChanged();
  2131. // 更新桌面卡片
  2132. await this.updateAllForms(true); // 强制更新
  2133. LogUtils.getInstance().LOGI('UnifiedPlayerService: Current state broadcasted manually');
  2134. } catch (error) {
  2135. LogUtils.getInstance().LOGI(`UnifiedPlayerService broadcastCurrentState error: ${error}`);
  2136. throw new Error;
  2137. }
  2138. }
  2139. // ==================== 私有辅助方法 ====================
  2140. /**
  2141. * 设置音频中断监听器
  2142. */
  2143. private setupAudioInterruptListener(): void {
  2144. try {
  2145. const ijkPlayer = this.playerManager.getIjkPlayer();
  2146. if (ijkPlayer) {
  2147. ijkPlayer.on('audioInterrupt', (event: InterruptEvent) => {
  2148. this.handleAudioInterrupt(event);
  2149. });
  2150. LogUtils.getInstance().LOGI('UnifiedPlayerService: Audio interrupt listener set up successfully');
  2151. } else {
  2152. LogUtils.getInstance()
  2153. .LOGI('UnifiedPlayerService: Failed to set up audio interrupt listener - no player instance');
  2154. }
  2155. } catch (error) {
  2156. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Error setting up audio interrupt listener: ${error.message}`);
  2157. }
  2158. }
  2159. /**
  2160. * 处理音频中断事件
  2161. */
  2162. async handleAudioInterrupt(event: InterruptEvent): Promise<void> {
  2163. try {
  2164. LogUtils.getInstance().LOGI(`onecold 与其他应用音频冲突 被截断点event: ${JSON.stringify(event)}`);
  2165. // 首先保存当前播放位置
  2166. await this.savePlaybackPosition();
  2167. switch (event.hintType) {
  2168. case InterruptHintType.INTERRUPT_HINT_PAUSE:
  2169. LogUtils.getInstance().LOGI('UnifiedPlayerService: Audio interrupt - pausing playback');
  2170. await this.pause();
  2171. break;
  2172. case InterruptHintType.INTERRUPT_HINT_RESUME:
  2173. LogUtils.getInstance().LOGI('UnifiedPlayerService: Audio interrupt - resuming playback');
  2174. await this.startPlayOrResumePlay();
  2175. break;
  2176. case InterruptHintType.INTERRUPT_HINT_STOP:
  2177. LogUtils.getInstance().LOGI('UnifiedPlayerService: Audio interrupt - stopping playback');
  2178. await this.stop();
  2179. break;
  2180. case InterruptHintType.INTERRUPT_HINT_DUCK:
  2181. LogUtils.getInstance().LOGI('UnifiedPlayerService: Audio interrupt - ducking (lowering volume)');
  2182. // 可以实现音量降低逻辑
  2183. break;
  2184. case InterruptHintType.INTERRUPT_HINT_UNDUCK:
  2185. LogUtils.getInstance().LOGI('UnifiedPlayerService: Audio interrupt - unducking (restoring volume)');
  2186. // 可以实现音量恢复逻辑
  2187. break;
  2188. default:
  2189. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Unknown audio interrupt hint type: ${event.hintType}`);
  2190. break;
  2191. }
  2192. // 通知状态变化
  2193. await this.updatePlaylistStateInModel();
  2194. await this.broadcastCurrentState();
  2195. } catch (error) {
  2196. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Error handling audio interrupt: ${error.message}`);
  2197. await this.handlePlaybackError(error as Error, PlayerErrorType.AUDIO_INTERRUPT_ERROR);
  2198. }
  2199. }
  2200. /**
  2201. * 更新状态模型中的播放列表状态
  2202. */
  2203. private async updatePlaylistStateInModel(): Promise<void> {
  2204. try {
  2205. const currentIndex = this.playlistModel.getCurrentIndex();
  2206. const totalCount = this.playlistModel.getTotalCount();
  2207. const playMode = this.stateModel.getState().playMode;
  2208. // 在随机播放模式下,确保当前歌曲在播放历史中
  2209. if (playMode === PlayMode.RANDOM) {
  2210. await this.playlistModel.ensureCurrentSongInHistory();
  2211. // 输出播放历史调试信息
  2212. const debugInfo = this.playlistModel.getPlayHistoryDebugInfo();
  2213. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Random mode play history - ${debugInfo}`);
  2214. }
  2215. const hasNext = this.playlistModel.hasNext(playMode);
  2216. const hasPrevious = await this.playlistModel.hasPrevious(playMode);
  2217. this.stateModel.updatePlaylistState(hasNext, hasPrevious, totalCount);
  2218. LogUtils.getInstance()
  2219. .LOGI(`UnifiedPlayerService: Playlist state updated - hasNext=${hasNext}, hasPrevious=${hasPrevious}, totalCount=${totalCount}, playMode=${playMode}`);
  2220. } catch (error) {
  2221. LogUtils.getInstance().LOGI(`UnifiedPlayerService updatePlaylistStateInModel error: ${error}`);
  2222. }
  2223. }
  2224. /**
  2225. * 更新状态模型中的播放列表状态(同步版本)
  2226. */
  2227. private updatePlaylistStateInModelSync(): void {
  2228. try {
  2229. const currentIndex = this.playlistModel.getCurrentIndex();
  2230. const totalCount = this.playlistModel.getTotalCount();
  2231. const playMode = this.stateModel.getState().playMode;
  2232. const hasNext = this.playlistModel.hasNext(playMode);
  2233. const hasPrevious = this.playlistModel.hasPreviousSync(playMode);
  2234. this.stateModel.updatePlaylistState(hasNext, hasPrevious, totalCount);
  2235. LogUtils.getInstance()
  2236. .LOGI(`UnifiedPlayerService: Playlist state updated sync - hasNext=${hasNext}, hasPrevious=${hasPrevious}, totalCount=${totalCount}, playMode=${playMode}`);
  2237. } catch (error) {
  2238. LogUtils.getInstance().LOGI(`UnifiedPlayerService updatePlaylistStateInModelSync error: ${error}`);
  2239. }
  2240. }
  2241. // ==================== PlayerStateCallback 实现 ====================
  2242. onPrepared(): void {
  2243. LogUtils.getInstance().LOGI('UnifiedPlayerService: Player prepared');
  2244. this.stateModel.updateLoadingState(false);
  2245. // 标记播放器已为当前歌曲准备就绪
  2246. this.isPlayerPreparedForCurrentSong = true;
  2247. console.log("Heanup2 UnifiedPlayerService: onPrepared - 播放器已为当前歌曲准备就绪");
  2248. setTimeout(() => {
  2249. const ijkPlayer = this.playerManager.getIjkPlayer();
  2250. const isActuallyPlaying = ijkPlayer ? ijkPlayer.isPlaying() : false;
  2251. console.log(`Heanup2 UnifiedPlayerService: onPrepared - 实际播放状态: ${isActuallyPlaying}`);
  2252. // 强制同步状态模型
  2253. this.stateModel.updatePlayingState(isActuallyPlaying);
  2254. // 确保播放列表状态是最新的
  2255. this.updatePlaylistStateInModelSync();
  2256. // 更新 AVSession 状态
  2257. if (isActuallyPlaying) {
  2258. let currentSong = this.getCurrentSong();
  2259. if (currentSong) {
  2260. console.log(`Heanup2 UnifiedPlayerService: onPrepared - updateAvSessionMetadata: ${json.stringify(currentSong)}`);
  2261. this.updateAvSessionMetadata(currentSong)
  2262. }
  2263. this.updateSessionPlayState(true); // 强制更新,不使用防抖
  2264. }
  2265. // 延迟更新卡片,确保状态已同步
  2266. setTimeout(() => {
  2267. this.updateWidgetsForStateChange();
  2268. }, 50);
  2269. }, 100);
  2270. // 播放器准备完成时立即更新卡片(移除加载状态)
  2271. this.updateWidgetsForStateChange();
  2272. }
  2273. onPlaybackStarted(): void {
  2274. LogUtils.getInstance().LOGI('UnifiedPlayerService: Playback started (new song)');
  2275. // 这个回调只应该在新歌曲开始播放时被调用(通过onPrepared触发)
  2276. // 不应该在暂停恢复时被调用
  2277. // 在随机播放模式下,确保当前歌曲在播放历史中
  2278. const playMode = this.stateModel.getState().playMode;
  2279. if (playMode === PlayMode.RANDOM) {
  2280. this.playlistModel.ensureCurrentSongInHistory();
  2281. LogUtils.getInstance().LOGI('UnifiedPlayerService: Ensured current song in play history for random mode');
  2282. }
  2283. // 立即更新播放状态
  2284. this.stateModel.updatePlayingState(true);
  2285. // 启动进度定时器
  2286. this.startProgressTimer();
  2287. // 保存播放状态变化
  2288. this.saveCurrentState();
  2289. // 立即更新系统播控状态,确保与播放状态同步
  2290. this.updateSessionPlayState(true); // 强制更新,不使用防抖
  2291. LogUtils.getInstance().LOGI('UnifiedPlayerService: AVSession state updated for new song playback');
  2292. // 播放开始时更新卡片显示播放状态
  2293. this.updateWidgetsForPlayStateChange();
  2294. // 同步更新所有桌面卡片状态
  2295. this.updateAllForms();
  2296. LogUtils.getInstance().LOGI('UnifiedPlayerService: New song playback started successfully');
  2297. }
  2298. onPlaybackCompleted(): void {
  2299. LogUtils.getInstance().LOGI('UnifiedPlayerService: Playback completed');
  2300. this.stateModel.updatePlayingState(false);
  2301. this.stopProgressTimer();
  2302. // 保存播放状态变化
  2303. this.saveCurrentState();
  2304. this.updateSessionPlayState();
  2305. // 更新卡片显示播放完成状态
  2306. this.updateWidgetsForPlayStateChange();
  2307. // 同步更新所有桌面卡片状态
  2308. this.updateAllForms();
  2309. // 异步处理自动播放逻辑,避免阻塞主线程
  2310. setTimeout(() => {
  2311. this.handleAutoPlayOnCompletion().catch(() => {
  2312. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Auto-play error`);
  2313. });
  2314. }, 100); // 延迟100ms执行,确保当前回调完成
  2315. }
  2316. /**
  2317. * 处理歌曲播放完成后的自动播放逻辑
  2318. */
  2319. private async handleAutoPlayOnCompletion(): Promise<void> {
  2320. try {
  2321. // 检查是否正在进行手动歌曲切换,如果是则跳过自动播放
  2322. if (this.isManualSongChange) {
  2323. LogUtils.getInstance().LOGI('UnifiedPlayerService: Manual song change in progress, skipping auto-play');
  2324. return;
  2325. }
  2326. // 防抖检查:避免快速连续的自动播放调用
  2327. const currentTime = Date.now();
  2328. if (currentTime - this.lastAutoPlayTime < this.autoPlayDebounceMs) {
  2329. LogUtils.getInstance().LOGI('UnifiedPlayerService: Auto-play debounced, skipping');
  2330. return;
  2331. }
  2332. this.lastAutoPlayTime = currentTime;
  2333. // 检查服务状态,避免在服务未就绪时执行自动播放
  2334. if (!this.isAllServicesReady()) {
  2335. LogUtils.getInstance().LOGI('UnifiedPlayerService: Services not ready for auto-play, skipping');
  2336. return;
  2337. }
  2338. const currentState = this.stateModel.getState();
  2339. const playMode = currentState.playMode;
  2340. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Handling auto-play, mode: ${playMode}`);
  2341. switch (playMode) {
  2342. case PlayMode.SEQUENCE: // 0: 顺序播放/列表循环
  2343. LogUtils.getInstance().LOGI('UnifiedPlayerService: Auto-playing next song in sequence mode');
  2344. // 检查是否有下一首歌曲
  2345. if (this.playlistModel.hasNext(playMode)) {
  2346. await this.playNext();
  2347. } else {
  2348. LogUtils.getInstance().LOGI('UnifiedPlayerService: No next song available in sequence mode');
  2349. }
  2350. break;
  2351. case PlayMode.SINGLE_REPEAT: // 1: 单曲循环
  2352. LogUtils.getInstance().LOGI('UnifiedPlayerService: Repeating current song');
  2353. // 重新播放当前歌曲
  2354. await this.startPlayOrResumePlay();
  2355. break;
  2356. case PlayMode.NORMAL: // 2: 正常播放,播完停止
  2357. LogUtils.getInstance().LOGI('UnifiedPlayerService: Normal mode - stopping after completion');
  2358. // 不做任何操作,保持停止状态
  2359. break;
  2360. case PlayMode.RANDOM: // 3: 随机播放
  2361. LogUtils.getInstance().LOGI('UnifiedPlayerService: Auto-playing random next song');
  2362. await this.playRandomNext();
  2363. break;
  2364. default:
  2365. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Unknown play mode: ${playMode}`);
  2366. break;
  2367. }
  2368. } catch (error) {
  2369. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Error in auto-play handling: ${error}`);
  2370. // 在自动播放失败时,简单记录错误但不触发复杂的错误处理流程
  2371. // 这样可以避免错误处理过程中的潜在阻塞
  2372. this.stateModel.updatePlayingState(false);
  2373. this.stateModel.updateLoadingState(false);
  2374. }
  2375. }
  2376. /**
  2377. * 随机播放下一首歌曲
  2378. */
  2379. private async playRandomNext(): Promise<void> {
  2380. try {
  2381. const playlist = this.playlistModel.getSongs();
  2382. if (playlist.length <= 1) {
  2383. LogUtils.getInstance().LOGI('UnifiedPlayerService: Not enough songs for random play');
  2384. return;
  2385. }
  2386. const currentIndex = this.playlistModel.getCurrentIndex();
  2387. let randomIndex: number;
  2388. // 确保随机选择的不是当前歌曲
  2389. do {
  2390. randomIndex = Math.floor(Math.random() * playlist.length);
  2391. } while (randomIndex === currentIndex);
  2392. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Playing random song at index ${randomIndex}`);
  2393. await this.playSongAtIndex(randomIndex);
  2394. } catch (error) {
  2395. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Error in random play: ${error}`);
  2396. // 不再抛出异常,而是通过错误处理机制处理
  2397. await this.handlePlaybackError(error as Error, PlayerErrorType.PLAYBACK_ERROR);
  2398. }
  2399. }
  2400. onPlaybackError(what: number, extra: number): void {
  2401. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Playback error - what: ${what}, extra: ${extra}`);
  2402. this.stateModel.updateLoadingState(false);
  2403. this.stateModel.updatePlayingState(false);
  2404. this.stopProgressTimer();
  2405. // 保存播放状态变化
  2406. this.saveCurrentState();
  2407. // 创建播放器错误对象
  2408. const playbackError = new PlayerError(
  2409. PlayerErrorType.PLAYBACK_ERROR,
  2410. `Playback error: what=${what}, extra=${extra}`,
  2411. what
  2412. );
  2413. playbackError.recoverable = what !== -1004 && what !== -1007; // 某些错误码可恢复
  2414. playbackError.retryCount = 0;
  2415. // 创建错误上下文
  2416. const errorContext: ErrorContext = {
  2417. currentSong: this.getCurrentSong(),
  2418. playlist: this.getPlaylist(),
  2419. currentIndex: this.getCurrentIndex(),
  2420. retryCount: 0,
  2421. isNetworkAvailable: true // 这里可以实际检查网络状态
  2422. };
  2423. // 使用错误恢复策略处理错误
  2424. this.errorRecovery.handleError(playbackError, errorContext).then((action: ErrorRecoveryAction) => {
  2425. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Error recovery action: ${action}`);
  2426. // 这里可以根据 action 执行相应的恢复操作
  2427. }).catch((recoveryError: Error) => {
  2428. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Error recovery failed: ${recoveryError}`);
  2429. });
  2430. }
  2431. // ========== 卡片更新相关方法 ==========
  2432. /**
  2433. * 播放状态变化时更新卡片
  2434. */
  2435. private async updateWidgetsForPlayStateChange(): Promise<void> {
  2436. try {
  2437. this.updateAllForms();
  2438. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Widgets updated for play state change`);
  2439. } catch (error) {
  2440. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Failed to update widgets for play state change: ${error}`);
  2441. }
  2442. }
  2443. /**
  2444. * 更新所有桌面卡片状态
  2445. * 将VideoItem和PlayerState数据传入桌面卡片,控制卡片的显示
  2446. */
  2447. async updateAllForms(forceUpdate: boolean = false) {
  2448. // 定义变量在方法开始处,确保在整个方法中都可以访问
  2449. try {
  2450. const now = Date.now();
  2451. this.isUpdatingForms = true;
  2452. this.lastUpdateTime = now;
  2453. // 从数据库获取所有Form信息
  2454. const context = AppStorage.get('context') as common.UIAbilityContext;
  2455. if (!context) {
  2456. LogUtils.getInstance().LOGI('UnifiedPlayerService updateAllForms: No context available');
  2457. return;
  2458. }
  2459. // 从数据库读取所有Form信息
  2460. let formInfoList: FormInfo[] = [];
  2461. try {
  2462. formInfoList = await FormRdbHelper.getInstance(context).queryAllForms();
  2463. LogUtils.getInstance().LOGI(`UnifiedPlayerService updateAllForms: Found ${formInfoList.length} forms in database`);
  2464. } catch (error) {
  2465. LogUtils.getInstance().LOGI(`UnifiedPlayerService updateAllForms: Failed to query forms from database: ${error}`);
  2466. return;
  2467. }
  2468. if (formInfoList.length === 0) {
  2469. LogUtils.getInstance().LOGI('UnifiedPlayerService updateAllForms: No forms found in database');
  2470. return;
  2471. }
  2472. // 记录更新统计
  2473. const invalidFormIds: string[] = [];
  2474. let flatWidgetData = await this.getWidgetFormData();
  2475. // 逐个更新所有卡片,根据卡片类型进行个性化处理
  2476. for (const formInfo of formInfoList) {
  2477. try {
  2478. const formBindingDataInstance = formBindingData.createFormBindingData(flatWidgetData);
  2479. await formProvider.updateForm(formInfo.formId, formBindingDataInstance);
  2480. LogUtils.getInstance().LOGI(`UnifiedPlayerService updateAllForms: Successfully updated form ${formInfo.formId} (${formInfo.formName})`);
  2481. } catch (error) {
  2482. const errorStr: string = error?.toString() || '';
  2483. invalidFormIds.push(formInfo.formId);
  2484. LogUtils.getInstance()
  2485. .LOGI(`UnifiedPlayerService updateAllForms: Form ${formInfo.formId} (${formInfo.formName}) - ${errorStr}}`);
  2486. }
  2487. }
  2488. // 自动清理无效的Form ID
  2489. if (invalidFormIds.length > 0) {
  2490. try {
  2491. for (const invalidFormId of invalidFormIds) {
  2492. await FormRdbHelper.getInstance(context).deleteForm(invalidFormId);
  2493. }
  2494. LogUtils.getInstance()
  2495. .LOGI(`UnifiedPlayerService updateAllForms: Removed ${invalidFormIds.length} invalid form IDs from database: ${invalidFormIds.join(', ')}`);
  2496. } catch (error) {
  2497. LogUtils.getInstance()
  2498. .LOGI(`UnifiedPlayerService updateAllForms: Failed to clean invalid form IDs from database: ${error}`);
  2499. }
  2500. }
  2501. } catch (error) {
  2502. LogUtils.getInstance().LOGI(`UnifiedPlayerService updateAllForms error: ${error}`);
  2503. } finally {
  2504. // 重置更新标志
  2505. this.isUpdatingForms = false;
  2506. }
  2507. }
  2508. public async getWidgetFormData(): Promise<widgeData | undefined> {
  2509. let imgMap: Record<string, number> = {};
  2510. this.updatePlaylistStateInModelSync();
  2511. const now = Date.now();
  2512. this.isUpdatingForms = true;
  2513. this.lastUpdateTime = now;
  2514. const formData: VideoItem = this.getCurrentSong() as VideoItem; // VideoItem
  2515. const songState: PlayerState = this.getCurrentState(); // PlayerState
  2516. // 从持久化存储获取所有formId - 使用新的getContext方法
  2517. let context = this.getContext();
  2518. // 如果还是没有context,尝试从AppStorage获取
  2519. if (!context) {
  2520. context = AppStorage.get('context') as common.UIAbilityContext;
  2521. }
  2522. // 构建卡片数据,包含VideoItem和PlayerState信息
  2523. const widgetPlayerState: WidgetPlayerState = {
  2524. isPlaying: songState.isPlaying || false,
  2525. isPaused: songState.isPaused || true,
  2526. isLoading: songState.isLoading || false,
  2527. currentPosition: songState.currentPosition || 0,
  2528. duration: songState.duration || 0,
  2529. hasNext: songState.hasNext || false,
  2530. hasPrevious: songState.hasPrevious || false,
  2531. playMode: songState.playMode || 0
  2532. };
  2533. const widgetPlaylistInfo: WidgetPlaylistInfo = {
  2534. currentIndex: this.getCurrentIndex(),
  2535. totalCount: this.getPlaylist().length
  2536. };
  2537. const widgetTimeInfo: WidgetTimeInfo = {
  2538. currentTimeText: this.formatTimeDisplay(Math.floor((songState.currentPosition || 0) / 1000)),
  2539. totalTimeText: this.formatTimeDisplay(Math.floor((songState.duration || 0) / 1000)),
  2540. progressPercentage: this.calculateProgressPercentage(songState.currentPosition || 0, songState.duration || 0)
  2541. };
  2542. if (!formData) {
  2543. console.log('UnifiedPlayerService getWidgetFormData: No formData available - formData:'+json.stringify(formData))
  2544. const defaultData: widgeData = {
  2545. id: '',
  2546. name: '未知歌曲',
  2547. artist: '未知艺术家',
  2548. album: '未知专辑',
  2549. pixelMapPath: '',
  2550. duration: 0,
  2551. filePath: '',
  2552. imgName: '',
  2553. imageColorHex: '2A2A2A',
  2554. isPlaying: false,
  2555. isPaused: true,
  2556. isLoading: false,
  2557. hasNext: false,
  2558. hasPrevious: false,
  2559. isFavorite: false,
  2560. currentPosition: 0,
  2561. playMode: 0,
  2562. currentIndex: 0,
  2563. totalCount: 0,
  2564. currentTimeText: '',
  2565. totalTimeText: '',
  2566. progressPercentage: 0,
  2567. playerState: widgetPlayerState,
  2568. playlistInfo: widgetPlaylistInfo,
  2569. timeInfo: widgetTimeInfo,
  2570. formImages: imgMap
  2571. }
  2572. return defaultData;
  2573. }
  2574. console.log('UnifiedPlayerService getWidgetFormData - context: '+json.stringify(context)+" - formData:"+json.stringify(formData))
  2575. // 处理封面图片
  2576. let imgName: string = '';
  2577. // 如果歌曲有封面图片路径,尝试处理图片
  2578. if (formData.pixelMapPath && formData.pixelMapPath.trim() !== '') {
  2579. try {
  2580. // 生成唯一的图片名称,确保每次更新时图片能正确刷新
  2581. imgName = 'songCover_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
  2582. // 判断是否为网络图片
  2583. if (formData.pixelMapPath.startsWith('http://') || formData.pixelMapPath.startsWith('https://')) {
  2584. // 处理网络图片:使用缓存机制
  2585. LogUtils.getInstance()
  2586. .LOGI(`UnifiedPlayerService updateAllForms: Processing network image ${formData.pixelMapPath}`);
  2587. if (this.imageCacheDir) {
  2588. const cacheFileName = this.generateCacheFileName(formData.pixelMapPath);
  2589. const cacheFilePath = this.imageCacheDir + '/' + cacheFileName;
  2590. // 检查缓存是否存在且有效
  2591. if (await this.isCacheValid(cacheFilePath)) {
  2592. // 使用缓存文件
  2593. LogUtils.getInstance().LOGI(`UnifiedPlayerService updateAllForms: Using cached image: ${cacheFilePath}`);
  2594. try {
  2595. const file = fileIo.openSync(cacheFilePath, fileIo.OpenMode.READ_ONLY);
  2596. imgMap[imgName] = file.fd;
  2597. LogUtils.getInstance()
  2598. .LOGI(`UnifiedPlayerService updateAllForms: Opened cached image with fd ${file.fd}, imgName: ${imgName}`);
  2599. } catch (error) {
  2600. LogUtils.getInstance()
  2601. .LOGI(`UnifiedPlayerService updateAllForms: Failed to open cached image: ${error}`);
  2602. }
  2603. } else {
  2604. // 下载到缓存
  2605. LogUtils.getInstance()
  2606. .LOGI(`UnifiedPlayerService updateAllForms: Downloading image to cache: ${cacheFilePath}`);
  2607. const downloadSuccess = await this.downloadImageToCache(formData.pixelMapPath, cacheFilePath);
  2608. if (downloadSuccess) {
  2609. try {
  2610. const file = fileIo.openSync(cacheFilePath, fileIo.OpenMode.READ_ONLY);
  2611. imgMap[imgName] = file.fd;
  2612. LogUtils.getInstance()
  2613. .LOGI(`UnifiedPlayerService updateAllForms: Opened downloaded image with fd ${file.fd}, imgName: ${imgName}`);
  2614. } catch (error) {
  2615. LogUtils.getInstance()
  2616. .LOGI(`UnifiedPlayerService updateAllForms: Failed to open downloaded image: ${error}`);
  2617. }
  2618. } else {
  2619. LogUtils.getInstance()
  2620. .LOGI(`UnifiedPlayerService updateAllForms: Failed to download network image ${formData.pixelMapPath}`);
  2621. }
  2622. }
  2623. } else {
  2624. LogUtils.getInstance().LOGI(`UnifiedPlayerService updateAllForms: Image cache directory not available`);
  2625. }
  2626. } else {
  2627. // 处理本地图片文件
  2628. let file = fileIo.openSync(formData.pixelMapPath, fileIo.OpenMode.READ_ONLY);
  2629. imgMap[imgName] = file.fd;
  2630. LogUtils.getInstance()
  2631. .LOGI(`UnifiedPlayerService updateAllForms: Opened local cover image ${formData.pixelMapPath} with fd ${file.fd}, imgName: ${imgName}`);
  2632. }
  2633. } catch (error) {
  2634. LogUtils.getInstance()
  2635. .LOGI(`UnifiedPlayerService updateAllForms: Failed to process cover image ${formData.pixelMapPath}: ${JSON.stringify(error as BusinessError)}`);
  2636. // 如果处理图片失败,使用空的imgMap
  2637. imgMap = {};
  2638. imgName = '';
  2639. }
  2640. } else {
  2641. LogUtils.getInstance().LOGI(`UnifiedPlayerService updateAllForms: No cover image path available`);
  2642. }
  2643. // 平铺数据结构,确保桌面卡片能正确接收
  2644. const widgetData: widgeData = {
  2645. // VideoItem数据平铺
  2646. id: formData.id || '',
  2647. name: formData.name || '',
  2648. artist: formData.artist || '',
  2649. album: formData.album || '',
  2650. pixelMapPath: formData.pixelMapPath || '',
  2651. duration: typeof formData.duration === 'string' ? parseInt(formData.duration || '0') : (formData.duration || 0),
  2652. filePath: formData.filePath || '',
  2653. imgName: imgName,
  2654. imageColorHex: '2A2A2A',
  2655. // PlayerState数据平铺
  2656. isPlaying: widgetPlayerState.isPlaying,
  2657. isPaused: widgetPlayerState.isPaused,
  2658. isLoading: widgetPlayerState.isLoading,
  2659. currentPosition: widgetPlayerState.currentPosition,
  2660. hasNext: widgetPlayerState.hasNext,
  2661. hasPrevious: widgetPlayerState.hasPrevious,
  2662. playMode: widgetPlayerState.playMode,
  2663. // 播放列表信息平铺
  2664. currentIndex: widgetPlaylistInfo.currentIndex,
  2665. totalCount: widgetPlaylistInfo.totalCount,
  2666. // 时间信息平铺
  2667. currentTimeText: widgetTimeInfo.currentTimeText,
  2668. totalTimeText: widgetTimeInfo.totalTimeText,
  2669. progressPercentage: widgetTimeInfo.progressPercentage,
  2670. // 保持嵌套结构兼容性
  2671. playerState: widgetPlayerState,
  2672. playlistInfo: widgetPlaylistInfo,
  2673. timeInfo: widgetTimeInfo,
  2674. // 卡片图片显示必填字段
  2675. formImages: imgMap,
  2676. isFavorite: this.getCurrentSongFavoriteState(),
  2677. };
  2678. return widgetData;
  2679. }
  2680. /**
  2681. * 计算播放进度百分比
  2682. */
  2683. private calculateProgressPercentage(current: number, total: number): number {
  2684. if (total <= 0) {
  2685. return 0;
  2686. }
  2687. return Math.min(100, Math.max(0, (current / total) * 100));
  2688. }
  2689. /**
  2690. * 格式化时间显示
  2691. */
  2692. private formatTimeDisplay(seconds: number): string {
  2693. const mins = Math.floor(seconds / 60);
  2694. const secs = Math.floor(seconds % 60);
  2695. return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
  2696. }
  2697. /**
  2698. * 状态变化时更新卡片
  2699. */
  2700. public async updateWidgetsForStateChange(): Promise<void> {
  2701. try {
  2702. await this.updateAllForms(true); // 强制更新,因为状态确实发生了变化
  2703. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Widgets updated for state change`);
  2704. } catch (error) {
  2705. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Failed to update widgets for state change: ${error}`);
  2706. }
  2707. }
  2708. /**
  2709. * 播放列表变化时更新卡片
  2710. */
  2711. private async updateWidgetsForPlaylistChange(): Promise<void> {
  2712. try {
  2713. this.updateAllForms();
  2714. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Widgets updated for playlist change`);
  2715. } catch (error) {
  2716. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Failed to update widgets for playlist change: ${error}`);
  2717. }
  2718. }
  2719. /**
  2720. * 应用启动时初始化卡片显示
  2721. */
  2722. public async initializeWidgetDisplay(): Promise<void> {
  2723. try {
  2724. this.updateAllForms();
  2725. LogUtils.getInstance().LOGI('UnifiedPlayerService: Widget display initialized');
  2726. } catch (error) {
  2727. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Failed to initialize widget display: ${error}`);
  2728. }
  2729. }
  2730. /**
  2731. * 解析时长字符串为毫秒数
  2732. */
  2733. private parseDuration(durationStr: string): number {
  2734. if (!durationStr || durationStr === '0') {
  2735. return 0;
  2736. }
  2737. try {
  2738. // 如果是纯数字,假设是秒数
  2739. const numValue = parseFloat(durationStr);
  2740. if (!isNaN(numValue)) {
  2741. const result = Math.floor(numValue * 1000); // 转换为毫秒
  2742. return result;
  2743. }
  2744. // 如果包含冒号,解析为 MM:SS 或 HH:MM:SS 格式
  2745. if (durationStr.includes(':')) {
  2746. const parts = durationStr.split(':').map(part => parseInt(part, 10));
  2747. let totalSeconds = 0;
  2748. if (parts.length === 2) {
  2749. // MM:SS 格式
  2750. totalSeconds = parts[0] * 60 + parts[1];
  2751. } else if (parts.length === 3) {
  2752. // HH:MM:SS 格式
  2753. totalSeconds = parts[0] * 3600 + parts[1] * 60 + parts[2];
  2754. }
  2755. const result = totalSeconds * 1000; // 转换为毫秒
  2756. return result;
  2757. }
  2758. return 0;
  2759. } catch (error) {
  2760. return 0;
  2761. }
  2762. }
  2763. // ==================== PlayerStateCallback 实现 ====================
  2764. /**
  2765. * 转换播放模式为AVSession循环模式
  2766. */
  2767. private convertPlayModeToLoopMode(playMode: number): avSession.LoopMode {
  2768. switch (playMode) {
  2769. case PlayMode.SINGLE_REPEAT:
  2770. return avSession.LoopMode.LOOP_MODE_SINGLE;
  2771. case PlayMode.NORMAL:
  2772. return avSession.LoopMode.LOOP_MODE_LIST;
  2773. case PlayMode.RANDOM:
  2774. return avSession.LoopMode.LOOP_MODE_SHUFFLE;
  2775. case PlayMode.SEQUENCE:
  2776. default:
  2777. return avSession.LoopMode.LOOP_MODE_SEQUENCE;
  2778. }
  2779. }
  2780. /**
  2781. * 更新AVSession播放状态(带防抖)
  2782. */
  2783. private updateSessionPlayState(force: boolean = false): void {
  2784. // 清除之前的定时器
  2785. if (this.avSessionUpdateTimer !== -1) {
  2786. clearTimeout(this.avSessionUpdateTimer);
  2787. }
  2788. if (force) {
  2789. // 强制更新,立即执行
  2790. this.doUpdateSessionPlayState();
  2791. } else {
  2792. // 增加防抖延迟到500ms,减少频繁更新
  2793. this.avSessionUpdateTimer = setTimeout(() => {
  2794. this.doUpdateSessionPlayState();
  2795. }, 500);
  2796. }
  2797. }
  2798. /**
  2799. * 实际执行AVSession播放状态更新
  2800. */
  2801. private doUpdateSessionPlayState(): void {
  2802. try {
  2803. if (!this.avSessionController) {
  2804. LogUtils.getInstance().LOGI('UnifiedPlayerService: AVSession controller not initialized');
  2805. return;
  2806. }
  2807. // 避免过于频繁的更新(最小间隔1000ms,增加防抖时间)
  2808. const now = Date.now();
  2809. if (now - this.lastAvSessionUpdate < 1000) {
  2810. LogUtils.getInstance().LOGI('UnifiedPlayerService: Skipping AVSession update due to rate limit');
  2811. return;
  2812. }
  2813. const currentSong = this.playlistModel.getCurrentSong();
  2814. const currentPosition = this.getCurrentPosition();
  2815. // 优先从播放器获取实际duration
  2816. let duration = 0;
  2817. const ijkPlayer = this.playerManager.getIjkPlayer();
  2818. if (ijkPlayer) {
  2819. duration = ijkPlayer.getDuration();
  2820. }
  2821. if (duration <= 0 && currentSong?.duration) {
  2822. duration = this.parseDuration(currentSong.duration);
  2823. }
  2824. const currentState = this.stateModel.getState();
  2825. // 按照官方文档要求设置完整的播放状态
  2826. const playbackState: avSession.AVPlaybackState = {
  2827. // 播放状态 - 修复:确保播放状态正确反映当前状态
  2828. state: currentState.isPlaying ? avSession.PlaybackState.PLAYBACK_STATE_PLAY :
  2829. avSession.PlaybackState.PLAYBACK_STATE_PAUSE,
  2830. // 播放速度
  2831. speed: currentState.speed || 1.0,
  2832. // 循环模式
  2833. loopMode: this.convertPlayModeToLoopMode(currentState.playMode),
  2834. // 收藏状态
  2835. isFavorite: this.getCurrentSongFavoriteState()
  2836. };
  2837. // 只有当duration > 0时才设置位置信息,避免进度条显示问题
  2838. if (duration > 0) {
  2839. playbackState.position = {
  2840. elapsedTime: Math.max(0, currentPosition), // 已播放时间(毫秒)
  2841. updateTime: Date.now() // 更新时间戳
  2842. };
  2843. playbackState.bufferedTime = Math.max(currentPosition, 0);
  2844. } else {
  2845. LogUtils.getInstance()
  2846. .LOGI(`UnifiedPlayerService: Skipping position info due to invalid duration: ${duration}ms`);
  2847. }
  2848. this.avSessionController.setAvSessionPlayState(playbackState);
  2849. // 记录上次更新时间
  2850. this.lastAvSessionUpdate = now;
  2851. } catch (error) {
  2852. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Failed to update AVSession play state: ${error}`);
  2853. }
  2854. }
  2855. /**
  2856. * 更新AVSession元数据(带防抖和智能更新)
  2857. */
  2858. private async updateAvSessionMetadata(song: VideoItem): Promise<void> {
  2859. // 清除之前的定时器
  2860. if (this.avMetadataUpdateTimer !== -1) {
  2861. clearTimeout(this.avMetadataUpdateTimer);
  2862. }
  2863. const now = Date.now();
  2864. if (now - this.lastAvSessionUpdateTime < 1000) {
  2865. return;
  2866. }
  2867. this.lastAvSessionUpdateTime = now;
  2868. // 如果播放器还没准备好,延迟更新等待播放器准备完成
  2869. const ijkPlayer = this.playerManager.getIjkPlayer();
  2870. const playerDuration = ijkPlayer ? ijkPlayer.getDuration() : 0;
  2871. const shouldWaitForPlayer = playerDuration <= 0 && this.isPlayerPreparedForCurrentSong === false;
  2872. this.avMetadataUpdateTimer = setTimeout(() => {
  2873. this.doUpdateAvSessionMetadata(song);
  2874. }, 800);
  2875. if (shouldWaitForPlayer) {
  2876. // 播放器还没准备好,延迟更新
  2877. this.avMetadataUpdateTimer = setTimeout(() => {
  2878. this.doUpdateAvSessionMetadata(song);
  2879. }, 800);
  2880. } else {
  2881. // 播放器已准备好或者不需要等待,稍微延迟更新
  2882. this.avMetadataUpdateTimer = setTimeout(() => {
  2883. this.doUpdateAvSessionMetadata(song);
  2884. }, 800);
  2885. }
  2886. }
  2887. /**
  2888. * 实际执行AVSession元数据更新
  2889. */
  2890. private async doUpdateAvSessionMetadata(song: VideoItem): Promise<void> {
  2891. try {
  2892. if (!this.avSessionController) {
  2893. LogUtils.getInstance().LOGI('UnifiedPlayerService: AVSession controller not initialized');
  2894. return;
  2895. }
  2896. // 避免过于频繁的元数据更新(最小间隔600ms)
  2897. const now = Date.now();
  2898. if (now - this.lastAvMetadataUpdate < 600) {
  2899. LogUtils.getInstance().LOGI('UnifiedPlayerService: Skipping AVSession metadata update due to rate limit');
  2900. return;
  2901. }
  2902. // 调试:打印VideoItem的duration字段
  2903. LogUtils.getInstance()
  2904. .LOGI(`UnifiedPlayerService: VideoItem duration field - value: '${song.duration}', type: ${typeof song.duration}`);
  2905. // 智能获取最准确的duration
  2906. let duration = 0;
  2907. try {
  2908. const ijkPlayer = this.playerManager.getIjkPlayer();
  2909. if (ijkPlayer && this.isPlayerPreparedForCurrentSong) {
  2910. const playerDuration = ijkPlayer.getDuration();
  2911. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Player getDuration() returned: ${playerDuration}ms`);
  2912. if (playerDuration > 0) {
  2913. duration = playerDuration;
  2914. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Using player duration: ${duration}ms`);
  2915. } else {
  2916. // 备用方案:解析VideoItem中的duration
  2917. duration = song.duration ? this.parseDuration(song.duration) : 0;
  2918. LogUtils.getInstance()
  2919. .LOGI(`UnifiedPlayerService: Player duration invalid, using parsed duration: ${duration}ms from '${song.duration}'`);
  2920. }
  2921. } else {
  2922. duration = song.duration ? this.parseDuration(song.duration) : 0;
  2923. LogUtils.getInstance()
  2924. .LOGI(`UnifiedPlayerService: Player not ready, using parsed duration: ${duration}ms from '${song.duration}'`);
  2925. }
  2926. } catch (error) {
  2927. duration = song.duration ? this.parseDuration(song.duration) : 0;
  2928. LogUtils.getInstance()
  2929. .LOGI(`UnifiedPlayerService: Error getting player duration, using parsed: ${duration}ms from '${song.duration}', error: ${error}`);
  2930. }
  2931. // 最终验证duration
  2932. if (isNaN(duration) || duration <= 0) {
  2933. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Final duration is invalid (${duration}), setting to 0`);
  2934. duration = 0;
  2935. }
  2936. // 获取歌词内容(如果有的话)
  2937. const lyricContent = song.lyricContent || '';
  2938. LogUtils.getInstance()
  2939. .LOGI(`UnifiedPlayerService: About to call setAVMetadataMusic with duration: ${duration}ms (type: ${typeof duration})`);
  2940. await this.avSessionController.setAVMetadataMusic(song, duration, lyricContent);
  2941. // 记录更新时间
  2942. this.lastAvMetadataUpdate = now;
  2943. LogUtils.getInstance()
  2944. .LOGI(`UnifiedPlayerService: AVSession metadata updated for ${song.name} with duration ${duration}ms`);
  2945. } catch (error) {
  2946. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Failed to update AVSession metadata: ${error}`);
  2947. }
  2948. }
  2949. // ==================== 辅助方法 ====================
  2950. /**
  2951. * 初始化图片缓存目录
  2952. */
  2953. private async initImageCacheDirectory(): Promise<void> {
  2954. try {
  2955. if (this.context) {
  2956. const cacheDir = this.context.getApplicationContext().cacheDir;
  2957. this.imageCacheDir = cacheDir + '/cover_images';
  2958. // 创建缓存目录
  2959. if (!fileIo.accessSync(this.imageCacheDir)) {
  2960. fileIo.mkdirSync(this.imageCacheDir);
  2961. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Created image cache directory: ${this.imageCacheDir}`);
  2962. }
  2963. // 清理过期缓存
  2964. await this.cleanExpiredCache();
  2965. }
  2966. } catch (error) {
  2967. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Failed to initialize image cache directory: ${error}`);
  2968. }
  2969. }
  2970. /**
  2971. * 生成图片URL的哈希值作为缓存文件名
  2972. */
  2973. private generateCacheFileName(url: string): string {
  2974. try {
  2975. const textEncoder = new util.TextEncoder();
  2976. const data = textEncoder.encodeInto(url);
  2977. // 使用URL的简单哈希算法
  2978. let hash = 0;
  2979. for (let i = 0; i < data.byteLength; i++) {
  2980. const char = data[i];
  2981. hash = ((hash << 5) - hash) + char;
  2982. hash = hash & hash; // 转换为32位整数
  2983. }
  2984. return Math.abs(hash).toString(16) + '.jpg';
  2985. } catch (error) {
  2986. // 如果哈希生成失败,使用URL的简单替换方案
  2987. return url.replace(/[^a-zA-Z0-9]/g, '_').substring(0, 50) + '.jpg';
  2988. }
  2989. }
  2990. /**
  2991. * 检查缓存文件是否存在且未过期
  2992. */
  2993. private async isCacheValid(cacheFilePath: string): Promise<boolean> {
  2994. try {
  2995. if (!fileIo.accessSync(cacheFilePath)) {
  2996. return false;
  2997. }
  2998. const stat = fileIo.statSync(cacheFilePath);
  2999. const fileAge = Date.now() - stat.mtime;
  3000. const maxAge = this.CACHE_EXPIRY_DAYS * 24 * 60 * 60 * 1000; // 转换为毫秒
  3001. return fileAge < maxAge;
  3002. } catch (error) {
  3003. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Failed to check cache validity: ${error}`);
  3004. return false;
  3005. }
  3006. }
  3007. /**
  3008. * 下载网络图片到缓存
  3009. */
  3010. private async downloadImageToCache(url: string, cacheFilePath: string): Promise<boolean> {
  3011. try {
  3012. const httpRequest = http.createHttp();
  3013. try {
  3014. const response = await httpRequest.request(url, {
  3015. method: http.RequestMethod.GET,
  3016. connectTimeout: 10000, // 10秒超时
  3017. readTimeout: 10000
  3018. });
  3019. if (response.responseCode === http.ResponseCode.OK && response.result) {
  3020. const tempFile = fileIo.openSync(cacheFilePath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
  3021. try {
  3022. await fileIo.write(tempFile.fd, response.result as ArrayBuffer);
  3023. fileIo.closeSync(tempFile.fd);
  3024. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Downloaded image to cache: ${cacheFilePath}`);
  3025. return true;
  3026. } catch (writeError) {
  3027. fileIo.closeSync(tempFile.fd);
  3028. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Write error: ${writeError}`);
  3029. return false;
  3030. }
  3031. } else {
  3032. LogUtils.getInstance()
  3033. .LOGI(`UnifiedPlayerService: Failed to download image ${url}: HTTP ${response.responseCode}`);
  3034. return false;
  3035. }
  3036. } finally {
  3037. httpRequest.destroy();
  3038. }
  3039. } catch (error) {
  3040. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Error downloading image to cache: ${error}`);
  3041. return false;
  3042. }
  3043. }
  3044. /**
  3045. * 清理过期缓存
  3046. */
  3047. private async cleanExpiredCache(): Promise<void> {
  3048. try {
  3049. if (!this.imageCacheDir || !fileIo.accessSync(this.imageCacheDir)) {
  3050. return;
  3051. }
  3052. const files = fileIo.listFileSync(this.imageCacheDir);
  3053. const maxAge = this.CACHE_EXPIRY_DAYS * 24 * 60 * 60 * 1000;
  3054. let totalSize = 0;
  3055. const fileInfos: Array<CacheFileInfo> = [];
  3056. // 收集文件信息
  3057. for (const file of files) {
  3058. const filePath = this.imageCacheDir + '/' + file;
  3059. try {
  3060. const stat = fileIo.statSync(filePath);
  3061. totalSize += stat.size;
  3062. const fileInfo: CacheFileInfo = {
  3063. path: filePath,
  3064. size: stat.size,
  3065. mtime: stat.mtime
  3066. };
  3067. fileInfos.push(fileInfo);
  3068. } catch (error) {
  3069. // 忽略无法读取的文件
  3070. }
  3071. }
  3072. // 删除过期文件
  3073. const now = Date.now();
  3074. for (const fileInfo of fileInfos) {
  3075. if (now - fileInfo.mtime > maxAge) {
  3076. try {
  3077. fileIo.unlinkSync(fileInfo.path);
  3078. totalSize -= fileInfo.size;
  3079. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Deleted expired cache file: ${fileInfo.path}`);
  3080. } catch (error) {
  3081. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Failed to delete expired cache file: ${error}`);
  3082. }
  3083. }
  3084. }
  3085. // 如果缓存大小超限,删除最旧的文件
  3086. const maxSizeBytes = this.MAX_CACHE_SIZE_MB * 1024 * 1024;
  3087. if (totalSize > maxSizeBytes) {
  3088. const sortedFiles = fileInfos
  3089. .filter(f => fileIo.accessSync(f.path))// 只保留仍存在的文件
  3090. .sort((a, b) => a.mtime - b.mtime); // 按修改时间排序,最旧的在前
  3091. for (const fileInfo of sortedFiles) {
  3092. if (totalSize <= maxSizeBytes) {
  3093. break;
  3094. }
  3095. try {
  3096. fileIo.unlinkSync(fileInfo.path);
  3097. totalSize -= fileInfo.size;
  3098. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Deleted cache file due to size limit: ${fileInfo.path}`);
  3099. } catch (error) {
  3100. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Failed to delete cache file: ${error}`);
  3101. }
  3102. }
  3103. }
  3104. LogUtils.getInstance()
  3105. .LOGI(`UnifiedPlayerService: Cache cleanup completed, total size: ${(totalSize / 1024 / 1024).toFixed(2)}MB`);
  3106. } catch (error) {
  3107. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Failed to clean expired cache: ${error}`);
  3108. }
  3109. }
  3110. /**
  3111. * 清理无效的Form ID
  3112. * 应用启动时调用,验证所有保存的Form ID是否仍然有效
  3113. */
  3114. private async cleanupInvalidFormIds(): Promise<void> {
  3115. try {
  3116. if (!this.context) {
  3117. LogUtils.getInstance().LOGI('UnifiedPlayerService: No context for cleanup invalid form IDs');
  3118. return;
  3119. }
  3120. // 从数据库读取所有Form信息
  3121. let formInfoList: FormInfo[] = [];
  3122. try {
  3123. formInfoList = await FormRdbHelper.getInstance(this.context).queryAllForms();
  3124. LogUtils.getInstance().LOGI(`UnifiedPlayerService cleanupInvalidFormIds: Found ${formInfoList.length} forms in database`);
  3125. } catch (error) {
  3126. LogUtils.getInstance().LOGI(`UnifiedPlayerService cleanupInvalidFormIds: Failed to query forms from database: ${error}`);
  3127. return;
  3128. }
  3129. if (formInfoList.length === 0) {
  3130. LogUtils.getInstance().LOGI('UnifiedPlayerService cleanupInvalidFormIds: No forms found in database');
  3131. return;
  3132. }
  3133. // 记录更新统计
  3134. const invalidFormIds: string[] = [];
  3135. let flatWidgetData = await this.getWidgetFormData();
  3136. // 逐个更新所有卡片,根据卡片类型进行个性化处理
  3137. for (const formInfo of formInfoList) {
  3138. try {
  3139. const formBindingDataInstance = formBindingData.createFormBindingData(flatWidgetData);
  3140. await formProvider.updateForm(formInfo.formId, formBindingDataInstance);
  3141. LogUtils.getInstance().LOGI(`UnifiedPlayerService cleanupInvalidFormIds: Successfully updated form ${formInfo.formId} (${formInfo.formName})`);
  3142. } catch (error) {
  3143. const errorStr: string = error?.toString() || '';
  3144. invalidFormIds.push(formInfo.formId);
  3145. LogUtils.getInstance()
  3146. .LOGI(`UnifiedPlayerService cleanupInvalidFormIds: Form ${formInfo.formId} (${formInfo.formName}) - ${errorStr}}`);
  3147. }
  3148. }
  3149. // 自动清理无效的Form ID
  3150. if (invalidFormIds.length > 0) {
  3151. try {
  3152. for (const invalidFormId of invalidFormIds) {
  3153. await FormRdbHelper.getInstance(this.context).deleteForm(invalidFormId);
  3154. }
  3155. LogUtils.getInstance()
  3156. .LOGI(`UnifiedPlayerService cleanupInvalidFormIds: Removed ${invalidFormIds.length} invalid form IDs from database: ${invalidFormIds.join(', ')}`);
  3157. } catch (error) {
  3158. LogUtils.getInstance()
  3159. .LOGI(`UnifiedPlayerService cleanupInvalidFormIds: Failed to clean invalid form IDs from database: ${error}`);
  3160. }
  3161. }
  3162. // const preferencesUtil = PreferencesUtil.getInstance();
  3163. // const prefs = await preferencesUtil.getPreferences(this.context);
  3164. // const formIds = await preferencesUtil.getFormIds(prefs);
  3165. //
  3166. // if (formIds.length === 0) {
  3167. // LogUtils.getInstance().LOGI('UnifiedPlayerService: No form IDs to cleanup');
  3168. // return;
  3169. // }
  3170. //
  3171. // const invalidFormIds: string[] = [];
  3172. //
  3173. // // 逐个验证Form ID的有效性
  3174. // for (const formId of formIds) {
  3175. // try {
  3176. // // 尝试更新卡片来验证其有效性
  3177. // const flatWidgetData = await this.getWidgetFormData();
  3178. // if (flatWidgetData) {
  3179. // const formBindingDataInstance = formBindingData.createFormBindingData(flatWidgetData);
  3180. // await formProvider.updateForm(formId, formBindingDataInstance);
  3181. // }
  3182. // } catch (error) {
  3183. // // 如果更新失败,说明这个Form ID无效
  3184. // invalidFormIds.push(formId);
  3185. // LogUtils.getInstance().LOGI(`UnifiedPlayerService: Invalid form ID detected: ${formId}, error: ${error}`);
  3186. // }
  3187. // }
  3188. //
  3189. // // 移除无效的Form ID
  3190. // if (invalidFormIds.length > 0) {
  3191. //
  3192. //
  3193. // LogUtils.getInstance().LOGI(`UnifiedPlayerService: Cleaned up ${invalidFormIds.length} invalid form IDs: ${invalidFormIds.join(', ')}`);
  3194. // } else {
  3195. // LogUtils.getInstance().LOGI(`UnifiedPlayerService: All ${formIds.length} form IDs are valid`);
  3196. // }
  3197. } catch (error) {
  3198. LogUtils.getInstance().LOGI(`UnifiedPlayerService: Failed to cleanup invalid form IDs: ${error}`);
  3199. }
  3200. }
  3201. }