学习例程6.1:HarmonyOS 鸿蒙Next 一个带语音的故事版。

学习例程6.1:HarmonyOS 鸿蒙Next 一个带语音的故事版。 想写个有点意思,既能给孩子玩又能有点儿教育意义的小游戏。

琢磨半天,可以让游戏附带个故事版,讲几句历史故事。

雏形如下。先上图。

图像

代码如下:

import { media } from "@kit.MediaKit"
import { common } from "@kit.AbilityKit"
import { audio } from "@kit.AudioKit"

@ComponentV2
@Entry
export struct testStoryBoard {
  build() {
    Column() {
      ViewStoryBoard()
    }
  }
}

@ComponentV2
@Preview
export struct ViewStoryBoard {
  @Local nowStoryFrame: StoryBoardFrame | undefined = undefined
  @Local nowStoryUnit: StoryBoardUnit | undefined = undefined
  @Local headOpacity: number = 0.1

  buildTest() {
    this.nowStoryFrame = new StoryBoardFrame()
    this.nowStoryFrame.backPicture = $r("app.media.huarongdao")
    let roleCaocao = new Role("曹操", "魏国丞相", $r("app.media.caocaoHeadRight"))
    let roleZhaoyun = new Role("赵云", "蜀国大将", $r("app.media.zhaoyun_head"))
    let unitCaocao = new StoryBoardUnit()
    unitCaocao.name = "曹操说"
    unitCaocao.role = roleCaocao
    unitCaocao.words = "呵呵!吾不笑别人,单笑周瑜无谋,诸葛亮少智。\n若是吾用兵之时,预先在这里伏下一军,如之奈何?"
    unitCaocao.sound = "caocao_01.mp3"
    this.nowStoryFrame.units.push(unitCaocao)

    let unitZhaoyun = new StoryBoardUnit()
    unitZhaoyun.name = "赵云说"
    unitZhaoyun.role = roleZhaoyun
    unitZhaoyun.words = "我赵子龙奉军师将令,在此等候多时了!"
    unitZhaoyun.sound = "zhaoyun_01.mp3"
    this.nowStoryFrame.units.push(unitZhaoyun)
    this.playOneUnit(this.nowStoryFrame, 0)
  }

  async playOneUnit(tframe: StoryBoardFrame, idx: number) {
    this.headOpacity = 0.1
    this.nowStoryUnit = tframe.units[idx]
    console.error("playonunit", this.nowStoryUnit.words)
    if (tframe.units[idx].sound != undefined) {
      let soundRaw = tframe.units[idx].sound as string
      let avPlayer: media.AVPlayer = await media.createAVPlayer();
      // 创建状态机变化回调函数
      avPlayer.on('durationUpdate', (duration: number) => {
        console.info('durationUpdate called,时长:' + duration)
      })
      avPlayer.on('timeUpdate', (times) => {
        console.info('timeUpdate', times)
        if (times > 100) {
          this.headOpacity = 1
          avPlayer.off('timeUpdate')
        }
      })
      console.error("注册 stateChange")
      avPlayer.on('stateChange', async (state: string, reason: media.StateChangeReason) => {
        switch (state) {
          case 'idle': // 成功调用reset接口后触发该状态机上报
            console.info('AVPlayer state idle called.');
            avPlayer.release(); // 调用release接口销毁实例对象
            break;
          case 'initialized': // avplayer 设置播放源后触发该状态上报
            console.info('AVPlayer state initialized called.');
            avPlayer.audioRendererInfo = {
              usage: audio.StreamUsage.STREAM_USAGE_MUSIC,
              rendererFlags: 0
            };
            avPlayer.prepare();
            break;
          case 'prepared': // prepare调用成功后上报该状态机
            console.info('准备好了AVPlayer state prepared called.');
            avPlayer.play(); // 调用播放接口开始播放
            break;
          case "completed":
            console.error(`${soundRaw}播放完毕!`)
            if (idx < tframe.units.length - 1) {
              this.playOneUnit(tframe, idx + 1)
            }
            avPlayer.reset(); // 调用reset接口初始化avplayer状态
            break
        }
      })

      // 通过UIAbilityContext的resourceManager成员的getRawFd接口获取媒体资源播放地址
      // 返回类型为{fd,offset,length},fd为HAP包fd地址,offset为媒体资源偏移量,length为播放长度
      let context = getContext(this) as common.UIAbilityContext;
      let fileDescriptor = await context.resourceManager.getRawFd(soundRaw);
      console.error(`[${soundRaw}]资源id:`, fileDescriptor.fd)
      let avFileDescriptor: media.AVFileDescriptor =
        { fd: fileDescriptor.fd, offset: fileDescriptor.offset, length: fileDescriptor.length };
      // 为fdSrc赋值触发initialized状态机上报
      avPlayer.fdSrc = avFileDescriptor;
    }
  }

  aboutToAppear(): void {
    this.buildTest()
  }

  build() {
    Stack() {
      Image(this.nowStoryFrame?.backPicture)
        .opacity(1)
      Stack() {
        Row() {}
        .width('100%')
        .backgroundColor('black')
        .opacity(0.8)
        .height(80)

        Row() {
          Column() {
            Image(this.nowStoryUnit?.role?.headPicture)
              .width("20%")
              .backgroundColor("#aaaaaa")
              .borderRadius('50%')
              .animation({ curve: Curve.Ease })
              .opacity(this.headOpacity)
            Text(this.nowStoryUnit?.role?.name)
              .fontColor("#aabbcc")
              .fontSize(10)
          }

          if (this.nowStoryUnit?.words) {
            ViewTxtShow({ Words: this.nowStoryUnit?.words })
              .width('80%')
              .margin(5)
          }

        }
      }.width('100%')
      .alignContent(Alignment.Start)

    }
    .width('100%')
    .height('300')
    .alignContent(Alignment.BottomStart)
  }
}

@ObservedV2
export class StoryBoardUnit {
  public name: string = ""
  public role: Role | undefined = undefined
  @Trace
  public words: string = ""
  public duration: number = 2000
  public sound: string | undefined = undefined
  public picture: Resource | string = ""
}

@ObservedV2
export class StoryBoardFrame {
  public units: StoryBoardUnit[] = []
  public backPicture: Resource | string = ""
  public Index: number = -1
}

@ObservedV2
export class Role {
  public name: string = "关羽"
  public introduce: string = "三国时期,蜀国大将。"
  public headPicture: Resource | string = ""

  constructor(tName: string, tIntroduce: string, tHeadPic: Resource | string) {
    this.name = tName
    this.introduce = tIntroduce
    this.headPicture = tHeadPic
  }
}

@ComponentV2
@Preview
export struct ViewTxtShow {
  @Param Words: string = "哈哈!人言周瑜、诸葛亮足智多谋,我看到底是无能之辈。若在此处埋伏一军,我等皆束手受缚矣。"

  @Monitor("Words")
  infoChange(monitor: IMonitor) {
    console.warn(`Words 已修改为`, this.Words);
    this.buidList()
    this.buildStepShow()
  }

  @Local wordList: string[] = []
  @Local wordOpacity: number[] = []

  buidList() {
    console.warn("BuildList")
    this.wordList = []
    this.wordOpacity = []
    for (let i = 0; i < this.Words.length; i++) {
      this.wordList[i] = this.Words[i]
      this.wordOpacity[i] = 0.3
    }
  }

  buildStepShow(idx: number = 0) {
    setTimeout(() => {
      this.wordOpacity[idx] = 1;
      if (idx < this.wordOpacity.length - 1) {
        let tidx = idx + 1;
        this.buildStepShow(tidx)
      }
    }, 301)
  }

  aboutToAppear(): void {
    console.warn("viewTxtShow aboutToAppear", this.Words)
    this.buidList()
    this.buildStepShow()
  }

  onDidBuild(): void {
    console.warn("viewTxtShow onDidBuild", this.Words)
  }

  build() {
    Column() {
      if (this.Words)
      Grid() {
        ForEach(this.wordList, (char: string, idx: number) => {
          GridItem() {
            Text(this.Words[idx])
              .opacity(this.wordOpacity[idx])
              .animation({ curve: Curve.Ease })
              .fontColor('white')

          }
        }, (item: string) => item)
      }
      .direction(Direction.Auto)
      .width("100%")
    }
  }
}

记录。分享。睡午觉。


更多关于学习例程6.1:HarmonyOS 鸿蒙Next 一个带语音的故事版。的实战教程也可以访问 https://www.itying.com/category-93-b0.html

1 回复

更多关于学习例程6.1:HarmonyOS 鸿蒙Next 一个带语音的故事版。的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS鸿蒙Next中,开发一个带语音的故事版应用,主要涉及以下几个技术点:

  1. 语音功能集成:使用鸿蒙系统提供的@ohos.multimedia.audio模块来实现语音播放功能。通过AudioPlayer类可以加载音频文件并进行播放控制,如播放、暂停、停止等操作。

  2. UI设计与布局:利用@ohos.arkui模块中的ComponentLayout来构建用户界面。可以通过Text组件显示故事文本,Button组件实现播放控制按钮,Image组件展示故事插图等。

  3. 事件处理:通过@ohos.arkui中的Event模块来处理用户交互事件,如点击按钮时触发语音播放或暂停操作。

  4. 资源管理:将音频文件、图片等资源放置在resources目录下,并通过$r$rawfile进行引用。

  5. 生命周期管理:在EntryAbility中管理应用的生命周期,确保在应用启动时加载必要的资源,并在应用退出时释放资源。

  6. 调试与测试:使用DevEco Studio进行代码编写、调试和测试,确保应用的功能和性能达到预期。

通过以上技术点的结合,可以开发出一个功能完善的带语音的故事版应用。

回到顶部