| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367 |
- import { PlayStatus } from '../../common/PlayStatus'
- import { PointLightButton } from '../PointLight/PointLightButton'
- import { PointLightDefaultButton } from '../PointLight/PointLightDeFaultButton'
- @Component
- export struct PlayerControls {
- // 播控支持紧凑模式,给横屏封面页复用同一套独立组件。
- @Prop compactMode: boolean = false
- // 整体透明度和底部偏移由外部页面控制,避免组件再持有布局状态。
- @Prop controlOpacity: number = 1
- @Prop bottomOffset: number = 70
- // 是否展示底部功能操作区,HiCar 等场景沿用原有隐藏逻辑。
- @Prop showBottomRow: boolean = true
- // 播控区需要的播放态、进度态都作为纯数据输入。
- @Prop isLandscape: boolean = false
- @Prop progressValue: number = 0
- @Prop progressMaxValue: number = 100
- @Prop slideEnable: boolean = false
- @Prop currentTime: string = '00:00'
- @Prop totalTime: string = '00:00'
- @Prop controlPlayStatus: number = PlayStatus.INIT
- @Prop isCircleBtn: boolean = false
- @Prop isPlayerLoading: boolean = false
- @Prop playType: number = 0
- @Prop fastForwardSeconds: string = '10'
- @Prop isShowBackFast: boolean = false
- // 编辑、收藏等按钮是否展示,由外部决定当前歌曲是否可操作。
- @Prop showEditButton: boolean = false
- @Prop isFavorite: boolean = false
- // 频谱区域也改成可注入 builder,后续可以继续抽到独立组件而不动页面骨架。
- @BuilderParam spectrumBuilder: () => void
- // 播控内部只触发动作,不再持有具体业务逻辑。
- onPlayPrevious: () => void = (): void => {}
- onPlayOrPause: () => void = (): void => {}
- onPlayNext: () => void = (): void => {}
- onToggleMore: () => void = (): void => {}
- onSetLoopMode: () => void = (): void => {}
- onSeek: (value: number) => void = (_value: number): void => {}
- onBackFast: () => void = (): void => {}
- onForwardFast: () => void = (): void => {}
- onRotate: () => void = (): void => {}
- onToggleEdit: () => void = (): void => {}
- onToggleEqualizer: () => void = (): void => {}
- onToggleFavorite: () => void = (): void => {}
- onOpenPlaylist: () => void = (): void => {}
- private resolvePlayModeIcon(): Resource {
- // 播放模式图标统一在独立组件里收口,减少 LocalMusic 内部判断分支。
- if (this.playType === 0) {
- return $r('app.media.loop')
- }
- if (this.playType === 1) {
- return $r('app.media.single')
- }
- if (this.playType === 2) {
- return $r('app.media.normal_play')
- }
- if (this.playType === 3) {
- return $r('app.media.random')
- }
- return $r('app.media.noloop')
- }
- private resolveFastForwardText(): string {
- // 统一把快进秒数当成字符串展示,避免上层状态类型变化时影响 UI 组件。
- return this.fastForwardSeconds
- }
- @Builder
- private MoreButtonContent() {
- // 更多按钮沿用原有资源,保持现有视觉风格不变。
- Image($r('app.media.menu'))
- .width(24)
- }
- @Builder
- private PreviousButtonContent() {
- Image($r('app.media.ic_previous'))
- .width(32)
- .aspectRatio(1)
- }
- @Builder
- private NextButtonContent() {
- Image($r('app.media.ic_next'))
- .width(32)
- .aspectRatio(1)
- }
- @Builder
- private PlayOrPauseButtonContent() {
- Stack() {
- // 播放暂停按钮单独保留加载圈,兼容远程歌曲缓冲场景。
- Image(this.controlPlayStatus === PlayStatus.PLAY
- ? (this.isCircleBtn ? $r('app.media.hm_pause') : $r('app.media.ic_public_play'))
- : (this.isCircleBtn ? $r('app.media.hm_play2') : $r('app.media.ic_public_pause')))
- .width(this.isLandscape ? 38 : 40)
- .fillColor(Color.White)
- .aspectRatio(1)
- if (this.isPlayerLoading) {
- Progress({ value: 0, total: 100, type: ProgressType.Ring })
- .width(this.isLandscape ? 40 : 43)
- .height(this.isLandscape ? 40 : 43)
- .color(Color.White)
- .style({ strokeWidth: 5, status: ProgressStatus.LOADING })
- }
- }
- .width(this.isLandscape ? 38 : 41)
- .height(this.isLandscape ? 38 : 41)
- }
- @Builder
- private CenterControls() {
- Row() {
- PointLightButton({
- builder: () => {
- this.MoreButtonContent()
- },
- isPx: false,
- builderHeight: 26,
- builderWidth: 26,
- })
- .onClick(() => {
- this.onToggleMore()
- })
- PointLightButton({
- builder: () => {
- this.PreviousButtonContent()
- },
- isPx: false,
- builderHeight: 26,
- builderWidth: 26,
- })
- .onClick(() => {
- this.onPlayPrevious()
- })
- PointLightButton({
- builder: () => {
- this.PlayOrPauseButtonContent()
- },
- isPx: false,
- builderHeight: this.isLandscape ? 38 : 41,
- builderWidth: this.isLandscape ? 38 : 41,
- })
- .onClick(() => {
- this.onPlayOrPause()
- })
- PointLightButton({
- builder: () => {
- this.NextButtonContent()
- },
- isPx: false,
- builderHeight: 26,
- builderWidth: 26,
- })
- .onClick(() => {
- this.onPlayNext()
- })
- PointLightButton({
- builder: () => {
- Image(this.resolvePlayModeIcon())
- .width(24)
- .aspectRatio(1)
- },
- isPx: false,
- builderHeight: 26,
- builderWidth: 26,
- })
- .onClick(() => {
- this.onSetLoopMode()
- })
- }
- .width('95%')
- .justifyContent(FlexAlign.SpaceEvenly)
- }
- @Builder
- private ProgressControls() {
- Column() {
- Row() {
- Stack() {
- SymbolGlyph($r('sys.symbol.arrow_counterclockwise'))
- .fontColor([Color.White])
- .fontSize(21)
- .effectStrategy(1)
- Text(this.resolveFastForwardText())
- .fontColor(Color.White)
- .fontSize(10)
- .fontWeight(FontWeight.Bold)
- }
- .padding({ left: 12, bottom: 6 })
- .visibility(this.isShowBackFast ? Visibility.Visible : Visibility.None)
- .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 })
- .onClick(() => {
- this.onBackFast()
- })
- Slider({
- value: this.progressValue,
- min: 0,
- max: this.progressMaxValue,
- step: 1,
- style: SliderStyle.InSet
- })
- .height(20)
- .blockColor('rgba(255,255,255,1)')
- .trackColor('rgba(255,255,255,0.3)')
- .selectedColor(Color.White)
- .trackThickness(4)
- .showSteps(false)
- .showTips(true)
- .layoutWeight(1)
- .enabled(this.slideEnable)
- .onChange((value: number, mode: SliderChangeMode) => {
- if (mode !== 2) {
- return
- }
- this.onSeek(value)
- })
- Stack() {
- SymbolGlyph($r('sys.symbol.arrow_clockwise'))
- .fontColor([Color.White])
- .fontSize(21)
- .effectStrategy(1)
- Text(this.resolveFastForwardText())
- .fontColor(Color.White)
- .fontSize(10)
- .fontWeight(FontWeight.Bold)
- }
- .padding({ right: 10, bottom: 6 })
- .visibility(this.isShowBackFast ? Visibility.Visible : Visibility.None)
- .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 })
- .onClick(() => {
- this.onForwardFast()
- })
- }
- .justifyContent(FlexAlign.Center)
- .padding({ left: 20, right: 20 })
- .width(this.isLandscape ? '88%' : '95%')
- Row() {
- Text(this.currentTime)
- .fontSize(10)
- .fontColor(Color.White)
- .margin({ left: 25 })
- Blank()
- Text(this.totalTime)
- .fontSize(10)
- .fontColor(Color.White)
- .margin({ right: 25 })
- }
- .width(this.isLandscape ? '85%' : '90%')
- .height(10)
- }
- .width('100%')
- .alignItems(HorizontalAlign.Center)
- }
- @Builder
- private BottomActionRow() {
- Row() {
- PointLightDefaultButton({
- isSysBol: true,
- pointColor: Color.White,
- imageResource: $r('sys.symbol.rectangle_portrait_rotate'),
- isPx: false,
- builderHeight: 22,
- builderWidth: 22,
- })
- .onClick(() => {
- this.onRotate()
- })
- if (this.showEditButton) {
- // 编辑标签只在当前有歌曲时展示,避免空态按钮触发无效动作。
- PointLightDefaultButton({
- isSysBol: true,
- pointColor: Color.White,
- imageResource: $r('sys.symbol.rename'),
- isPx: false,
- builderHeight: 22,
- builderWidth: 22,
- })
- .onClick(() => {
- this.onToggleEdit()
- })
- }
- PointLightDefaultButton({
- isSysBol: true,
- pointColor: Color.White,
- imageResource: $r('sys.symbol.slider_vertical_3'),
- isPx: false,
- builderHeight: 22,
- builderWidth: 22,
- })
- .onClick(() => {
- this.onToggleEqualizer()
- })
- PointLightDefaultButton({
- isSysBol: true,
- pointColor: Color.White,
- imageResource: this.isFavorite ? $r('sys.symbol.heart_fill') : $r('sys.symbol.heart'),
- isPx: false,
- builderHeight: 22,
- builderWidth: 22,
- })
- .onClick(() => {
- this.onToggleFavorite()
- })
- PointLightDefaultButton({
- isSysBol: true,
- pointColor: Color.White,
- imageResource: $r('sys.symbol.music_note_list'),
- isPx: false,
- builderHeight: 26,
- builderWidth: 26,
- })
- .onClick(() => {
- this.onOpenPlaylist()
- })
- }
- .width(this.isLandscape ? '88%' : '95%')
- .justifyContent(FlexAlign.SpaceAround)
- .visibility(this.showBottomRow ? Visibility.Visible : Visibility.None)
- .height(30)
- .animation({
- duration: 666,
- curve: 'ease-in-out'
- })
- }
- @Builder
- private FullControls() {
- Column() {
- // 频谱显示是否启用由外部 builder 自己判断,组件本身不再持有该业务状态。
- this.spectrumBuilder()
- this.ProgressControls()
- this.CenterControls()
- this.BottomActionRow()
- }
- .justifyContent(FlexAlign.Center)
- .alignItems(HorizontalAlign.Center)
- .opacity(this.controlOpacity)
- .position({ bottom: this.bottomOffset })
- }
- build() {
- if (this.compactMode) {
- // 横屏封面区域只复用紧凑播控,不再在 LocalMusic 里额外维护一套 UI。
- this.CenterControls()
- } else {
- // 竖屏和完整播放页统一走独立播控组件。
- this.FullControls()
- }
- }
- }
|