SpectrumView.ets 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. import { IjkMediaPlayer } from '@ohos/ijkplayer'
  2. import { FFTAnalyzer } from '../../common/util/FFTAnalyzer'
  3. import { SpectrumRenderer } from './SpectrumRenderer'
  4. import { BarSpectrumRenderer } from './BarSpectrumRenderer'
  5. import { WaveSpectrumRenderer } from './WaveSpectrumRenderer'
  6. import { CircleSpectrumRenderer } from './CircleSpectrumRenderer'
  7. import { ParticleSpectrumRenderer } from './ParticleSpectrumRenderer'
  8. import { normalizeHexColor } from './SpectrumRenderer'
  9. import Logger from '../../common/util/Logger'
  10. const TAG: string = 'heanup-SpectrumView'
  11. const BAND_COUNT: number = 53
  12. const FRAME_INTERVAL: number = 33 // ~30fps
  13. export enum SpectrumMode {
  14. BAR = 0,
  15. WAVE = 1,
  16. CIRCLE = 2,
  17. PARTICLE = 3
  18. }
  19. const MODE_NAMES: string[] = ['柱状', '波形', '圆形', '粒子']
  20. @Component
  21. export struct SpectrumView {
  22. @StorageLink('spectrumMode') currentMode: number = 0
  23. @Prop @Watch('onPlayingChange') isPlaying: boolean = false
  24. @StorageProp('themeColor') themeColor: string = '#FF0050'
  25. @Prop @Watch('onVisibilityChange') isVisible: boolean = false
  26. @State showModePanel: boolean = false
  27. @State canvasWidth: number = 0
  28. @State canvasHeight: number = 0
  29. private renderSettings: RenderingContextSettings = new RenderingContextSettings(true)
  30. private canvasContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.renderSettings)
  31. private fftAnalyzer: FFTAnalyzer = new FFTAnalyzer()
  32. private renderers: SpectrumRenderer[] = [
  33. new BarSpectrumRenderer(),
  34. new WaveSpectrumRenderer(),
  35. new CircleSpectrumRenderer(),
  36. new ParticleSpectrumRenderer()
  37. ]
  38. private timerId: number = -1
  39. onVisibilityChange(): void {
  40. this.updateRenderingState()
  41. }
  42. onPlayingChange(): void {
  43. this.updateRenderingState()
  44. }
  45. aboutToAppear(): void {
  46. // 读取持久化的特效偏好
  47. const savedMode: number | undefined = AppStorage.get<number>('spectrumMode')
  48. if (savedMode !== undefined && savedMode >= 0 && savedMode <= 3) {
  49. this.currentMode = savedMode
  50. }
  51. this.updateRenderingState()
  52. Logger.info(TAG, 'SpectrumView aboutToAppear, mode=' + this.currentMode)
  53. }
  54. aboutToDisappear(): void {
  55. this.stopRendering()
  56. }
  57. private startRendering(): void {
  58. if (this.timerId !== -1) return
  59. Logger.info(TAG, 'startRendering')
  60. this.timerId = setInterval(() => {
  61. this.renderFrame()
  62. }, FRAME_INTERVAL)
  63. }
  64. private stopRendering(): void {
  65. if (this.timerId !== -1) {
  66. clearInterval(this.timerId)
  67. this.timerId = -1
  68. Logger.info(TAG, 'stopRendering')
  69. }
  70. }
  71. private renderFrame(): void {
  72. const player: IjkMediaPlayer = IjkMediaPlayer.getInstance()
  73. const pcmData: number[] = player.getSpectrumData()
  74. if (pcmData.length === 0) return
  75. const spectrumData = this.fftAnalyzer.analyze(pcmData, 44100)
  76. const rawBands: number[] = spectrumData.getBands(BAND_COUNT)
  77. const bands: number[] = this.fftAnalyzer.smooth(rawBands)
  78. const energy: number = spectrumData.getEnergy()
  79. const ctx: CanvasRenderingContext2D = this.canvasContext
  80. const renderer: SpectrumRenderer = this.renderers[this.currentMode]
  81. const brandColor: string = normalizeHexColor(this.themeColor, '#FF0050')
  82. renderer.render(ctx, bands, energy, this.canvasWidth, this.canvasHeight, brandColor)
  83. }
  84. private updateRenderingState(): void {
  85. if (this.isVisible && this.isPlaying) {
  86. this.startRendering()
  87. } else {
  88. this.stopRendering()
  89. }
  90. }
  91. private switchMode(mode: number): void {
  92. if (mode === this.currentMode) return
  93. this.renderers[this.currentMode].reset()
  94. this.currentMode = mode
  95. AppStorage.setOrCreate<number>('spectrumMode', mode)
  96. this.fftAnalyzer.reset()
  97. // 清空 Canvas
  98. this.canvasContext.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
  99. Logger.info(TAG, 'switchMode to ' + MODE_NAMES[mode])
  100. }
  101. build() {
  102. Stack({ alignContent: Alignment.Center }) {
  103. Canvas(this.canvasContext)
  104. .width('100%')
  105. .height('100%')
  106. .backgroundColor(Color.Transparent)
  107. .onReady(() => {
  108. this.canvasContext.imageSmoothingEnabled = true
  109. this.canvasContext.imageSmoothingQuality = 'medium'
  110. })
  111. .onAreaChange((_oldVal: Area, newVal: Area) => {
  112. this.canvasWidth = newVal.width as number
  113. this.canvasHeight = newVal.height as number
  114. })
  115. }
  116. .width('100%')
  117. .height('100%')
  118. .onVisibleAreaChange([0.0, 1.0], (_isVisible: boolean, currentRatio: number) => {
  119. if (currentRatio <= 0.5) {
  120. this.stopRendering()
  121. return
  122. }
  123. this.updateRenderingState()
  124. })
  125. }
  126. }