MusicPlayerWidgetCard.ets 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. import { MusicCardActionConstants } from '../../common/player/MusicCardActionConstants'
  2. import Logger from '../../common/util/Logger'
  3. import {
  4. clampMusicCardWidgetProgress,
  5. resolveMusicCardWidgetCoverSource
  6. } from '../common/MusicPlayerWidgetHelper'
  7. const cardStorage: LocalStorage = new LocalStorage()
  8. const ENTRY_ABILITY_NAME = 'EntryAbility'
  9. const TAG = 'MusicPlayerWidgetCard'
  10. @Entry(cardStorage)
  11. @Component
  12. struct MusicPlayerWidgetCard {
  13. @LocalStorageProp('formId') formId: string = ''
  14. @LocalStorageProp('title') title: string = '未在播放'
  15. @LocalStorageProp('artist') artist: string = ''
  16. @LocalStorageProp('coverPath') coverPath: string = ''
  17. @LocalStorageProp('coverImageName') coverImageName: string = ''
  18. @LocalStorageProp('hasCoverImage') hasCoverImage: boolean = false
  19. @LocalStorageProp('hasSong') hasSong: boolean = false
  20. @LocalStorageProp('isPlaying') isPlaying: boolean = false
  21. @LocalStorageProp('currentPositionMs') @Watch('syncSliderFromPlayback') currentPositionMs: number = 0
  22. @LocalStorageProp('durationMs') @Watch('syncSliderFromPlayback') durationMs: number = 0
  23. @LocalStorageProp('currentTimeText') currentTimeText: string = '00:00'
  24. @LocalStorageProp('durationTimeText') durationTimeText: string = '00:00'
  25. @State isChangeBg: boolean = true
  26. @State sliderProgressMs: number = 0
  27. aboutToAppear(): void {
  28. Logger.info(TAG,
  29. `musicCard small aboutToAppear formId=${this.formId}, title=${this.title}, artist=${this.artist}, ` +
  30. `coverImageName=${this.coverImageName}, coverPath=${this.coverPath}, hasCoverImage=${this.hasCoverImage}, ` +
  31. `hasSong=${this.hasSong}, isPlaying=${this.isPlaying}, currentPositionMs=${this.currentPositionMs}, ` +
  32. `durationMs=${this.durationMs}`)
  33. this.syncSliderFromPlayback()
  34. }
  35. private syncSliderFromPlayback(): void {
  36. this.sliderProgressMs = clampMusicCardWidgetProgress(this.currentPositionMs, this.durationMs)
  37. }
  38. private postControlAction(action: string): void {
  39. Logger.info(TAG, `musicCard small postControlAction formId=${this.formId}, action=${action}`)
  40. postCardAction(this, {
  41. action: 'call',
  42. abilityName: ENTRY_ABILITY_NAME,
  43. params: {
  44. method: MusicCardActionConstants.CALL_METHOD_HANDLE_ACTION,
  45. ttmusic_music_card_form_id: this.formId,
  46. ttmusic_music_card_action: action,
  47. ttmusic_music_card_source: MusicCardActionConstants.ACTION_SOURCE_WIDGET
  48. }
  49. })
  50. }
  51. private postRouterAction(action: string): void {
  52. Logger.info(TAG, `musicCard small postRouterAction formId=${this.formId}, action=${action}`)
  53. postCardAction(this, {
  54. action: 'router',
  55. abilityName: ENTRY_ABILITY_NAME,
  56. params: {
  57. ttmusic_music_card_form_id: this.formId,
  58. ttmusic_music_card_action: action,
  59. ttmusic_music_card_source: MusicCardActionConstants.ACTION_SOURCE_WIDGET
  60. }
  61. })
  62. }
  63. private resolveCoverSource(): string | Resource {
  64. return resolveMusicCardWidgetCoverSource(this.coverImageName, this.coverPath, this.hasCoverImage)
  65. }
  66. private resolveProgressValue(): number {
  67. if (this.durationMs <= 0) {
  68. return 0
  69. }
  70. return Math.min(100, Math.max(0, Math.floor(this.sliderProgressMs * 100 / this.durationMs)))
  71. }
  72. private resolveTitleArtistText(): string {
  73. const safeTitle = this.title?.trim() ?? ''
  74. const safeArtist = this.artist?.trim() ?? ''
  75. if (safeTitle !== '' && safeArtist !== '') {
  76. return `${safeTitle} - ${safeArtist}`
  77. }
  78. if (safeTitle !== '') {
  79. return safeTitle
  80. }
  81. if (safeArtist !== '') {
  82. return safeArtist
  83. }
  84. return '未在播放'
  85. }
  86. private resolvePlayPauseIcon(): Resource {
  87. return this.isPlaying ? $r('sys.symbol.pause_fill') : $r('sys.symbol.play_fill')
  88. }
  89. @Builder
  90. private ControlButton(imageResource: Resource, action: string, buttonSize: number, symbolSize: number) {
  91. Button() {
  92. SymbolGlyph(imageResource)
  93. .fontSize(symbolSize)
  94. .fontColor([Color.White])
  95. .effectStrategy(SymbolEffectStrategy.HIERARCHICAL)
  96. }
  97. .width(buttonSize * 1.35)
  98. .height(buttonSize * 1.35)
  99. .backgroundColor(Color.Transparent)
  100. .type(ButtonType.Circle)
  101. .stateEffect(true)
  102. .onClick(() => this.postControlAction(action))
  103. }
  104. @Builder
  105. private PlayProgressButton() {
  106. Stack({ alignContent: Alignment.Center }) {
  107. Progress({ value: this.resolveProgressValue(), total: 100, type: ProgressType.Ring })
  108. .width(34)
  109. .height(34)
  110. .color(Color.White)
  111. .style({ strokeWidth: 2 })
  112. Button() {
  113. SymbolGlyph(this.resolvePlayPauseIcon())
  114. .fontSize(19)
  115. .fontColor([Color.White])
  116. .effectStrategy(SymbolEffectStrategy.HIERARCHICAL)
  117. }
  118. .width(34)
  119. .height(34)
  120. .type(ButtonType.Circle)
  121. .backgroundColor('#26FFFFFF')
  122. .stateEffect(false)
  123. .onClick(() => this.postControlAction(MusicCardActionConstants.ACTION_PLAY_PAUSE))
  124. }
  125. .width(34)
  126. .height(34)
  127. }
  128. private hasCoverBackground(): boolean {
  129. if (this.hasCoverImage && this.coverImageName.length > 0) {
  130. return true;
  131. }
  132. return this.coverPath.length > 0;
  133. }
  134. build() {
  135. Stack() {
  136. if (this.hasCoverBackground()&&this.isChangeBg) {
  137. Column()
  138. .width('100%')
  139. .height('100%')
  140. .backgroundImage(this.resolveCoverSource())
  141. .backgroundImageSize({ width: '100%' })
  142. .backgroundBlurStyle(BlurStyle.BACKGROUND_ULTRA_THICK)
  143. } else {
  144. Column()
  145. .width('100%')
  146. .height('100%')
  147. .linearGradient( { direction: GradientDirection.Right, colors:[
  148. ['#00BC70',0.0],['#D81B60',1.0]]})
  149. }
  150. Column({ space: 12 }) {
  151. Image(this.resolveCoverSource())
  152. .width(60)
  153. .height(60)
  154. .borderRadius(6)
  155. .objectFit(ImageFit.Cover)
  156. .onClick(()=>{
  157. this.isChangeBg = !this.isChangeBg
  158. })
  159. Text(this.resolveTitleArtistText())
  160. .fontSize(13)
  161. .fontWeight(FontWeight.Bold)
  162. .fontColor(Color.White)
  163. .maxLines(1)
  164. .textAlign(TextAlign.Center)
  165. .textOverflow({ overflow: TextOverflow.Ellipsis })
  166. Row({ space: 16 }) {
  167. this.ControlButton($r('sys.symbol.backward_end_fill'),
  168. MusicCardActionConstants.ACTION_PREVIOUS, 18, 18)
  169. this.PlayProgressButton()
  170. this.ControlButton($r('sys.symbol.forward_end_fill'),
  171. MusicCardActionConstants.ACTION_NEXT, 18, 18)
  172. }
  173. .justifyContent(FlexAlign.Center)
  174. .alignItems(VerticalAlign.Center)
  175. }
  176. .width('100%')
  177. .height('100%')
  178. .alignItems(HorizontalAlign.Center)
  179. .justifyContent(FlexAlign.Center)
  180. .padding({ left: 16, right: 16, top: 16, bottom: 14 })
  181. }
  182. .width('100%')
  183. .height('100%')
  184. .clip(true)
  185. .onClick(() => this.postRouterAction(MusicCardActionConstants.ACTION_OPEN_PLAYER))
  186. }
  187. }