项目源码地址

项目源码已发布到GitCode平台, 方便开发者进行下载和使用。

https://gitcode.com/qq_33681891/NovelReader

前言

文本朗读功能是阅读类应用的重要功能之一,它可以为用户提供更便捷的阅读体验,特别是在驾车、运动或视力不便的情况下。本教程将详细讲解如何在HarmonyOS应用中实现文本朗读功能,以小说阅读器应用为例,深入分析TextReader的使用方法和实现原理。

一、HarmonyOS文本朗读能力简介

1.1 SpeechKit简介

HarmonyOS提供了SpeechKit能力,其中包含TextReader模块,用于实现文本朗读功能。通过TextReader,开发者可以轻松地将文本转换为语音,为用户提供听觉阅读体验。

1.2 TextReader的主要功能

  • 文本朗读:将文本内容转换为语音输出
  • 朗读控制:支持开始、暂停、继续、停止等控制操作
  • 朗读参数设置:支持设置语速、音量、音调等参数
  • 朗读状态监听:支持监听朗读的开始、进行中、结束等状态

二、TextReader的基本使用

2.1 导入TextReader模块

import { TextReader } from '@kit.SpeechKit';

在使用TextReader之前,需要先导入相关模块。

2.2 创建ReadInfo配置

export function textReaderInfo(textId: string, text: string): TextReader.ReadInfo {
    const config: TextReader.ReadInfo = {
        id: textId,
        title: {
            text: '',
            isClickable: false
        },
        bodyInfo: text
    }
    return config;
}

ReadInfo是TextReader朗读的配置信息,包含以下主要字段:

  • id:朗读内容的唯一标识符
  • title:朗读内容的标题,包含文本和是否可点击的配置
  • bodyInfo:朗读的主体内容

2.3 初始化TextReader实例

private textReader: TextReader = new TextReader();

在组件中创建TextReader实例。

2.4 开始朗读

startReading(text: string) {
    const readInfo = textReaderInfo('novel_content', text);
    this.textReader.start(readInfo);
}

调用start方法开始朗读文本。

三、实现小说朗读功能

3.1 准备朗读内容列表

@Component
export struct NovelReader {
  @State readInfoList: TextReader.ReadInfo[] = [];
  @State selectedReadInfo: TextReader.ReadInfo = null;
  private textReader: TextReader = new TextReader();
  
  aboutToAppear() {
    // 初始化朗读内容列表
    for (let i = CONFIGURATION.PAGEFLIPPAGESTART; i <= CONFIGURATION.PAGEFLIPPAGEEND; i++) {
      const pageContent = this.getPageContent(i);
      const readInfo = textReaderInfo('page_' + i, pageContent);
      this.readInfoList.push(readInfo);
    }
    
    if (this.readInfoList.length > 0) {
      this.selectedReadInfo = this.readInfoList[0];
    }
  }
  
  getPageContent(pageNum: number): string {
    // 获取页面内容的逻辑
    return '第' + pageNum + '页的内容...';
  }
  
  // 其他方法和UI构建...
}

3.2 实现朗读控制

@Component
export struct ReadingControls {
  @Link readInfoList: TextReader.ReadInfo[];
  @Link selectedReadInfo: TextReader.ReadInfo;
  private textReader: TextReader = new TextReader();
  @State isReading: boolean = false;
  @State currentProgress: number = 0;
  
  build() {
    Column() {
      Row() {
        Button('上一页')
          .onClick(() => this.previousPage())
        
        Button(this.isReading ? '暂停' : '朗读')
          .onClick(() => this.toggleReading())
        
        Button('下一页')
          .onClick(() => this.nextPage())
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceAround)
      
      Slider({
        value: this.currentProgress,
        min: 0,
        max: 100,
        step: 1
      })
      .width('90%')
      .onChange((value: number) => {
        this.currentProgress = value;
        // 实际应用中,这里可以实现跳转到指定进度的功能
      })
    }
    .width('100%')
    .padding(10)
  }
  
  toggleReading() {
    if (this.isReading) {
      this.textReader.pause();
    } else {
      if (this.selectedReadInfo) {
        this.textReader.start(this.selectedReadInfo);
      }
    }
    this.isReading = !this.isReading;
  }
  
  previousPage() {
    const currentIndex = this.readInfoList.indexOf(this.selectedReadInfo);
    if (currentIndex > 0) {
      this.selectedReadInfo = this.readInfoList[currentIndex - 1];
      if (this.isReading) {
        this.textReader.stop();
        this.textReader.start(this.selectedReadInfo);
      }
    }
  }
  
  nextPage() {
    const currentIndex = this.readInfoList.indexOf(this.selectedReadInfo);
    if (currentIndex < this.readInfoList.length - 1) {
      this.selectedReadInfo = this.readInfoList[currentIndex + 1];
      if (this.isReading) {
        this.textReader.stop();
        this.textReader.start(this.selectedReadInfo);
      }
    }
  }
}

3.3 监听朗读状态

aboutToAppear() {
  // 初始化朗读内容列表...
  
  // 注册朗读状态监听
  this.textReader.on('stateChange', (state) => {
    switch (state) {
      case TextReader.ReadState.READING:
        console.info('正在朗读...');
        this.isReading = true;
        break;
      case TextReader.ReadState.PAUSED:
        console.info('朗读已暂停');
        this.isReading = false;
        break;
      case TextReader.ReadState.COMPLETED:
        console.info('朗读已完成');
        this.isReading = false;
        this.autoPlayNext();
        break;
      case TextReader.ReadState.STOPPED:
        console.info('朗读已停止');
        this.isReading = false;
        break;
    }
  });
  
  // 注册朗读进度监听
  this.textReader.on('progressChange', (progress) => {
    this.currentProgress = progress * 100;
  });
}

autoPlayNext() {
  const currentIndex = this.readInfoList.indexOf(this.selectedReadInfo);
  if (currentIndex < this.readInfoList.length - 1) {
    this.selectedReadInfo = this.readInfoList[currentIndex + 1];
    this.textReader.start(this.selectedReadInfo);
  }
}

aboutToDisappear() {
  // 取消监听
  this.textReader.off('stateChange');
  this.textReader.off('progressChange');
  // 停止朗读
  this.textReader.stop();
}

四、高级功能实现

4.1 朗读参数设置

@Component
export struct ReadingSettings {
  private textReader: TextReader = new TextReader();
  @State speed: number = 1.0; // 默认语速
  @State volume: number = 1.0; // 默认音量
  @State pitch: number = 1.0; // 默认音调
  
  build() {
    Column() {
      Text('朗读设置')
        .fontSize(20)
        .fontWeight(500)
        .margin({ bottom: 20 })
      
      Row() {
        Text('语速:')
        Slider({
          value: this.speed * 100,
          min: 50,
          max: 200,
          step: 10
        })
        .width('70%')
        .onChange((value: number) => {
          this.speed = value / 100;
          this.updateSettings();
        })
        Text(this.speed.toFixed(1) + 'x')
      }
      .width('100%')
      .margin({ bottom: 10 })
      
      Row() {
        Text('音量:')
        Slider({
          value: this.volume * 100,
          min: 0,
          max: 100,
          step: 5
        })
        .width('70%')
        .onChange((value: number) => {
          this.volume = value / 100;
          this.updateSettings();
        })
        Text(Math.round(this.volume * 100) + '%')
      }
      .width('100%')
      .margin({ bottom: 10 })
      
      Row() {
        Text('音调:')
        Slider({
          value: this.pitch * 100,
          min: 50,
          max: 200,
          step: 10
        })
        .width('70%')
        .onChange((value: number) => {
          this.pitch = value / 100;
          this.updateSettings();
        })
        Text(this.pitch.toFixed(1))
      }
      .width('100%')
    }
    .width('100%')
    .padding(15)
  }
  
  updateSettings() {
    // 更新朗读参数
    this.textReader.setSpeed(this.speed);
    this.textReader.setVolume(this.volume);
    this.textReader.setPitch(this.pitch);
  }
}

4.2 朗读内容分段处理

对于长文本,可以进行分段处理,提高朗读的自然度:

function splitTextIntoChunks(text: string): string[] {
  // 按句号、问号、感叹号等分割文本
  const sentences = text.split(/(?<=[。?!.?!])/);
  const chunks: string[] = [];
  let currentChunk = '';
  
  for (const sentence of sentences) {
    if (sentence.trim() === '') continue;
    
    // 如果当前块加上新句子不超过200个字符,则添加到当前块
    if (currentChunk.length + sentence.length <= 200) {
      currentChunk += sentence;
    } else {
      // 否则,保存当前块并开始新块
      if (currentChunk !== '') {
        chunks.push(currentChunk);
      }
      currentChunk = sentence;
    }
  }
  
  // 添加最后一个块
  if (currentChunk !== '') {
    chunks.push(currentChunk);
  }
  
  return chunks;
}

function createReadInfoList(text: string): TextReader.ReadInfo[] {
  const chunks = splitTextIntoChunks(text);
  const readInfoList: TextReader.ReadInfo[] = [];
  
  for (let i = 0; i < chunks.length; i++) {
    const readInfo = textReaderInfo('chunk_' + i, chunks[i]);
    readInfoList.push(readInfo);
  }
  
  return readInfoList;
}

4.3 朗读位置高亮显示

@Component
export struct HighlightTextReader {
  @State text: string = '这是一段示例文本,用于演示朗读时的高亮效果。这是第二句话。这是第三句话,比较长一些,包含更多的内容。';
  @State currentSentenceIndex: number = -1;
  private textReader: TextReader = new TextReader();
  private sentences: string[] = [];
  
  aboutToAppear() {
    // 分割文本为句子
    this.sentences = this.text.split(/(?<=[。?!.?!])/);
    
    // 注册朗读状态和进度监听
    this.textReader.on('stateChange', (state) => {
      if (state === TextReader.ReadState.COMPLETED) {
        this.currentSentenceIndex = -1;
      }
    });
    
    this.textReader.on('progressChange', (progress) => {
      // 根据进度计算当前句子索引
      const sentenceCount = this.sentences.length;
      const estimatedIndex = Math.floor(progress * sentenceCount);
      if (estimatedIndex !== this.currentSentenceIndex) {
        this.currentSentenceIndex = estimatedIndex;
      }
    });
  }
  
  build() {
    Column() {
      // 显示文本,当前句子高亮
      ForEach(this.sentences, (sentence: string, index: number) => {
        Text(sentence)
          .fontSize(16)
          .fontColor(index === this.currentSentenceIndex ? '#0000FF' : '#000000')
          .fontWeight(index === this.currentSentenceIndex ? 500 : 400)
      })
      
      Button('开始朗读')
        .onClick(() => {
          const readInfo = textReaderInfo('highlight_text', this.text);
          this.textReader.start(readInfo);
        })
    }
    .width('100%')
    .padding(15)
  }
  
  aboutToDisappear() {
    this.textReader.off('stateChange');
    this.textReader.off('progressChange');
    this.textReader.stop();
  }
}

五、与数据源结合使用

5.1 从BasicDataSource获取朗读内容

@Component
export struct NovelReaderWithDataSource {
  private data: BasicDataSource = new BasicDataSource([]);
  @State readInfoList: TextReader.ReadInfo[] = [];
  @State selectedReadInfo: TextReader.ReadInfo = null;
  private textReader: TextReader = new TextReader();
  
  aboutToAppear(): void {
    // 初始化数据源
    for (let i = CONFIGURATION.PAGEFLIPPAGESTART; i <= CONFIGURATION.PAGEFLIPPAGEEND; i++) {
      this.data.pushItem(STRINGCONFIGURATION.PAGEFLIPRESOURCE + i.toString());
    }
    
    // 从数据源生成朗读内容列表
    this.generateReadInfoList();
  }
  
  generateReadInfoList() {
    this.readInfoList = [];
    for (let i = 0; i < this.data.totalCount(); i++) {
      const content = this.data.getData(i);
      const readInfo = textReaderInfo('item_' + i, content);
      this.readInfoList.push(readInfo);
    }
    
    if (this.readInfoList.length > 0) {
      this.selectedReadInfo = this.readInfoList[0];
    }
  }
  
  // 其他方法和UI构建...
}

5.2 动态加载朗读内容

// 监听数据源变化
aboutToAppear() {
  // 初始化数据源...
  
  // 创建数据变化监听器
  const dataChangeListener: DataChangeListener = {
    onDataAdd: (index: number) => {
      // 数据添加时更新朗读列表
      const content = this.data.getData(index);
      const readInfo = textReaderInfo('item_' + index, content);
      this.readInfoList.splice(index, 0, readInfo);
    },
    onDataDelete: (index: number) => {
      // 数据删除时更新朗读列表
      this.readInfoList.splice(index, 1);
      if (this.selectedReadInfo === this.readInfoList[index]) {
        this.selectedReadInfo = this.readInfoList[0] || null;
      }
    },
    onDataChange: (index: number) => {
      // 数据变化时更新朗读列表
      const content = this.data.getData(index);
      const readInfo = textReaderInfo('item_' + index, content);
      this.readInfoList[index] = readInfo;
      if (this.selectedReadInfo === this.readInfoList[index]) {
        this.selectedReadInfo = readInfo;
      }
    },
    onDataReloaded: () => {
      // 数据重新加载时更新朗读列表
      this.generateReadInfoList();
    }
  };
  
  // 注册数据变化监听器
  this.data.registerDataChangeListener(dataChangeListener);
}

总结

本教程详细介绍了HarmonyOS中文本朗读功能的实现方法,以TextReader为核心,讲解了基本使用、高级功能和最佳实践。通过合理使用TextReader,可以为用户提供优质的朗读体验,提升应用的可用性和用户满意度。在实际开发中,可以根据具体需求扩展和优化朗读功能,如支持多语言、自定义语音等。


全栈若城
1 声望2 粉丝