UnifiedPlayerService.ets 155 KB

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