import { IjkMediaPlayer } from '@ohos/ijkplayer' import { FFTAnalyzer } from '../../common/util/FFTAnalyzer' import { SpectrumRenderer } from './SpectrumRenderer' import { BarSpectrumRenderer } from './BarSpectrumRenderer' import { WaveSpectrumRenderer } from './WaveSpectrumRenderer' import { CircleSpectrumRenderer } from './CircleSpectrumRenderer' import { ParticleSpectrumRenderer } from './ParticleSpectrumRenderer' import { normalizeHexColor } from './SpectrumRenderer' import Logger from '../../common/util/Logger' const TAG: string = 'heanup-SpectrumView' const BAND_COUNT: number = 53 const FRAME_INTERVAL: number = 33 // ~30fps export enum SpectrumMode { BAR = 0, WAVE = 1, CIRCLE = 2, PARTICLE = 3 } const MODE_NAMES: string[] = ['柱状', '波形', '圆形', '粒子'] @Component export struct SpectrumView { @StorageLink('spectrumMode') currentMode: number = 0 @Prop @Watch('onPlayingChange') isPlaying: boolean = false @StorageProp('themeColor') themeColor: string = '#FF0050' @Prop @Watch('onVisibilityChange') isVisible: boolean = false @State showModePanel: boolean = false @State canvasWidth: number = 0 @State canvasHeight: number = 0 private renderSettings: RenderingContextSettings = new RenderingContextSettings(true) private canvasContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.renderSettings) private fftAnalyzer: FFTAnalyzer = new FFTAnalyzer() private renderers: SpectrumRenderer[] = [ new BarSpectrumRenderer(), new WaveSpectrumRenderer(), new CircleSpectrumRenderer(), new ParticleSpectrumRenderer() ] private timerId: number = -1 onVisibilityChange(): void { this.updateRenderingState() } onPlayingChange(): void { this.updateRenderingState() } aboutToAppear(): void { // 读取持久化的特效偏好 const savedMode: number | undefined = AppStorage.get('spectrumMode') if (savedMode !== undefined && savedMode >= 0 && savedMode <= 3) { this.currentMode = savedMode } this.updateRenderingState() Logger.info(TAG, 'SpectrumView aboutToAppear, mode=' + this.currentMode) } aboutToDisappear(): void { this.stopRendering() } private startRendering(): void { if (this.timerId !== -1) return Logger.info(TAG, 'startRendering') this.timerId = setInterval(() => { this.renderFrame() }, FRAME_INTERVAL) } private stopRendering(): void { if (this.timerId !== -1) { clearInterval(this.timerId) this.timerId = -1 Logger.info(TAG, 'stopRendering') } } private renderFrame(): void { const player: IjkMediaPlayer = IjkMediaPlayer.getInstance() const pcmData: number[] = player.getSpectrumData() if (pcmData.length === 0) return const spectrumData = this.fftAnalyzer.analyze(pcmData, 44100) const rawBands: number[] = spectrumData.getBands(BAND_COUNT) const bands: number[] = this.fftAnalyzer.smooth(rawBands) const energy: number = spectrumData.getEnergy() const ctx: CanvasRenderingContext2D = this.canvasContext const renderer: SpectrumRenderer = this.renderers[this.currentMode] const brandColor: string = normalizeHexColor(this.themeColor, '#FF0050') renderer.render(ctx, bands, energy, this.canvasWidth, this.canvasHeight, brandColor) } private updateRenderingState(): void { if (this.isVisible && this.isPlaying) { this.startRendering() } else { this.stopRendering() } } private switchMode(mode: number): void { if (mode === this.currentMode) return this.renderers[this.currentMode].reset() this.currentMode = mode AppStorage.setOrCreate('spectrumMode', mode) this.fftAnalyzer.reset() // 清空 Canvas this.canvasContext.clearRect(0, 0, this.canvasWidth, this.canvasHeight) Logger.info(TAG, 'switchMode to ' + MODE_NAMES[mode]) } build() { Stack({ alignContent: Alignment.Center }) { Canvas(this.canvasContext) .width('100%') .height('100%') .backgroundColor(Color.Transparent) .onReady(() => { this.canvasContext.imageSmoothingEnabled = true this.canvasContext.imageSmoothingQuality = 'medium' }) .onAreaChange((_oldVal: Area, newVal: Area) => { this.canvasWidth = newVal.width as number this.canvasHeight = newVal.height as number }) } .width('100%') .height('100%') .onVisibleAreaChange([0.0, 1.0], (_isVisible: boolean, currentRatio: number) => { if (currentRatio <= 0.5) { this.stopRendering() return } this.updateRenderingState() }) } }