音乐流媒体应用开发:HarmonyOS Next实战教程 原创

wuyanghcoa
发布于 2024-11-30 13:24
浏览
0收藏

前言

在数字化时代,音乐已经成为我们生活中不可或缺的一部分。本文将带领大家探讨如何基于HarmonyOS Next开发一个仿网易云音乐APP,从音频播放的核心技术到自定义播放组件的开发,我们将重点讨论音频播放的基本界面和功能、播放原理,以及如何进行鸿蒙网络访问和歌曲列表的构建。

本文主要介绍歌曲列表的获取和歌单、排行榜的展示。

实现效果

音乐流媒体应用开发:HarmonyOS Next实战教程-鸿蒙开发者社区

具体实现

主页面

流程图如下:

flowchart TD
    Start[初始化] --> LoadSelfInfo[加载用户信息]
    LoadSelfInfo --> LoadRecommendSongs[加载推荐歌曲]
    LoadRecommendSongs --> LoadArrayImage[加载推荐歌曲图片]
    LoadArrayImage --> LoadTopSongs[加载热门歌曲]
    LoadTopSongs --> LoadRankBlock[加载排行榜]
    LoadRankBlock --> PageReady[页面准备就绪]

    subgraph LoadSelfInfo
        SetImageOn[设置图片开关] --> LoadUserInfo[加载用户信息]
        LoadUserInfo --> LoadLikeList[加载喜欢的歌曲列表]
    end

    subgraph LoadRecommendSongs
        RequestRecommendSongs[请求推荐歌曲] --> ParseRecommendSongs[解析推荐歌曲数据]
        ParseRecommendSongs --> AddRecommendSongs[添加推荐歌曲]
    end

    subgraph LoadTopSongs
        RequestTopSongs[请求热门歌曲] --> ParseTopSongs[解析热门歌曲数据]
        ParseTopSongs --> AddTopSongs[添加热门歌曲]
    end

    subgraph LoadRankBlock
        RequestRankList[请求排行榜列表] --> ParseRankList[解析排行榜数据]
        ParseRankList --> LoadRankSongs[加载排行榜歌曲]
        LoadRankSongs --> AddRankSongs[添加排行榜歌曲]
    end

    subgraph PageReady
        ShowPage[显示页面] --> HandleTabChange[处理标签页切换]
        HandleTabChange --> UpdateLikeList[更新喜欢的歌曲列表]
    end

受篇幅限制,只展示包含歌曲榜单和推荐的页面部分实现

...
Navigation(this.page_info){
Tabs({barPosition:BarPosition.End,controller:this.tabs_controller}){
TabContent(){

  Column(){
    //搜索栏
    TextInput({placeholder:'Search'}).height('7%')
      .onChange((value:string)=>{
        this.Search_cont = value
      })
      .onSubmit(()=>{
        //跳转到搜索页面
        const param:os = new os(this.Search_cont,this.page_info)
        param.calling_back = ()=>{this.current_song = Static_Config.current_song}
        const aim_info:NavPathInfo = new NavPathInfo('Search_page',param)
        this.page_info.pushDestination(aim_info)
      })

    Column(){   //main content here
      List(){

        ListItem(){
          Text("Hello,"+this.current_user.user_name).maxFontSize(40).minFontSize(15).width('100%').height('7%').padding(10)
            .onClick(async ()=>{
              let result:string[] = fileIo.listFileSync(this.fileDir)
              for(let i=0;i!=result.length;i++){
                console.info('delete: '+result[i])
                fileIo.unlink(this.fileDir+'/'+result[i])
              }
            })
        }.margin({top:'1.5%'})

        //轮播图
        ListItem() {
          banner_swiper()
        }

        //推荐榜单
        ListItem(){
          List(){
            ForEach(this.Rank_block_list,(item:rank_block)=>{
              ListItem(){
                suggest_block(item)
              }.padding(5).onClick(()=>{
                let dialog_controller:CustomDialogController|null = new CustomDialogController({
                  builder: list_dialog({
                    value:item,
                    calling_back:()=>{this.current_song = Static_Config.current_song},
                  }),
                  alignment:DialogAlignment.Bottom,
                  offset:{dx:0,dy:-100},
                  shadow:{radius:50,color:Color.Red,offsetX:30,offsetY:-30}
                })
                dialog_controller.open()
                dialog_controller = null
              })
            },(item:rank_block)=>item.rank_id.toString())
          }.listDirection(Axis.Horizontal).width('100%').height($r('app.string.suggest_module_height'))
          .scrollBar(BarState.Off)
        }

        //每日推荐模块
        ListItem(){
          Text(this.title_daily_suggest)
            .maxFontSize(30)
            .minFontSize(15)
            .width('100%')
            .height('6%')
            .padding(5)
            .onClick(()=>{
              for(let i=0;i!=this.recommend_songs.length;i++){
                const now:Song = this.recommend_songs[i]
                fileIo.unlink(this.fileDir+'/'+now.id.toString()+'.jpg')
              }
            })
        }
        .margin({top:'2'})

        ListItem(){
          Text()
            .width('100%')
            .height(1)
            .borderWidth(1)
            .borderColor(Configuration.text_underline_color)
        }

        if(this.recommend_finish){
          ListItem(){
            List(){
              ForEach(this.recommend_songs,(item:Song)=>{
                ListItem(){
                  song_item({song:item}).padding(2)
                    .onClick(()=>{
                      this.switch_and_play(item)
                    })
                }.width('80%')
              },(item:Song)=>item.id.toString())
            }.listDirection(Axis.Horizontal).lanes(3).height('20%').scrollBar(BarState.Off)
          }
        }

        //英文热门歌曲
        ListItem(){
          Text(this.title_new_E_song).maxFontSize(30).minFontSize(15).width('100%').height('6%').padding(5)
            .onClick(()=>{
              player.Start_play('https://music.163.com/song/media/outer/url?id=2046938156.mp3')
            })
        }.margin({top:'2'})

        ListItem(){
          Text()
            .width('100%')
            .height(1)
            .borderWidth(1)
            .borderColor(Configuration.text_underline_color)
        }

        ListItem(){
          List(){
            ForEach(this.Top_english_songs,(item:Song)=>{
              ListItem(){
                song_item({song:item}).padding(2)
                  .onClick(()=>{
                    this.switch_and_play(item)
                  })
              }.width('80%')
            },(item:Song)=>item.id.toString())
          }.listDirection(Axis.Horizontal).lanes(3).height('20%').scrollBar(BarState.Off)
        }

        //New Chinese song
        ListItem(){
          Text(this.title_new_C_song)
            .maxFontSize(30)
            .minFontSize(15)
            .width('100%')
            .height('6%')
            .padding(5)
        }.margin({top:'2'})

        ListItem(){
          Text()
            .width('100%')
            .height(1)
            .borderWidth(1)
            .borderColor(Configuration.text_underline_color)
        }

        ListItem(){
          List(){
            ForEach(this.Top_Chinese_songs,(item:Song)=>{
              ListItem(){
                song_item({song:item}).padding(2)
                  .onClick(()=>{
                    this.switch_and_play(item)
                  })
              }.width('80%')
            },(item:Song)=>item.id.toString())
          }.listDirection(Axis.Horizontal).lanes(3).height('20%').scrollBar(BarState.Off)
        }

        //最近常听:Not finish yet
        ListItem(){
          Text(this.Usually_module).maxFontSize(30).minFontSize(15).width('100%').height('6%').padding(5)
        }.margin({top:'8'})

        /*ListItem(){
          List(){
            ForEach(this.Suggest_song_list,(id:string)=>{
              ListItem(){
                song_item({shape:1})
                  .padding(2)
              }.width($r('app.string.song_block_square_size')).height($r('app.string.song_block_square_size'))
            },(id:string)=>id)
          }.listDirection(Axis.Horizontal).scrollBar(BarState.Off)
        }*/

      }
    }.height('86%').width('100%')

    //Play column
    play_column({song_item:this.current_song}).height(Size_Data.play_column_size).width('100%').padding(2)
      .onClick(()=>{
        const param = new os(this.Search_cont,this.page_info) as object
        const aim_info = new NavPathInfo('Song_page',param)
        this.page_info.pushDestination(aim_info)
        // this.page_info.pushPath({name:'Search_page',param:new os(this.Search_cont)})
      })

  }.width('100%')

}.tabBar(this.tab_bar_builder('Main',0,$r('app.media.Main'),$r('app.media.Main_Chosen')))
...

排行榜组件

实现效果:

音乐流媒体应用开发:HarmonyOS Next实战教程-鸿蒙开发者社区

具体实现:

import {song_block,song_item} from '../Build/song_build'
import {suggest_block} from '../Build/Suggest_build'
import {play_column} from '../Build/Play_column'
import {Introduction_block as Ib,user_info} from '../Build/Self_page'
import {Song,Static_Config,Page_transmission as os} from '../Class_def/song_def'
import {Size_Data} from '../Common/Constant/Size_data'
import {Order} from '../Common/Constant/Order'
import http from "@ohos.net.http"

//存储排行榜数据,每个block对应一个排行榜
@Observed
export class rank_block{
  rank_id:number|string
  rank_name:string
  rank_des:string
  rank_songs:Array<Song> = []

  constructor(_id?:number|string,_name?:string,_des?:string) {
    this.rank_id = _id==undefined?-1:_id
    this.rank_name = _name==undefined?"":_name
    this.rank_des = _des==undefined?"":_des
  }

  get_rank_songs(){
    let http_request = http.createHttp()
    let response = http_request.request(
      Order.url + Order.play_list_all + this.rank_id.toString()
    );

    response.then((data)=> {
      if (data.responseCode == 200) {
        let res = data.result as string
        let json_res = JSON.parse(res) as object
        let songs = (json_res as object)?.["songs"] as Array<object>
        for(let i=0;i!=(songs as Array<object>).length && i!=10;i++){
          let song_info = (songs as Array<object>)[i] as object
          let song_id:string = (song_info as object)?.["id"]
          let song_name = (song_info as object)?.["name"] as string
          let ar = (song_info as object)?.["ar"] as Array<object>
          let aim_ar = (ar as Array<object>)[0] as object
          let singer_id:string = (aim_ar as object)?.["id"]
          let singer_name = (aim_ar as object)?.["name"] as string
          let image = ((song_info as object)?.["al"] as object)?.["picUrl"] as string
          this.rank_songs.push(new Song(song_id,song_name,singer_id,singer_name,image))
          console.info("debug",song_name,this.rank_songs.length,this.rank_songs[i].image)
        }
      }
    })
  }

  load_songs(data:object){
    this.rank_id = (data as object)?.["id"] as string
    this.rank_name = (data as object)?.["name"] as string
    this.rank_des = (data as object)?.["description"] as string
    console.info("debug init finished: ",this.rank_id.toString())
  }

  push_new_song(new_song:Song){
    this.rank_songs.push(new_song)
  }

  song_num():number {
    return this.rank_songs.length
  }
}

推荐歌单组件

实现效果:

音乐流媒体应用开发:HarmonyOS Next实战教程-鸿蒙开发者社区

具体实现:

import {song_block,song_item} from '../Build/song_build'
import {suggest_block} from '../Build/Suggest_build'
import {play_column} from '../Build/Play_column'
import {Introduction_block as Ib,user_info} from '../Build/Self_page'
import {Song,Static_Config,Page_transmission as os} from '../Class_def/song_def'
import {Size_Data} from '../Common/Constant/Size_data'
import {Order} from '../Common/Constant/Order'
import {rank_block} from '../Class_def/Ranking_list'
import { webview } from '@kit.ArkWeb'
import player from '../Api/AVPlayer'

@CustomDialog
@Component
export struct web_dialog{
  dialog_controller?:CustomDialogController
  web_controller:webview.WebviewController = new webview.WebviewController()
  singer_id:string = ""
  build() {
    Column(){
      Web({ src:Order.Get_singer_web(this.singer_id),
            controller:this.web_controller,
            renderMode:RenderMode.ASYNC_RENDER})
        .width('100%')
        .height('100%')
        .onlineImageAccess(true)
    }
    .width('100%')
    .height(Size_Data.dialog_height)
  }
}

@CustomDialog
@Component
export struct list_dialog{
  dialog_controller?:CustomDialogController
  @Prop value:rank_block
  Position:string = '/data/storage/el2/base/haps/entry/files/'

  calling_back = ()=>{}

  build() {
    Column(){
      Stack(){
        //Image(fileUri.getUriFromPath(this.Position+this.value.rank_songs[0].id.toString()+'.jpg'))
        Image(this.value.rank_songs[0].image)
          .position({top:15,left:10})
          .clip(true)
          .width(Size_Data.dialog_cover_size)
          .height(Size_Data.dialog_cover_size)
          .autoResize(true)
        Column(){

            //排行榜单名称
            Text(this.value.rank_name)
              .width('100%')
              .height('50%')
              .textAlign(TextAlign.Start)
              .maxLines(1)
              .maxFontSize(25)
              .minFontSize(15)
              .margin({left:5})

            //排行榜单介绍
            Text(this.value.rank_des)
              .width('100%')
              .height('50%')
              .textAlign(TextAlign.Start)
              .fontSize(15)
              .fontColor(Color.Gray)
              .maxLines(3)
              .textOverflow({overflow:TextOverflow.Ellipsis})
              .margin({left:5})

        }
        .width('70%')
        .height(Size_Data.dialog_title_height)
        .position({top:5,right:5})

      }
      .width('100%')

      Blank().width('100%').height(30)

      List(){
        ForEach(this.value.rank_songs,(songs:Song)=>{
          ListItem(){
            song_item({song:songs,shape:0})
              .onClick(()=>{
                Static_Config.current_song = songs
                player.player_reset(Order.Get_song_url(songs.id.toString()))
                this.calling_back()
              })
          }
          .width('100%')
          .height($r('app.string.song_block_height'))
        },(songs:Song)=>songs.id.toString())
      }
      .width('100%')
      .height('60%')
      .scrollBar(BarState.Off)
    }
    .width('100%')
    .height(Size_Data.dialog_height)
    .linearGradient({direction:GradientDirection.Top,colors:[[Color.Red,0.0],[Color.White,0.5]]})
    .renderGroup(true)
  }
}

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
分类
已于2024-12-3 15:44:06修改
收藏
回复
举报
回复
    相关推荐