PlayerWidgetRectangle.ets 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. /**
  2. * 矩形封面播放器卡片 (2x4)
  3. * 显示专辑封面、歌曲信息和播放控制
  4. */
  5. // 定义对齐规则
  6. const RectangleSingerAlignRules: Record<string, Record<string, string | VerticalAlign | HorizontalAlign>> = {
  7. 'top': { 'anchor': 'musicTitle', 'align': VerticalAlign.Bottom },
  8. 'left': { 'anchor': 'musicCover', 'align': HorizontalAlign.End }
  9. };
  10. const RectangleCoverAlignRules: Record<string, Record<string, string | VerticalAlign | HorizontalAlign>> = {
  11. 'left': { 'anchor': '__container__', 'align': HorizontalAlign.Start },
  12. 'top': { 'anchor': '__container__', 'align': VerticalAlign.Top }
  13. };
  14. const RectangleTitleAlignRules: Record<string, Record<string, string | VerticalAlign | HorizontalAlign>> = {
  15. 'left': { 'anchor': 'musicCover', 'align': HorizontalAlign.End },
  16. 'top': { 'anchor': '__container__', 'align': VerticalAlign.Top }
  17. };
  18. const RectanglePlayControlAlignRules: Record<string, Record<string, string | VerticalAlign | HorizontalAlign>> = {
  19. 'bottom': { 'anchor': '__container__', 'align': VerticalAlign.Bottom },
  20. 'left': { 'anchor': '__container__', 'align': HorizontalAlign.Start },
  21. 'right': { 'anchor': '__container__', 'align': HorizontalAlign.End }
  22. };
  23. @Entry
  24. @Component
  25. struct PlayerWidgetRectangle {
  26. // 卡片数据属性 - 适配新的数据结构
  27. @LocalStorageProp('formId') formId: string = '202504'; // New formId for this card
  28. // VideoItem 主要数据
  29. @LocalStorageProp('id') songId: string = '';
  30. @LocalStorageProp('name') songTitle: string = 'Dream It Possible';
  31. @LocalStorageProp('artist') songArtist: string = 'Delacey';
  32. @LocalStorageProp('album') songAlbum: string = '未知专辑';
  33. @LocalStorageProp('pixelMapPath') coverImage: string = '';
  34. @LocalStorageProp('duration') songDuration: number = 0;
  35. @LocalStorageProp('filePath') songFilePath: string = '';
  36. // PlayerState 播放状态数据
  37. @LocalStorageProp('playerState') playerState: Record<string, Object> = {};
  38. // 平铺状态数据(优先使用)
  39. @LocalStorageProp('isPlaying') isPlaying: boolean = false;
  40. @LocalStorageProp('isPaused') isPaused: boolean = true;
  41. @LocalStorageProp('isLoading') isLoading: boolean = false;
  42. @LocalStorageProp('hasNext') hasNext: boolean = false;
  43. @LocalStorageProp('hasPrevious') hasPrevious: boolean = false;
  44. // 播放列表信息
  45. @LocalStorageProp('playlistInfo') playlistInfo: Record<string, Object> = {};
  46. @LocalStorageProp('currentIndex') currentIndex: number = 0;
  47. @LocalStorageProp('totalCount') totalCount: number = 0;
  48. // 时间信息
  49. @LocalStorageProp('timeInfo') timeInfo: Record<string, Object> = {};
  50. @LocalStorageProp('currentTimeText') currentTimeText: string = '00:00';
  51. @LocalStorageProp('totalTimeText') totalTimeText: string = '00:00';
  52. @LocalStorageProp('progressPercentage') progressPercentage: number = 0;
  53. // 兼容性属性(用于获取复合状态)
  54. @LocalStorageProp('imgName') imgName: string = ''; // 图片文件名,用于memory://协议
  55. @LocalStorageProp('isFavorite') isFavorite: boolean = false;
  56. /**
  57. * 格式化歌曲标题显示
  58. */
  59. private getDisplayTitle(): string {
  60. if (!this.songTitle || this.songTitle.trim() === '') {
  61. return '暂无播放';
  62. }
  63. return this.songTitle;
  64. }
  65. /**
  66. * 格式化艺术家显示
  67. */
  68. private getDisplayArtist(): string {
  69. if (!this.songArtist || this.songArtist.trim() === '') {
  70. return '未知艺术家';
  71. }
  72. return this.songArtist;
  73. }
  74. /**
  75. * 检查是否为网络URL
  76. */
  77. private isNetworkUrl(url: string): boolean {
  78. return url.startsWith('http://') || url.startsWith('https://');
  79. }
  80. /**
  81. * 获取专辑封面
  82. */
  83. private getCoverImage(): Resource | string {
  84. console.info(`Heanup PlayerWidgetRectangle: getCoverImage called, coverImage='${this.coverImage}', imgName='${this.imgName}'`);
  85. // 优先使用通过formImages传递的本地图片(支持网络图片下载后的显示)
  86. if (this.imgName && this.imgName.trim() !== '') {
  87. const memoryUrl = 'memory://' + this.imgName;
  88. console.info(`Heanup PlayerWidgetRectangle: Using memory image: ${memoryUrl}`);
  89. return memoryUrl;
  90. }
  91. // 如果有coverImage且不是网络URL,使用本地路径
  92. if (this.coverImage && this.coverImage.trim() !== '' && !this.isNetworkUrl(this.coverImage)) {
  93. console.info(`Heanup PlayerWidgetRectangle: Using local cover image: ${this.coverImage}`);
  94. return this.coverImage;
  95. }
  96. // 默认使用内置图片
  97. console.info(`Heanup PlayerWidgetRectangle: Using default cover image`);
  98. return $r('app.media.ic_avatar4'); // 使用默认专辑封面
  99. }
  100. build() {
  101. Stack() {
  102. // 背景和主要内容
  103. RelativeContainer() {
  104. // 歌曲标题
  105. Text(this.getDisplayTitle())
  106. .fontSize(16)
  107. .fontWeight(FontWeight.Bold)
  108. .width('60%')
  109. .fontColor(Color.White)
  110. .textOverflow({ overflow: TextOverflow.Ellipsis })
  111. .maxLines(1)
  112. .alignRules(RectangleTitleAlignRules)
  113. .margin({ left: 16, top: 8 })
  114. .id('musicTitle')
  115. // 艺术家名称
  116. Text(this.getDisplayArtist())
  117. .fontSize(12)
  118. .fontColor('#CCFFFFFF')
  119. .fontWeight(FontWeight.Normal)
  120. .maxLines(1)
  121. .alignRules(RectangleSingerAlignRules)
  122. .margin({ left: 16, top: 2 })
  123. .id('singerText')
  124. // 专辑封面
  125. Image(this.getCoverImage())
  126. .width(88)
  127. .height(88)
  128. .borderRadius(8) // Square cover with rounded corners
  129. .alignRules(RectangleCoverAlignRules)
  130. .id('musicCover')
  131. .onClick(() => {
  132. postCardAction(this, {
  133. 'action': 'router',
  134. 'abilityName': 'EntryAbility'
  135. });
  136. })
  137. // 播放控制按钮区域
  138. Row() {
  139. // 上一首按钮
  140. Button() {
  141. SymbolGlyph($r('sys.symbol.backward_end_fill'))
  142. .fontSize(36)
  143. .fontColor(['#E5FFFFFF'])
  144. }
  145. .width(40)
  146. .height(40)
  147. .backgroundColor(Color.Transparent)
  148. .opacity(this.hasPrevious ? 1.0 : 0.5)
  149. .onClick(() => {
  150. console.info(`Heanup PlayerWidgetRectangle: Previous button clicked`);
  151. if (!this.isLoading && this.hasPrevious) {
  152. postCardAction(this, {
  153. 'action': 'call',
  154. 'abilityName': 'EntryAbility',
  155. 'params': {
  156. 'formId': this.formId,
  157. 'method': 'prevSong'
  158. }
  159. });
  160. }
  161. })
  162. // 播放/暂停按钮
  163. Button() {
  164. SymbolGlyph(this.isPlaying ? $r('sys.symbol.pause_round_triangle_fill') : $r('sys.symbol.play_round_triangle_fill'))
  165. .fontSize(36)
  166. .symbolEffect(new ReplaceSymbolEffect(EffectScope.WHOLE), true)
  167. .fontColor(['#E5FFFFFF'])
  168. }
  169. .width(48)
  170. .height(48)
  171. .backgroundColor(Color.Transparent)
  172. .margin({ left: 20, right: 20 })
  173. .onClick(() => {
  174. console.info('Heanup PlayerWidgetRectangle: Play/Pause button clicked');
  175. if (!this.isLoading) {
  176. postCardAction(this, {
  177. 'action': 'call',
  178. 'abilityName': 'EntryAbility',
  179. 'params': {
  180. 'formId': this.formId,
  181. 'method': 'playPause',
  182. 'widgetIsPlaying': this.isPlaying // 传递卡片当前显示的播放状态
  183. }
  184. });
  185. }
  186. })
  187. // 下一首按钮
  188. Button() {
  189. SymbolGlyph($r('sys.symbol.forward_end_fill'))
  190. .fontSize(36)
  191. .fontColor(['#E5FFFFFF'])
  192. }
  193. .width(40)
  194. .height(40)
  195. .backgroundColor(Color.Transparent)
  196. .opacity(this.hasNext ? 1.0 : 0.5)
  197. .onClick(() => {
  198. console.info(`Heanup PlayerWidgetRectangle: Next button clicked`);
  199. if (!this.isLoading && this.hasNext) {
  200. postCardAction(this, {
  201. 'action': 'call',
  202. 'abilityName': 'EntryAbility',
  203. 'params': {
  204. 'formId': this.formId,
  205. 'method': 'nextSong'
  206. }
  207. });
  208. }
  209. })
  210. }
  211. .width('100%')
  212. .justifyContent(FlexAlign.Center)
  213. .alignRules(RectanglePlayControlAlignRules)
  214. .id('playControls')
  215. }
  216. .height('100%')
  217. .width('100%')
  218. .padding(12)
  219. .backgroundImage(this.getCoverImage())
  220. .backgroundBlurStyle(BlurStyle.BACKGROUND_ULTRA_THICK)
  221. .backgroundImageSize(ImageSize.Cover)
  222. .onClick(() => {
  223. console.info('Heanup PlayerWidgetRectangle: Container clicked, jumping to main app');
  224. postCardAction(this, {
  225. 'action': 'router',
  226. 'abilityName': 'EntryAbility'
  227. });
  228. })
  229. // 收藏按钮放在Stack顶层,确保可以正常点击
  230. Button() {
  231. SymbolGlyph(this.isFavorite ? $r('sys.symbol.heart_fill') : $r('sys.symbol.heart'))
  232. .fontSize(24)
  233. .symbolEffect(new ReplaceSymbolEffect(EffectScope.WHOLE), true)
  234. .fontColor(['#E5FFFFFF'])
  235. }
  236. .width(40)
  237. .height(40)
  238. .backgroundColor(Color.Transparent)
  239. .onClick(() => {
  240. console.info(`Heanup PlayerWidgetRectangle: Favorite button clicked`);
  241. postCardAction(this, {
  242. 'action': 'call',
  243. 'abilityName': 'EntryAbility',
  244. 'params': {
  245. 'formId': this.formId,
  246. 'method': 'toggleFavorite'
  247. }
  248. });
  249. })
  250. .position({
  251. x: '100%',
  252. y: 0
  253. })
  254. .translate({ x: -52, y: 12 }) // 调整到右上角位置
  255. }
  256. .height('100%')
  257. .width('100%')
  258. }
  259. }