【HarmonyOS AI赋能】朗读控件详解 原创

GeorgeGcs
发布于 2025-10-13 00:41
浏览
0收藏

【HarmonyOS AI赋能】朗读控件详解

一、前言

鸿蒙系统提供了系统级别的朗读控件,来实现对文本进行朗读的业务需求。不需要复杂的SDK接入和集成,就可实现商业级别的朗读效果。

朗读控件分为听筒组件朗读控制器,以及朗读面板三部分组成。
朗读面板又分为吸边小面板全屏朗读面板

需要注意的是,仅支持中国境内(不包含中国香港、中国澳门、中国台湾)提供服务。并且实时朗读的正文信息长度10000字符以内。
【HarmonyOS AI赋能】朗读控件详解-鸿蒙开发者社区

二、如何使用朗读控件?

以下代码为上图所示的DEMO源码,可直接新建工程后,贴到index.ets类中,启自动签名后,启动查看效果。下面为大家详细拆解如何使用。

// 导入语音朗读相关的组件和类型
import { TextReader, TextReaderIcon, ReadStateCode } from '@kit.SpeechKit';

@Entry
@Component
struct Index {

  /**
   * 待加载的文章列表
   */
  @State readInfoList: TextReader.ReadInfo[] = [];

  /**
   * 当前选中的文章
   */
  @State selectedReadInfo: TextReader.ReadInfo = this.readInfoList[0];

  /**
   * 朗读状态
   */
  @State readState: ReadStateCode = ReadStateCode.WAITING;

  /**
   * 初始化状态标记
   */
  @State isInit: boolean = false;

  // 组件即将显示时触发
  async aboutToAppear(){
    /**
     * 模拟加载文章数据
     */
    let readInfoList: TextReader.ReadInfo[] = [{
      id: '001',
      title: {
        text:'水调歌头.明月几时有',
        isClickable:true
      },
      author:{
        text:'宋.苏轼',
        isClickable:true
      },
      date: {
        text:'2024/01/01',
        isClickable:false
      },
      bodyInfo: '明月几时有?把酒问青天。不知天上宫阙,今夕是何年?'
    }];

    // 更新状态变量
    this.readInfoList = readInfoList;
    this.selectedReadInfo = this.readInfoList[0];

    // 初始化朗读组件
    this.init();
  }

  /**
   * 初始化朗读组件
   */
  async init() {
    // 朗读参数配置
    const readerParam: TextReader.ReaderParam = {
      isVoiceBrandVisible: true, // 显示品牌信息
      businessBrandInfo: {
        panelName: '小艺朗读', // 面板名称
        panelIcon: $r('app.media.startIcon') // 面板图标
      }
    }

    try {
      // 获取上下文
      let context: Context | undefined = this.getUIContext().getHostContext()
      if (context) {
        // 初始化朗读组件
        await TextReader.init(context, readerParam);
        this.isInit = true; // 标记初始化完成
        this.setActionListener(); // 设置事件监听
      }
    } catch (err) {
      // 初始化失败时打印错误信息
      console.error(`TextReader failed to init. Code: ${err.code}, message: ${err.message}`);
    }
  }

  // 设置朗读事件监听
  setActionListener() {
    // 监听朗读状态变化
    TextReader.on('stateChange', (state: TextReader.ReadState) => {
      this.onStateChanged(state);
    });

    // 监听加载更多请求
    TextReader.on('requestMore', () => {
      TextReader.loadMore([], true);
    })
  }

  // 处理朗读状态变化
  onStateChanged = (state: TextReader.ReadState) => {
    // 只处理当前选中文章的状态变化
    if (this.selectedReadInfo?.id === state.id) {
      this.readState = state.state;
    } else {
      this.readState = ReadStateCode.WAITING;
    }
  }

  // 构建UI界面
  build() {
    Column() {
      // 朗读状态图标
      TextReaderIcon({ readState: this.readState })
        .margin({ right: 20 })
        .width(32)
        .height(32)
        .onClick(async () => {
          // 点击图标时开始朗读
          try {
            await TextReader.start(this.readInfoList, this.selectedReadInfo?.id);
          } catch (err) {
            // 朗读失败时打印错误信息
            console.error(`TextReader failed to start. Code: ${err.code}, message: ${err.message}`);
          }
        })
    }
    .height('100%')
  }
}

(1)听筒控件TextReaderIcon
提供的听筒控件,可以同步朗读状态,如上动态图所示,有现成的朗读效果,如果业务需要使用,可以用。或者直接跳过也可以,控件参数比较简单,如下代码所示:

 TextReaderIcon({ readState: this.readState })
        .width(32)
        .height(32)
        .onClick(async () => {
					// do something...
        })

readState 需要通过朗读控制器TextReader去监听,当前的朗读状态,然后设置给朗读控件,就可以实现朗读控件的动态效果。

    TextReader.on('stateChange', (state: TextReader.ReadState) => {
			
    });

并且根据DEMO代码可发现,听筒控件的点击事件,触发了朗读控制器对象的开启操作。

综上所述,我们可以不使用话筒控件,直接使用朗读控制器,调用其接口实现文本朗读的效果。

(2)朗读控制器TextReader
TextReader是整个朗读操作逻辑的核心操作对象,系统接口提供了该单例对象。使用之前需要先初始化:

    // 朗读参数配置
    const readerParam: TextReader.ReaderParam = {
      isVoiceBrandVisible: true, // 显示品牌信息
      businessBrandInfo: {
        panelName:  '朗读', // 面板名称
      },
      isMinibarNeeded: true
    }
    await TextReader.init(context, readerParam);

然后再进行常规的启动,暂停(pause),销毁暂停(stop)【ps: 我现在对系统接口,这种类似双暂停的命名很无语 = =。猛地看起来,两个暂停,傻傻分不清楚。但是目前stop后者,多用于整个生命周期回收重置的调用处理。】:

 // 朗读启动配置
    const startParams: TextReader.StartParams = {
      isMinibarHidden: this.mTextReaderInitData?.isMinibarNeeded ?? true,
    }
    // 填充朗读内容
    let readInfoList: TextReader.ReadInfo[] = [{
        id: '002',
        title: {
          text:'水调歌头.明月几时有2',
          isClickable:true
        },
        author:{
          text:'宋.苏轼2',
          isClickable:true
        },
        date: {
          text:'2025/02/02',
          isClickable:false
        },
        bodyInfo: '2明月几时有?把酒问青天。不知天上宫阙,今夕是何年?'
      }];
    // 启动朗读
    await TextReader.start(readInfoList, this.readInfoList[0].id, startParams);

再之后进行根据业务需求,做一些监听和反监听的处理了,种类很多详情参见api接口:

    TextReader.on('stateChange', (state: TextReader.ReadState) => {

    });

(3)朗读面板
关于朗读面板,我理解是通过子窗口来实现,吸边小面板和全屏面板的效果。因为文档中有强调使用朗读控件初始化前,需要使用windowManager进行舞台窗口对象的注入(
WindowManager.setWindowStage(windowStage);):

import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { WindowManager } from '@kit.SpeechKit';


export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);

  }

  onWindowStageCreate(windowStage: window.WindowStage): void {

    WindowManager.setWindowStage(windowStage);
    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        return;
      }
    });
  }

}

【HarmonyOS AI赋能】朗读控件详解-鸿蒙开发者社区
但是在实际使用中我发现,即使不调用该注入方法。初始化也不会报错。目前已提交工单,后续结论同步到该文章。

朗读面板的操作逻辑很简单,分别提供了显示和隐藏两个面板(吸边小面板和全屏面板)的属性或者接口,来控制显隐。

吸边小面板可通过属性和方法分别设置显隐:

首先是在初始化配置参数中:
    const readerParam: TextReader.ReaderParam = {
      isMinibarNeeded:  true
    }
    
其次是在启动配置参数中:
    const startParams: TextReader.StartParams = {
      isMinibarHidden:  true,
    }
    
再之后就是方法接口:
    TextReader.showMinibar();
    TextReader.hideMinibar();

全屏朗读面板默认启动朗读后就会显示,系统提供了两套接口,可以在start后调用hide就可隐藏全屏朗读面板:

    TextReader.hidePanel();
    TextReader.showPanel();
    await TextReader.start(readInfoList, this.mCurrentReadInfo.id);
    TextReader.hidePanel();

三、工具类封装源码共享:

封装ReaderIconView朗读图标,联动管理类的朗读状态,即插即用。

import { ReadStateCode, TextReaderIcon } from "@kit.SpeechKit";
import { TextReaderMgr, TextReaderRegister } from "../mgr/TextReaderMgr";
import { common } from "@kit.AbilityKit";

@Component
export struct ReaderIconView {
  private TAG: string = "ReaderIconView";

  /**
   * 朗读状态
   */
  @State readState: ReadStateCode = ReadStateCode.WAITING;

  private mTextReaderRegister: TextReaderRegister = {
    onStateChange: (state: ReadStateCode): void => {
      this.readState = state;
      console.log(this.TAG, "mTextReaderRegister onStateChange state: " + state);
    }
  }

  aboutToAppear(): void {
    const context = getContext(this) as common.UIAbilityContext;
    TextReaderMgr.Ins().initReader(context, null, null, this.mTextReaderRegister);
    console.log(this.TAG, " aboutToAppear initReader done");
  }


  build() {
    TextReaderIcon({ readState: this.readState })
      .width("100%")
      .height("100%")
  }

}

封装单例朗读管理类,用于便捷操作朗读相关接口,封装细节,方便快速调用:

import { ReadStateCode, TextReader } from "@kit.SpeechKit";

/**
 * 初始化配置对象
 */
export class TextReaderInitData {
  // 全屏面板标题名称
  panelName: string = "";
  // 是否需要吸边小面板
  isMinibarNeeded: boolean = true;
  // 是否需要全屏面板
  isPanelNeeded: boolean = true;
}

/**
 * 控制器操作回调
 */
export interface TextReaderCall {
  onReady: () => void
  onInitFail: (err: string) => void
  onFail: (err: string) => void
}

/**
 * 监听回调
 */
export interface TextReaderRegister {
  onStateChange: (state: ReadStateCode) => void
}

/**
 * 错误码
 */
export enum TextReaderFail {
  UnInit = "0",
  TextReaderInfoNULL = "1"
}

/**
 * 文本朗读对象
 */
export class TextReaderInfo {
  title: string = "";
  content: string = "";
  author?: string = "";
  date?: string = "";
}

/**
 * 文本朗读管理类
 */
export class TextReaderMgr {
  private TAG: string = "TextReaderMgr";
  private static mTextReaderMgr: TextReaderMgr | null = null;
  private mInit: boolean = false;
  private mTextReaderCall: TextReaderCall | null = null;
  private mTextReaderInitData: TextReaderInitData | null = null;
  private mTextReaderRegister: TextReaderRegister | null = null;

  private mCurrentReadInfo: TextReader.ReadInfo | null = null;

  public static Ins() {
    if (!TextReaderMgr.mTextReaderMgr) {
      TextReaderMgr.mTextReaderMgr = new TextReaderMgr();
    }
    return TextReaderMgr.mTextReaderMgr;
  }

  /**
   * 设置朗读事件监听
   */
  private setActionListener() {
    // 监听朗读状态变化
    TextReader.on('stateChange', (state: TextReader.ReadState) => {
      let readState: ReadStateCode = ReadStateCode.WAITING;
      if (this.mCurrentReadInfo?.id === state.id) {
        readState = state.state;
      } else {
        readState = ReadStateCode.WAITING;
      }
      this.mTextReaderRegister?.onStateChange(readState);
    });

    // 监听加载更多请求
    TextReader.on('requestMore', (callbackStr) => {
      console.log(this.TAG, " callbackStr: " + callbackStr);
      let readInfoList: TextReader.ReadInfo[] = [{
        id: '002',
        title: {
          text: '水调歌头.明月几时有2',
          isClickable: true
        },
        author: {
          text: '宋.苏轼2',
          isClickable: true
        },
        date: {
          text: '2025/02/02',
          isClickable: false
        },
        bodyInfo: '2明月几时有?把酒问青天。不知天上宫阙,今夕是何年?'
      }];
      TextReader.loadMore(readInfoList, true);
    })
  }

  /**
   * 初始化朗读播放控件
   */
  public async initReader(context: Context, callback?: TextReaderCall | null, data?: TextReaderInitData | null,
    register?: TextReaderRegister) {
    this.mTextReaderCall = callback ?? null;
    this.mTextReaderInitData = data ?? null;
    this.mTextReaderRegister = register ?? null;

    // 朗读参数配置
    const readerParam: TextReader.ReaderParam = {
      isVoiceBrandVisible: data?.panelName == "" ? false : true ?? true, // 显示品牌信息
      businessBrandInfo: {
        panelName: data?.panelName == "" ? '朗读' : data?.panelName ?? '朗读', // 面板名称
      },
      isMinibarNeeded: data?.isMinibarNeeded ?? true
    }

    try {
      if (context) {
        // 初始化朗读组件
        await TextReader.init(context, readerParam);
        this.mInit = true; // 标记初始化完成
        this.setActionListener(); // 设置事件监听
        this.mTextReaderCall?.onReady();
      }
    } catch (err) {
      // 初始化失败时打印错误信息
      console.error(this.TAG, `TextReader failed to init. Code: ${err.code}, message: ${err.message}`);
      this.mTextReaderCall?.onInitFail(JSON.stringify(err));
    }
  }

  /**
   * 文本朗读播放接口(不显示字幕全屏面板和吸边小面板,直接朗读文本)
   * @param content 实时朗读的正文信息(长度10000字符以内)
   */
  public async startContent(context: Context, content: string) {
    await this.initReader(context);
    let readInfoList: TextReader.ReadInfo[] = [{
      id: '0',
      title: {
        text: '',
        isClickable: true
      },
      bodyInfo: content
    }];
    this.mCurrentReadInfo = readInfoList[0];
    await TextReader.start(readInfoList, this.mCurrentReadInfo.id);
    TextReader.hidePanel();
  }

  /**
   * 启动朗读
   * @param infoArr
   */
  public async start(infoArr: TextReaderInfo[]) {
    // 判断当前是否初始化成功过
    if (!this.mInit) {
      console.error(this.TAG, "start error ! mInit false !");
      this.mTextReaderCall?.onFail(TextReaderFail.UnInit);
      return;
    }
    if (!infoArr) {
      console.error(this.TAG, "start error ! infoArr null !");
      this.mTextReaderCall?.onFail(TextReaderFail.TextReaderInfoNULL);
      return;
    }
    // 朗读启动配置
    const startParams: TextReader.StartParams = {
      isMinibarHidden: this.mTextReaderInitData?.isMinibarNeeded ?? true,
    }
    // 填充朗读内容
    let readInfoList: TextReader.ReadInfo[] = [];
    for (let index = 0; index < infoArr.length; index++) {
      const info = infoArr[index];
      let tempInfo: TextReader.ReadInfo = {
        id: " " + index,
        title: {
          text: info.title,
          isClickable: true,
        },
        bodyInfo: info.content,
        date: {
          text: info.author ?? "",
          isClickable: true,
        },
        author: {
          text: info.author ?? "",
          isClickable: true,
        }
      }
      readInfoList.push(tempInfo);
    }
    this.mCurrentReadInfo = readInfoList[0];
    // 启动朗读
    await TextReader.start(readInfoList, this.mCurrentReadInfo.id, startParams);
  }
}

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
收藏
回复
举报
回复
    相关推荐