MusicPlayerView.ets 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771
  1. import { PreferencesUtil, StrUtil, ToastUtil } from '@pura/harmony-utils'
  2. import { PlayStatus } from '../../common/PlayStatus'
  3. import { CommonConstants } from '../../common/constants/CommonConstants'
  4. import { isPlaybackControlPlaying } from '../../common/player/PlaybackControlStateHelper'
  5. import { MusicPlaybackController } from '../../controller/MusicPlaybackController'
  6. import { LyricController } from '../../lyric/LyricController'
  7. import { Lyric } from '../../lyric/bean/Lyric'
  8. import { LyricParser } from '../../lyric/parse/LyricParser'
  9. import { LyricView2 } from '../../lyric/view/LyricView2'
  10. import { SettingPage } from '../../pages/SettingPage'
  11. import { PlaybackViewState, PlaybackViewStateCenter } from '../../playback/PlaybackViewStateCenter'
  12. import Logger from '../../common/util/Logger'
  13. import { VideoItem } from '../../viewmodel/VideoItem'
  14. import { resolvePlayerLyricContent, resolvePlayerTimeTexts } from './MusiPlayerViewStateHelper'
  15. import { PointLightButton } from '../PointLight/PointLightButton'
  16. import { PointLightDefaultButton } from '../PointLight/PointLightDeFaultButton'
  17. const TAG = 'MusicPlayerView'
  18. @Component
  19. export struct MusicPlayerView {
  20. @StorageProp('isLandscape') isLandscape: boolean = false;
  21. @StorageProp('topSafeHeight') topSafeHeight: number = 0;
  22. @StorageProp('bottomSafeHeight') bottomSafeHeight: number = 0;
  23. @StorageProp('windowWidth') windowWidth: number = 0;
  24. @StorageProp('windowHeight') windowHeight: number = 0;
  25. @StorageProp('isHiCarStatus') isHiCarStatus: boolean = false;
  26. @StorageProp('curDisplayIsHiCar') curDisplayIsHiCar: boolean = false;
  27. onClose: () => void = () => {}
  28. @State currentSong: VideoItem | undefined = undefined
  29. @State controlPlayStatus: number = PlayStatus.INIT
  30. @State progressValue: number = 0
  31. @State playbackPositionMs: number = 0
  32. @State playbackDurationMs: number = 0
  33. @State cover: string | undefined = ''
  34. @StorageLink('musicPlayType') playType: number = 0
  35. @StorageLink('playbackIsFavorite') isFavorite: boolean = false
  36. @StorageProp('themeColor') themeColor: string =
  37. PreferencesUtil.getStringSync('THEME_COLOR', CommonConstants.DEFAULT_THEME_COLOR)
  38. @State isMusicBGCover: boolean = true
  39. @State currentTime: string = '00:00'
  40. @State totalTime: string = '00:00'
  41. @State currentSwiperIndex: number = 0
  42. @State sliderProgressValue: number = 0
  43. @State showSimple: boolean = false
  44. @State rotateAngle: number = 0
  45. @State rotateAngle2: number = -45
  46. @State isRotationRunning: boolean = false
  47. @State isPlayerLoading: boolean = false
  48. private rotationTimer: number = -1
  49. private playbackController: MusicPlaybackController = MusicPlaybackController.getInstance()
  50. private lyricController: LyricController = new LyricController()
  51. .setEmptyHint('暂无歌词')
  52. .setTextColor('#DDDDDD')
  53. .setHighlightColor('#FFFFFF')
  54. .setLightText(true)
  55. .setAlignMode('center')
  56. .setHightLightCenter(true)
  57. private lyricParser: LyricParser = new LyricParser()
  58. private appliedLyricContent: string = ''
  59. private lyricSyncTimer: number = -1
  60. private playbackViewStateCenter: PlaybackViewStateCenter = PlaybackViewStateCenter.getInstance()
  61. private readonly playbackStateObserver: (state: PlaybackViewState) => void =
  62. (state: PlaybackViewState): void => {
  63. this.applyPlaybackViewState(state)
  64. }
  65. aboutToAppear(): void {
  66. this.isMusicBGCover = PreferencesUtil.getBooleanSync(SettingPage.iS_MUSIC_BG_COVER, true)
  67. this.showSimple = PreferencesUtil.getBooleanSync('showSimple', false)
  68. Logger.info(TAG, '[PLAYER_DETACH] player aboutToAppear subscribe')
  69. this.playbackViewStateCenter.subscribe(this.playbackStateObserver)
  70. this.startLyricSyncTimer()
  71. }
  72. aboutToDisappear(): void {
  73. Logger.info(TAG, '[PLAYER_DETACH] player aboutToDisappear unsubscribe')
  74. this.playbackViewStateCenter.unsubscribe(this.playbackStateObserver)
  75. this.stopLyricSyncTimer()
  76. this.stopRotation()
  77. }
  78. private syncTimeTexts(): void {
  79. const timeTexts = resolvePlayerTimeTexts(this.playbackPositionMs, this.playbackDurationMs)
  80. this.currentTime = timeTexts.currentTime
  81. this.totalTime = timeTexts.totalTime
  82. }
  83. private applyPlaybackViewState(state: PlaybackViewState): void {
  84. const previousSongPath = this.currentSong?.filePath ?? ''
  85. const nextSongPath = state.currentSong?.filePath ?? ''
  86. const songChanged = previousSongPath !== nextSongPath
  87. const lyricChanged = this.currentSong?.lyricContent !== state.currentSong?.lyricContent
  88. Logger.info(TAG,
  89. `[PLAYER_DETACH] player apply song=${state.currentSong?.name ?? ''}, status=${state.controlPlayStatus}, ` +
  90. `position=${state.playbackPositionMs}, duration=${state.playbackDurationMs}, progress=${state.progressValue}, ` +
  91. `songChanged=${songChanged}, lyricChanged=${lyricChanged}`)
  92. this.currentSong = state.currentSong
  93. this.controlPlayStatus = state.controlPlayStatus
  94. this.progressValue = state.progressValue
  95. this.sliderProgressValue = state.progressValue
  96. this.playbackPositionMs = state.playbackPositionMs
  97. this.playbackDurationMs = state.playbackDurationMs
  98. this.cover = state.cover
  99. this.syncTimeTexts()
  100. if (songChanged || lyricChanged) {
  101. this.syncLyricContentIfNeeded(true)
  102. } else {
  103. this.syncLyricContentIfNeeded()
  104. }
  105. this.syncLyricPosition()
  106. this.syncRotationState()
  107. }
  108. private startLyricSyncTimer(): void {
  109. this.stopLyricSyncTimer()
  110. this.lyricSyncTimer = setInterval(() => {
  111. this.syncLyricContentIfNeeded()
  112. }, 500)
  113. }
  114. private stopLyricSyncTimer(): void {
  115. if (this.lyricSyncTimer >= 0) {
  116. clearInterval(this.lyricSyncTimer)
  117. this.lyricSyncTimer = -1
  118. }
  119. }
  120. private syncLyricContentIfNeeded(force: boolean = false): void {
  121. const lyricContent = resolvePlayerLyricContent(this.currentSong)
  122. if (!force && lyricContent === this.appliedLyricContent) {
  123. return
  124. }
  125. this.appliedLyricContent = lyricContent
  126. if (lyricContent.length === 0) {
  127. this.lyricController.setLyric(null)
  128. return
  129. }
  130. const lines: string[] = lyricContent.split('\n').map((line: string) => line.trim())
  131. const lyric: Lyric = this.lyricParser.parse(lines)
  132. this.lyricController.setLyric(lyric)
  133. this.syncLyricPosition()
  134. }
  135. private syncLyricPosition(): void {
  136. this.lyricController.updatePosition(Math.max(0, this.playbackPositionMs))
  137. }
  138. private syncRotationState(): void {
  139. const isPlaying = isPlaybackControlPlaying(this.controlPlayStatus)
  140. if (isPlaying) {
  141. this.startRotation()
  142. return
  143. }
  144. this.stopRotation()
  145. }
  146. private startRotation(): void {
  147. if (!this.isRotationRunning) {
  148. this.isRotationRunning = true
  149. this.rotationTimer = setInterval(() => {
  150. if (this.isRotationRunning) {
  151. this.rotateAngle += 36
  152. }
  153. }, 1000)
  154. }
  155. animateTo({
  156. duration: 777,
  157. iterations: 1,
  158. curve: Curve.Linear
  159. }, () => {
  160. this.rotateAngle2 = -9
  161. })
  162. }
  163. private stopRotation(): void {
  164. this.isRotationRunning = false
  165. if (this.rotationTimer >= 0) {
  166. clearInterval(this.rotationTimer)
  167. this.rotationTimer = -1
  168. }
  169. animateTo({
  170. duration: 1000,
  171. curve: Curve.Linear,
  172. iterations: 1,
  173. }, () => {
  174. this.rotateAngle2 = -45
  175. })
  176. }
  177. private onSeek(value: number): void {
  178. const seekValue = MusicPlaybackController.resolveSeekValueFromPercent(value, this.playbackDurationMs)
  179. void this.playbackController.seekTo(`${Math.floor(seekValue)}`, 'musi-player-view')
  180. }
  181. private buildArtistAlbumText(): string {
  182. const artist = this.currentSong?.artist ?? ''
  183. const album = this.currentSong?.album ?? ''
  184. if (StrUtil.isNotEmpty(artist) && StrUtil.isNotEmpty(album)) {
  185. return `${artist} ${album}`
  186. }
  187. if (StrUtil.isNotEmpty(artist)) {
  188. return artist
  189. }
  190. if (StrUtil.isNotEmpty(album)) {
  191. return album
  192. }
  193. return ''
  194. }
  195. private resolveSongName(): string {
  196. return this.currentSong?.name ?? '暂无播放'
  197. }
  198. private resolveSongArtist(): string {
  199. return this.currentSong?.artist ?? ''
  200. }
  201. private toggleSimpleMode(): void {
  202. this.showSimple = !this.showSimple
  203. PreferencesUtil.put('showSimple', this.showSimple)
  204. }
  205. private notifyFeaturePending(featureName: string): void {
  206. ToastUtil.showToast(`${featureName}功能待迁移`)
  207. }
  208. @Builder
  209. private PlayTitle() {
  210. Row() {
  211. Row() {
  212. PointLightDefaultButton({
  213. isSysBol: true,
  214. pointColor: Color.White,
  215. imageResource: $r('sys.symbol.chevron_down'),
  216. isPx: false,
  217. builderHeight: 20,
  218. builderWidth: 20,
  219. })
  220. .onClick(() => {
  221. this.onClose()
  222. })
  223. }
  224. .width('100%')
  225. }
  226. .width('100%')
  227. .padding({ left: 25, top: this.topSafeHeight })
  228. }
  229. @Builder
  230. private RectangleCoverView() {
  231. Stack({ alignContent: Alignment.Center }) {
  232. Image(StrUtil.isNotEmpty(this.cover) ? this.cover as string : $r('app.media.alt'))
  233. .height(px2vp(this.windowHeight)*0.4)
  234. .width('auto')
  235. .aspectRatio(1)
  236. .objectFit(ImageFit.Contain)
  237. .alt($r('app.media.alt'))
  238. .margin({ right: 8, left: 8, top: 12 })
  239. .borderRadius(20)
  240. .clip(true)
  241. .shadow({
  242. radius: 22,
  243. type: ShadowType.BLUR,
  244. color: 'on_primary'
  245. })
  246. .onClick(() => {
  247. this.isMusicBGCover = !this.isMusicBGCover
  248. })
  249. }
  250. }
  251. @Builder
  252. private musicNameInfo() {
  253. Row() {
  254. Column({ space: 8 }) {
  255. Text(this.resolveSongName())
  256. .fontSize(this.resolveSongName().length >= 28 ? 18 : 20)
  257. .fontWeight(FontWeight.Bold)
  258. .textOverflow({ overflow: TextOverflow.MARQUEE })
  259. .maxLines(1)
  260. .width('99%')
  261. .textAlign(TextAlign.Start)
  262. .fontColor(Color.White)
  263. Text(this.buildArtistAlbumText())
  264. .fontSize(15)
  265. .textOverflow({ overflow: TextOverflow.MARQUEE })
  266. .fontColor(Color.White)
  267. .textAlign(TextAlign.Start)
  268. .visibility(StrUtil.isEmpty(this.buildArtistAlbumText()) ? Visibility.None : Visibility.Visible)
  269. .width('99%')
  270. .maxLines(1)
  271. }
  272. .layoutWeight(1)
  273. .margin({ left: 15 })
  274. Blank()
  275. Row() {
  276. PointLightDefaultButton({
  277. isSysBol: true,
  278. pointColor: Color.White,
  279. imageResource: this.showSimple ? $r('sys.symbol.sun_max') : $r('sys.symbol.moon'),
  280. isPx: false,
  281. builderHeight: 26,
  282. builderWidth: 26,
  283. })
  284. .onClick(() => {
  285. this.toggleSimpleMode()
  286. })
  287. }
  288. .margin({ left: 8, right: 8 })
  289. }
  290. .width('90%')
  291. .margin({ top: 15 })
  292. .visibility(this.currentSwiperIndex === 0 ? Visibility.Visible : Visibility.None)
  293. }
  294. @Builder
  295. private LyricsTopItem() {
  296. Row() {
  297. Stack({ alignContent: Alignment.Center }) {
  298. Image(StrUtil.isNotEmpty(this.cover) ? this.cover as string : $r('app.media.alt'))
  299. .height(58)
  300. .width(58)
  301. .clip(true)
  302. .alt($r('app.media.alt'))
  303. .borderRadius(8)
  304. .shadow({
  305. radius: 22,
  306. type: ShadowType.BLUR,
  307. color: 'on_primary'
  308. })
  309. }
  310. .width(62)
  311. .height('100%')
  312. Column() {
  313. Column() {
  314. Text(this.resolveSongName())
  315. .fontSize(16)
  316. .maxLines(1)
  317. .fontWeight(FontWeight.Bolder)
  318. .fontColor(Color.White)
  319. .textOverflow({ overflow: TextOverflow.Ellipsis })
  320. .margin({ left: 10 })
  321. Text(this.resolveSongArtist())
  322. .fontSize(13)
  323. .fontWeight(FontWeight.Bolder)
  324. .padding({ top: 8 })
  325. .fontColor(Color.White)
  326. .margin({ left: 10 })
  327. .maxLines(1)
  328. .textOverflow({ overflow: TextOverflow.Ellipsis })
  329. .visibility(StrUtil.isEmpty(this.resolveSongArtist()) ? Visibility.None : Visibility.Visible)
  330. }
  331. .height('100%')
  332. .width('100%')
  333. .justifyContent(FlexAlign.Center)
  334. .alignItems(HorizontalAlign.Start)
  335. }
  336. .height('100%')
  337. .layoutWeight(1)
  338. .margin({ right: 8 })
  339. Column() {
  340. PointLightDefaultButton({
  341. isSysBol: false,
  342. pointColor: Color.White,
  343. imageResource: $r('app.media.lyric'),
  344. isPx: false,
  345. builderHeight: 23,
  346. builderWidth: 23,
  347. })
  348. .onClick(() => {
  349. this.notifyFeaturePending('歌词设置')
  350. })
  351. }
  352. .width(44)
  353. .height('100%')
  354. .justifyContent(FlexAlign.Center)
  355. .alignItems(HorizontalAlign.End)
  356. }
  357. .width('90%')
  358. .padding({ left: 28, right: 28 })
  359. .height(58)
  360. .alignItems(VerticalAlign.Center)
  361. }
  362. @Builder
  363. private playMenuBuilder() {
  364. Image($r('app.media.menu'))
  365. .width(24)
  366. .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.5 })
  367. }
  368. @Builder
  369. private playPreviousBuilder() {
  370. Image($r('app.media.ic_previous'))
  371. .width(32)
  372. .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.5 })
  373. .aspectRatio(CommonConstants.ASPECT_RATIO)
  374. }
  375. @Builder
  376. private playNextBuilder() {
  377. Image($r('app.media.ic_next'))
  378. .width(32)
  379. .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.5 })
  380. .aspectRatio(CommonConstants.ASPECT_RATIO)
  381. }
  382. @Builder
  383. private PlayOrPauseButton() {
  384. Stack() {
  385. Column() {
  386. Image(this.controlPlayStatus === PlayStatus.PLAY ? $r('app.media.hm_pause') : $r('app.media.hm_play2'))
  387. .width(40)
  388. .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.5 })
  389. .fillColor(Color.White)
  390. .aspectRatio(CommonConstants.ASPECT_RATIO)
  391. }
  392. if (this.isPlayerLoading) {
  393. Progress({ value: 0, total: 100, type: ProgressType.Ring })
  394. .width(43)
  395. .height(43)
  396. .color(Color.White)
  397. .style({ strokeWidth: 5, status: ProgressStatus.LOADING })
  398. }
  399. }
  400. .width(41)
  401. .height(41)
  402. }
  403. @Builder
  404. private playModeBuilder() {
  405. Column() {
  406. if (this.playType === 0) {
  407. Image($r('app.media.loop'))
  408. .width(24)
  409. .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.5 })
  410. .aspectRatio(CommonConstants.ASPECT_RATIO)
  411. } else if (this.playType === 1) {
  412. Image($r('app.media.single'))
  413. .width(24)
  414. .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.5 })
  415. .aspectRatio(CommonConstants.ASPECT_RATIO)
  416. } else if (this.playType === 2) {
  417. Image($r('app.media.normal_play'))
  418. .width(24)
  419. .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.5 })
  420. .aspectRatio(CommonConstants.ASPECT_RATIO)
  421. } else if (this.playType === 3) {
  422. Image($r('app.media.random'))
  423. .width(24)
  424. .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.5 })
  425. .aspectRatio(CommonConstants.ASPECT_RATIO)
  426. } else {
  427. Image($r('app.media.noloop'))
  428. .width(24)
  429. .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.5 })
  430. .aspectRatio(CommonConstants.ASPECT_RATIO)
  431. }
  432. }
  433. }
  434. @Builder
  435. private playModeButton() {
  436. PointLightButton({
  437. builder: () => {
  438. this.playModeBuilder()
  439. },
  440. isPx: false,
  441. builderHeight: 26,
  442. builderWidth: 26,
  443. })
  444. .onClick(async () => {
  445. await this.playbackController.setLoopMode()
  446. })
  447. }
  448. @Builder
  449. private TopPlayProgressView() {
  450. Column() {
  451. Row() {
  452. Slider({
  453. value: this.sliderProgressValue,
  454. min: 0,
  455. max: 100,
  456. step: 1,
  457. style: SliderStyle.InSet
  458. })
  459. .height(20)
  460. .blockColor('rgba(255,255,255,1)')
  461. .trackColor('rgba(255,255,255,0.3)')
  462. .selectedColor(Color.White)
  463. .trackThickness(4)
  464. .showSteps(false)
  465. .showTips(true)
  466. .layoutWeight(1)
  467. .enabled(this.playbackDurationMs > 0)
  468. .onChange((value: number, mode: SliderChangeMode) => {
  469. this.sliderProgressValue = value
  470. if (mode !== 2) {
  471. return
  472. }
  473. this.onSeek(value)
  474. })
  475. }
  476. .justifyContent(FlexAlign.Center)
  477. .padding({ left: 20, right: 20 })
  478. .width('95%')
  479. Row() {
  480. Text(this.currentTime)
  481. .fontSize(10)
  482. .fontColor(Color.White)
  483. .margin({ left: 25 })
  484. Blank()
  485. Text(this.totalTime)
  486. .fontSize(10)
  487. .fontColor(Color.White)
  488. .margin({ right: 25 })
  489. }
  490. .width('90%')
  491. .height(10)
  492. }
  493. .width('100%')
  494. .alignItems(HorizontalAlign.Center)
  495. }
  496. @Builder
  497. private playCenterView() {
  498. Row() {
  499. PointLightButton({
  500. builder: () => {
  501. this.playMenuBuilder()
  502. },
  503. isPx: false,
  504. builderHeight: 26,
  505. builderWidth: 26,
  506. })
  507. .onClick(() => {
  508. this.notifyFeaturePending('更多')
  509. })
  510. PointLightButton({
  511. builder: () => {
  512. this.playPreviousBuilder()
  513. },
  514. isPx: false,
  515. builderHeight: 26,
  516. builderWidth: 26,
  517. })
  518. .onClick(async () => {
  519. await this.playbackController.playPrevious()
  520. })
  521. PointLightButton({
  522. builder: () => {
  523. this.PlayOrPauseButton()
  524. },
  525. isPx: false,
  526. builderHeight: 41,
  527. builderWidth: 41,
  528. })
  529. .onClick(() => {
  530. void this.playbackController.playOrPause()
  531. })
  532. PointLightButton({
  533. builder: () => {
  534. this.playNextBuilder()
  535. },
  536. isPx: false,
  537. builderHeight: 26,
  538. builderWidth: 26,
  539. })
  540. .onClick(async () => {
  541. await this.playbackController.playNext()
  542. })
  543. this.playModeButton()
  544. }
  545. .width('95%')
  546. .margin({ bottom: 15 })
  547. .justifyContent(FlexAlign.SpaceEvenly)
  548. }
  549. @Builder
  550. private playListBuilder() {
  551. PointLightDefaultButton({
  552. isSysBol: true,
  553. pointColor: Color.White,
  554. imageResource: $r('sys.symbol.music_note_list'),
  555. isPx: false,
  556. builderHeight: 26,
  557. builderWidth: 26,
  558. })
  559. .onClick(() => {
  560. void this.playbackController.openPlayList()
  561. })
  562. }
  563. @Builder
  564. private BottomView() {
  565. Row() {
  566. PointLightDefaultButton({
  567. isSysBol: true,
  568. pointColor: Color.White,
  569. imageResource: $r('sys.symbol.rectangle_portrait_rotate'),
  570. isPx: false,
  571. builderHeight: 22,
  572. builderWidth: 22,
  573. })
  574. .onClick(() => {
  575. this.notifyFeaturePending('旋转')
  576. })
  577. PointLightDefaultButton({
  578. isSysBol: true,
  579. pointColor: Color.White,
  580. imageResource: $r('sys.symbol.rename'),
  581. isPx: false,
  582. builderHeight: 22,
  583. builderWidth: 22,
  584. })
  585. .onClick(() => {
  586. this.notifyFeaturePending('编辑标签')
  587. })
  588. PointLightDefaultButton({
  589. isSysBol: true,
  590. pointColor: Color.White,
  591. imageResource: $r('sys.symbol.slider_vertical_3'),
  592. isPx: false,
  593. builderHeight: 22,
  594. builderWidth: 22,
  595. })
  596. .onClick(() => {
  597. this.notifyFeaturePending('均衡器')
  598. })
  599. PointLightDefaultButton({
  600. isSysBol: true,
  601. pointColor: Color.White,
  602. imageResource: this.isFavorite ? $r('sys.symbol.heart_fill') : $r('sys.symbol.heart'),
  603. isPx: false,
  604. builderHeight: 22,
  605. builderWidth: 22,
  606. })
  607. .onClick(() => {
  608. this.notifyFeaturePending('收藏')
  609. })
  610. this.playListBuilder()
  611. }
  612. .width('95%')
  613. .justifyContent(FlexAlign.SpaceAround)
  614. .visibility(this.showSimple ? Visibility.None : Visibility.Visible)
  615. .height(30)
  616. .animation({
  617. duration: 666,
  618. curve: 'ease-in-out' // 可选动画曲线
  619. })
  620. }
  621. @Builder
  622. private BottomControl() {
  623. Column() {
  624. this.TopPlayProgressView()
  625. this.playCenterView()
  626. this.BottomView()
  627. }
  628. .justifyContent(FlexAlign.Center)
  629. .alignItems(HorizontalAlign.Center)
  630. .margin({ top: 10 })
  631. .position({ bottom: 55}) // 将 固定在底部
  632. }
  633. @Builder
  634. private CoverPageBuilder() {
  635. Column() {
  636. this.RectangleCoverView()
  637. this.musicNameInfo()
  638. this.BottomControl()
  639. }
  640. .width('100%')
  641. .height('100%')
  642. .justifyContent(FlexAlign.Start)
  643. .alignItems(HorizontalAlign.Center)
  644. .padding({ top: 20 })
  645. }
  646. @Builder
  647. private LyricPageBuilder() {
  648. Column() {
  649. this.LyricsTopItem()
  650. LyricView2({
  651. controller: this.lyricController,
  652. enableSeek: true,
  653. seekUIColor: '#ff0000',
  654. seekLineColor: '#80ffffff',
  655. seekUIStyle: 'listItem',
  656. onSeekAction: (position: number) => {
  657. void this.playbackController.seekTo(`${Math.max(0, Math.floor(position))}`, 'musi-player-lyric-seek')
  658. return true
  659. }
  660. })
  661. .width('100%')
  662. .layoutWeight(1)
  663. .margin({ top: 2, bottom: 10, left: 4, right: 4 })
  664. }
  665. .width('100%')
  666. .height('100%')
  667. .justifyContent(FlexAlign.Start)
  668. .alignItems(HorizontalAlign.Center)
  669. .padding({ left: 4, right: 4, top: 38 })
  670. }
  671. build() {
  672. Stack({ alignContent: Alignment.Center }) {
  673. Column()
  674. .width('100%')
  675. .height('100%')
  676. .linearGradient({
  677. direction: GradientDirection.Right,
  678. colors: [['#00BC70', 0.0], ['#D81B60', 1.0]]
  679. })
  680. .visibility(this.isMusicBGCover && StrUtil.isNotEmpty(this.cover) ? Visibility.None : Visibility.Visible)
  681. Column() {
  682. this.PlayTitle()
  683. Swiper() {
  684. this.CoverPageBuilder()
  685. this.LyricPageBuilder()
  686. }
  687. .onChange((index: number) => {
  688. this.currentSwiperIndex = index
  689. if (index === 1) {
  690. this.syncLyricContentIfNeeded()
  691. this.syncLyricPosition()
  692. }
  693. })
  694. .position({ x: 0, y:this.isLandscape?42: 35 })
  695. .indicator(false)
  696. .displayCount(1)
  697. .loop(false)
  698. .autoPlay(false)
  699. .layoutWeight(1)
  700. .width('100%')
  701. }
  702. .width('100%')
  703. .height('100%')
  704. .justifyContent(FlexAlign.Start)
  705. .padding({ bottom: 5 })
  706. }
  707. .backgroundImage(!this.isMusicBGCover || StrUtil.isEmpty(this.cover) ? null : this.cover)
  708. .backgroundImageSize(!this.isMusicBGCover ? { width: '100%' } : { height: '150%', width: '100%' })
  709. .backgroundBlurStyle(!this.isMusicBGCover ? BlurStyle.NONE : BlurStyle.BACKGROUND_ULTRA_THICK)
  710. .backgroundBrightness({ rate: 0, lightUpDegree: -0.1 })
  711. .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
  712. }
  713. }