一、应用概述

1.1 应用简介

音乐播放器(Music Player)是一款模拟音乐播放应用,支持歌曲列表浏览、播放/暂停控制、上一曲/下一曲切换、进度调节、随机/循环播放模式、收藏管理、音量控制和播放列表管理。该应用以HarmonyOS NEXT的ArkTS框架为基础,深入展示了播放控制逻辑、状态管理、列表渲染、播放模式算法、收藏管理和用户交互设计等关键技术。

1.2 核心功能

功能模块 功能描述 技术实现 设计考量
歌曲列表 浏览所有歌曲 LazyForEach列表渲染 虚拟列表优化
播放控制 播放/暂停/上一曲/下一曲 状态机管理 无缝切换
进度条 歌曲进度显示与调节 Slider组件 + 时间格式化 拖拽跳转
播放模式 顺序/随机/单曲循环 状态切换算法 3种模式
收藏功能 收藏喜欢的歌曲 Set集合管理 持久化存储
音量控制 音量调节 Slider组件 系统音量联动
播放列表 创建/编辑播放列表 列表管理 拖拽排序
歌曲搜索 按名称/歌手搜索 字符串匹配 实时过滤

1.3 应用架构

音乐播放器应用采用分层架构:

  1. UI表现层:歌曲列表、播放控制栏、进度条、音量控制、播放列表管理。
  2. 业务逻辑层:播放管理器、播放模式算法、收藏管理器、播放列表管理器。
  3. 数据持久层:使用Preferences API存储收藏数据、播放列表和设置。

二、播放控制

2.1 播放器状态机

enum PlaybackState {
  IDLE = 'idle',
  PLAYING = 'playing',
  PAUSED = 'paused',
  STOPPED = 'stopped'
}

enum PlaybackMode {
  SEQUENTIAL = 'sequential', // 顺序播放
  SHUFFLE = 'shuffle',       // 随机播放
  REPEAT_ONE = 'repeat_one'  // 单曲循环
}

interface Song {
  id: string;
  title: string;
  artist: string;
  album: string;
  duration: number;       // 时长(秒)
  coverColor: string;     // 封面颜色
  genre: string;
  year: number;
  isFavorite: boolean;
}

interface Playlist {
  id: string;
  name: string;
  description: string;
  songs: string[];        // 歌曲ID列表
  createdAt: number;
  updatedAt: number;
}

class MusicPlayerEngine {
  @State playbackState: PlaybackState = PlaybackState.IDLE;
  @State currentSongIndex: number = 0;
  @State currentTime: number = 0;    // 当前播放进度(秒)
  @State duration: number = 0;       // 当前歌曲时长(秒)
  @State volume: number = 80;        // 音量 0-100
  @State playbackMode: PlaybackMode = PlaybackMode.SEQUENTIAL;
  @State isFavorite: boolean = false;
  
  private playlist: Song[] = [];
  private shuffleHistory: number[] = []; // 随机播放历史
  private timerId: number | null = null;
  
  // 播放指定歌曲
  playSong(index: number): void {
    if (index < 0 || index >= this.playlist.length) return;
    
    this.currentSongIndex = index;
    this.currentTime = 0;
    this.duration = this.playlist[index].duration;
    this.playbackState = PlaybackState.PLAYING;
    this.isFavorite = this.playlist[index].isFavorite;
    
    this.startProgressTimer();
  }
  
  // 播放/暂停切换
  togglePlayPause(): void {
    if (this.playbackState === PlaybackState.PLAYING) {
      this.pause();
    } else {
      this.resume();
    }
  }
  
  private pause(): void {
    this.playbackState = PlaybackState.PAUSED;
    this.stopProgressTimer();
  }
  
  private resume(): void {
    if (this.playbackState === PlaybackState.PAUSED) {
      this.playbackState = PlaybackState.PLAYING;
      this.startProgressTimer();
    } else if (this.playbackState === PlaybackState.IDLE && this.playlist.length > 0) {
      this.playSong(0);
    }
  }
  
  // 下一曲
  nextSong(): void {
    const nextIndex = this.getNextIndex();
    this.playSong(nextIndex);
  }
  
  // 上一曲
  previousSong(): void {
    // 如果当前进度超过3秒,重新播放当前歌曲
    if (this.currentTime > 3) {
      this.currentTime = 0;
      return;
    }
    
    const prevIndex = this.getPreviousIndex();
    this.playSong(prevIndex);
  }
  
  // 根据播放模式获取下一首索引
  private getNextIndex(): number {
    switch (this.playbackMode) {
      case PlaybackMode.SEQUENTIAL:
        return (this.currentSongIndex + 1) % this.playlist.length;
      case PlaybackMode.SHUFFLE:
        return this.getShuffleNextIndex();
      case PlaybackMode.REPEAT_ONE:
        return this.currentSongIndex; // 重复当前
      default:
        return (this.currentSongIndex + 1) % this.playlist.length;
    }
  }
  
  private getPreviousIndex(): number {
    return (this.currentSongIndex - 1 + this.playlist.length) % this.playlist.length;
  }
  
  private getShuffleNextIndex(): number {
    // 随机播放但不重复已播放的歌曲
    const available = this.playlist
      .map((_, i) => i)
      .filter(i => !this.shuffleHistory.includes(i) && i !== this.currentSongIndex);
    
    if (available.length === 0) {
      this.shuffleHistory = [this.currentSongIndex];
      return this.getShuffleNextIndex();
    }
    
    const randomIndex = available[Math.floor(Math.random() * available.length)];
    this.shuffleHistory.push(randomIndex);
    return randomIndex;
  }
  
  // 进度更新
  private startProgressTimer(): void {
    this.stopProgressTimer();
    this.timerId = setInterval(() => {
      this.currentTime++;
      if (this.currentTime >= this.duration) {
        if (this.playbackMode === PlaybackMode.REPEAT_ONE) {
          this.currentTime = 0;
        } else {
          this.nextSong();
        }
      }
    }, 1000);
  }
  
  private stopProgressTimer(): void {
    if (this.timerId !== null) {
      clearInterval(this.timerId);
      this.timerId = null;
    }
  }
  
  // 跳转到指定进度
  seekTo(time: number): void {
    this.currentTime = Math.max(0, Math.min(time, this.duration));
  }
  
  // 格式化时间
  formatTime(seconds: number): string {
    const mins = Math.floor(seconds / 60);
    const secs = Math.floor(seconds % 60);
    return `${mins}:${secs.toString().padStart(2, '0')}`;
  }
}

三、收藏与播放列表管理

3.1 收藏管理器

class FavoriteManager {
  private favorites: Set<string> = new Set();
  private prefs: preferences.Preferences | null = null;
  
  async init(context: Context): Promise<void> {
    this.prefs = await preferences.getPreferences(context, 'music_prefs');
    await this.load();
  }
  
  toggleFavorite(songId: string): boolean {
    if (this.favorites.has(songId)) {
      this.favorites.delete(songId);
      this.save();
      return false;
    } else {
      this.favorites.add(songId);
      this.save();
      return true;
    }
  }
  
  isFavorite(songId: string): boolean {
    return this.favorites.has(songId);
  }
  
  getFavoriteSongs(songs: Song[]): Song[] {
    return songs.filter(s => this.favorites.has(s.id));
  }
  
  getFavoriteCount(): number {
    return this.favorites.size;
  }
  
  private async load(): Promise<void> {
    if (!this.prefs) return;
    const json = await this.prefs.get('favorites', '[]');
    try { this.favorites = new Set(JSON.parse(json)); } catch { this.favorites = new Set(); }
  }
  
  private async save(): Promise<void> {
    if (!this.prefs) return;
    await this.prefs.put('favorites', JSON.stringify(Array.from(this.favorites)));
    await this.prefs.flush();
  }
}

四、UI交互设计

4.1 播放控制栏

@Component
struct PlayerControlBar {
  @Link playbackState: PlaybackState;
  @Link currentTime: number;
  @Link duration: number;
  @Link playbackMode: PlaybackMode;
  @Link volume: number;
  @Link currentSong: Song | null;
  
  onPlayPause: (() => void) | null = null;
  onNext: (() => void) | null = null;
  onPrevious: (() => void) | null = null;
  onSeek: ((time: number) => void) | null = null;
  onModeChange: (() => void) | null = null;
  
  build() {
    Column() {
      // 进度条
      Slider({
        value: this.currentTime,
        min: 0,
        max: this.duration,
        step: 1
      })
        .width('100%')
        .onChange((value: number) => {
          this.onSeek?.(value);
        })
      
      Row() {
        Text(this.formatTime(this.currentTime))
          .fontSize(12)
          .fontColor('#666666')
        Text(this.formatTime(this.duration))
          .fontSize(12)
          .fontColor('#666666')
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceBetween)
      
      // 控制按钮
      Row() {
        Button(this.getModeIcon())
          .fontSize(20)
          .backgroundColor('transparent')
          .onClick(() => { this.onModeChange?.(); })
        
        Button('⏮')
          .fontSize(24)
          .backgroundColor('transparent')
          .onClick(() => { this.onPrevious?.(); })
        
        Button(this.playbackState === PlaybackState.PLAYING ? '⏸' : '▶️')
          .fontSize(32)
          .width(56)
          .height(56)
          .backgroundColor('#4CAF50')
          .borderRadius(28)
          .onClick(() => { this.onPlayPause?.(); })
        
        Button('⏭')
          .fontSize(24)
          .backgroundColor('transparent')
          .onClick(() => { this.onNext?.(); })
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceEvenly)
      .padding(8)
    }
    .padding(16)
    .backgroundColor('#FFFFFF')
    .borderRadius(16)
    .shadow({ radius: 8, color: '#20000000', offsetY: -4 })
  }
  
  private getModeIcon(): string {
    switch (this.playbackMode) {
      case PlaybackMode.SEQUENTIAL: return '🔁';
      case PlaybackMode.SHUFFLE: return '🔀';
      case PlaybackMode.REPEAT_ONE: return '🔂';
    }
  }
  
  private formatTime(seconds: number): string {
    const m = Math.floor(seconds / 60);
    const s = Math.floor(seconds % 60);
    return `${m}:${s.toString().padStart(2, '0')}`;
  }
}

五、总结

5.1 核心技术要点

  1. 播放控制状态机:IDLE/PLAYING/PAUSED/STOPPED四种状态完整转换。
  2. 三种播放模式:顺序播放、随机播放(不重复历史)、单曲循环。
  3. 进度管理:支持拖拽跳转,实时更新播放进度。
  4. 收藏管理:基于Set的收藏管理,配合Preferences持久化。
  5. 列表渲染:使用LazyForEach优化长列表性能。
  6. 搜索过滤:按歌曲名和歌手的实时搜索。

5.2 扩展方向

  1. 歌词显示:同步滚动歌词显示。
  2. 专辑封面:显示歌曲专辑封面图片。
  3. 均衡器:自定义音效调节。
  4. 歌单管理:创建和管理多个播放列表。
  5. 在线音乐:接入在线音乐API,搜索和播放网络歌曲。

5.3 核心代码量统计

模块 核心代码行数 接口数 组件数
播放引擎 180 10 -
收藏管理器 70 5 -
播放列表管理器 100 6 -
UI组件 280 5 5
总计 630 26 5

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐