鸿蒙开发案例:分贝仪

【1】引言(完整代码在最后面)

分贝仪是一个简单的应用,用于测量周围环境的噪音水平。通过麦克风采集音频数据,计算当前的分贝值,并在界面上实时显示。该应用不仅展示了鸿蒙系统的基础功能,还涉及到了权限管理、音频处理和UI设计等多个方面。

【2】环境准备

电脑系统:windows 10

开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806

工程版本:API 12

真机:mate60 pro

语言:ArkTS、ArkUI

权限:ohos.permission.MICROPHONE(麦克风权限)

系统库:

• @kit.AudioKit:用于音频处理的库。

• @kit.AbilityKit:用于权限管理和应用能力的库。

• @kit.BasicServicesKit:提供基本的服务支持,如错误处理等。

【3】功能模块

3.1 权限管理

在使用麦克风之前,需要请求用户的权限。如果用户拒绝,会显示一个对话框引导用户手动开启权限。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 请求用户权限
requestPermissionsFromUser() {
  const context = getContext(this) as common.UIAbilityContext;
  const atManager = abilityAccessCtrl.createAtManager();
  atManager.requestPermissionsFromUser(context, this.requiredPermissions, (err, data) => {
    const grantStatus: Array<number> = data.authResults;
    if (grantStatus.toString() == "-1") {
      this.showAlertDialog();
    } else if (grantStatus.toString() == "0") {
      this.initialize();
    }
  });
}

3.2 分贝计算

通过读取麦克风采集的音频数据,计算当前环境的分贝值。计算过程中会对音频样本进行归一化处理,并计算其均方根(RMS)值,最终转换成分贝值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 分贝计算
calculateDecibel(pcm: ArrayBuffer): number {
  let sum = 0;
  const pcmView = new DataView(pcm);
  const numSamples = pcm.byteLength / 2;
 
  for (let i = 0; i < pcm.byteLength; i += 2) {
    const sample = pcmView.getInt16(i, true) / 32767.0;
    sum += sample * sample;
  }
 
  const meanSquare = sum / numSamples;
  const rmsAmplitude = Math.sqrt(meanSquare);
  const referencePressure = 20e-6;
  const decibels = 20 * Math.log10(rmsAmplitude / referencePressure);
 
  if (isNaN(decibels)) {
    return -100;
  }
 
  const minDb = 20;
  const maxDb = 100;
  const mappedValue = ((decibels - minDb) / (maxDb - minDb)) * 100;
  return Math.max(0, Math.min(100, mappedValue));
}

3.3 UI设计

界面上包含一个仪表盘显示当前分贝值,以及一段文字描述当前的噪音水平。分贝值被映射到0到100的范围内,以适应仪表盘的显示需求。界面上还有两个按钮,分别用于开始和停止分贝测量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// 构建UI
build() {
  Column() {
    Text("分贝仪")
      .width('100%')
      .height(44)
      .backgroundColor("#fe9900")
      .textAlign(TextAlign.Center)
      .fontColor(Color.White);
 
    Row() {
      Gauge({ value: this.currentDecibel, min: 1, max: 100 }) {
        Column() {
          Text(`${this.displayedDecibel}分贝`)
            .fontSize(25)
            .fontWeight(FontWeight.Medium)
            .fontColor("#323232")
            .width('40%')
            .height('30%')
            .textAlign(TextAlign.Center)
            .margin({ top: '22.2%' })
            .textOverflow({ overflow: TextOverflow.Ellipsis })
            .maxLines(1);
 
          Text(`${this.displayType}`)
            .fontSize(16)
            .fontColor("#848484")
            .fontWeight(FontWeight.Regular)
            .width('47.4%')
            .height('15%')
            .textAlign(TextAlign.Center)
            .backgroundColor("#e4e4e4")
            .borderRadius(5);
        }.width('100%');
      }
      .startAngle(225)
      .endAngle(135)
      .colors(this.gaugeColors)
      .height(250)
      .strokeWidth(18)
      .description(null)
      .trackShadow({ radius: 7, offsetX: 7, offsetY: 7 })
      .padding({ top: 30 });
    }.width('100%').justifyContent(FlexAlign.Center);
 
    Column() {
      ForEach(this.typeArray, (item: ValueBean, index: number) => {
        Row() {
          Text(item.description)
            .textAlign(TextAlign.Start)
            .fontColor("#3d3d3d");
        }.width(250)
          .padding({ bottom: 10, top: 10 })
          .borderWidth({ bottom: 1 })
          .borderColor("#737977");
      });
    }.width('100%');
 
    Row() {
      Button('开始检测').clickEffect({ level: ClickEffectLevel.LIGHT }).onClick(() => {
        if (this.audioRecorder) {
          this.startRecording();
        } else {
          this.requestPermissionsFromUser();
        }
      });
 
      Button('停止检测').clickEffect({ level: ClickEffectLevel.LIGHT }).onClick(() => {
        if (this.audioRecorder) {
          this.stopRecording();
        }
      });
    }.width('100%')
      .justifyContent(FlexAlign.SpaceEvenly)
      .padding({
        left: 20,
        right: 20,
        top: 40,
        bottom: 40
      });
  }.height('100%').width('100%');
}

【4】关键代码解析

4.1 权限检查与请求

在应用启动时,首先检查是否已经获得了麦克风权限。如果没有获得权限,则请求用户授权。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 检查权限
checkPermissions() {
  const atManager = abilityAccessCtrl.createAtManager();
  const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
  const tokenId = bundleInfo.appInfo.accessTokenId;
 
  const authResults = this.requiredPermissions.map((permission) => atManager.checkAccessTokenSync(tokenId, permission));
  return authResults.every(v => v === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED);
}
 
// 请求用户权限
requestPermissionsFromUser() {
  const context = getContext(this) as common.UIAbilityContext;
  const atManager = abilityAccessCtrl.createAtManager();
  atManager.requestPermissionsFromUser(context, this.requiredPermissions, (err, data) => {
    const grantStatus: Array<number> = data.authResults;
    if (grantStatus.toString() == "-1") {
      this.showAlertDialog();
    } else if (grantStatus.toString() == "0") {
      this.initialize();
    }
  });
}

4.2 音频记录器初始化

在获得权限后,初始化音频记录器,设置采样率、通道数、采样格式等参数,并开始监听音频数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 初始化音频记录器
initialize() {
  const streamInfo: audio.AudioStreamInfo = {
    samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100,
    channels: audio.AudioChannel.CHANNEL_1,
    sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
    encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW
  };
 
  const recorderInfo: audio.AudioCapturerInfo = {
    source: audio.SourceType.SOURCE_TYPE_MIC,
    capturerFlags: 0
  };
 
  const recorderOptions: audio.AudioCapturerOptions = {
    streamInfo: streamInfo,
    capturerInfo: recorderInfo
  };
 
  audio.createAudioCapturer(recorderOptions, (err, recorder) => {
    if (err) {
      console.error(`创建音频记录器失败, 错误码: ${err.code}, 错误信息: ${err.message}`);
      return;
    }
    console.info(`${this.TAG}: 音频记录器创建成功`);
    this.audioRecorder = recorder;
 
    if (this.audioRecorder !== undefined) {
      this.audioRecorder.on('readData', (buffer: ArrayBuffer) => {
        this.currentDecibel = this.calculateDecibel(buffer);
        this.updateDisplay();
      });
    }
  });
}

4.3 更新显示

每秒钟更新一次显示的分贝值,并根据当前分贝值确定其所属的噪音级别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 更新显示
updateDisplay() {
  if (Date.now() - this.lastUpdateTimestamp > 1000) {
    this.lastUpdateTimestamp = Date.now();
    this.displayedDecibel = Math.floor(this.currentDecibel);
 
    for (const item of this.typeArray) {
      if (this.currentDecibel >= item.minDb && this.currentDecibel < item.maxDb) {
        this.displayType = item.label;
        break;
      }
    }
  }
}

【5】完整代码

5.1 配置麦克风权限

路径:src/main/module.json5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
 
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.MICROPHONE",
        "reason": "$string:microphone_reason",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when":"inuse"
        }
      }
    ],

5.2 配置权限弹窗时的描述文字

路径:src/main/resources/base/element/string.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
  "string": [
    {
      "name": "module_desc",
      "value": "module description"
    },
    {
      "name": "EntryAbility_desc",
      "value": "description"
    },
    {
      "name": "EntryAbility_label",
      "value": "label"
    },
    {
      "name": "microphone_reason",
      "value": "需要麦克风权限说明"
    }
  ]
}

5.3 完整代码

路径:src/main/ets/pages/Index.ets

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
import { audio } from '@kit.AudioKit'; // 导入音频相关的库
import { abilityAccessCtrl, bundleManager, common, Permissions } from '@kit.AbilityKit'; // 导入权限管理相关的库
import { BusinessError } from '@kit.BasicServicesKit'; // 导入业务错误处理
 
// 定义一个类,用于存储分贝范围及其描述
class ValueBean {
  label: string; // 标签
  description: string; // 描述
  minDb: number; // 最小分贝值
  maxDb: number; // 最大分贝值
  colorStart: string; // 起始颜色
  colorEnd: string; // 结束颜色
 
  // 构造函数,初始化属性
  constructor(label: string, description: string, minDb: number, maxDb: number, colorStart: string, colorEnd: string) {
    this.label = label;
    this.description = description;
    this.minDb = minDb;
    this.maxDb = maxDb;
    this.colorStart = colorStart;
    this.colorEnd = colorEnd;
  }
}
 
// 定义分贝仪组件
@Entry
@Component
struct DecibelMeter {
  TAG: string = 'DecibelMeter'; // 日志标签
  audioRecorder: audio.AudioCapturer | undefined = undefined; // 音频记录器
  requiredPermissions: Array<Permissions> = ['ohos.permission.MICROPHONE']; // 需要的权限
  @State currentDecibel: number = 0; // 当前分贝值
  @State displayedDecibel: number = 0; // 显示的分贝值
  lastUpdateTimestamp: number = 0; // 上次更新时间戳
  @State displayType: string = ''; // 当前显示类型
  // 定义分贝范围及其描述
  typeArray: ValueBean[] = [
    new ValueBean("寂静", "0~20dB : 寂静,几乎感觉不到", 0, 20, "#02b003", "#016502"),
    new ValueBean("安静", '20~40dB :安静,轻声交谈', 20, 40, "#7ed709", "#4f8800"),
    new ValueBean("正常", '40~60dB :正常,普通室内谈话', 40, 60, "#ffef01", "#ad9e04"),
    new ValueBean("吵闹", '60~80dB :吵闹,大声说话', 60, 80, "#f88200", "#965001"),
    new ValueBean("很吵", '80~100dB: 很吵,可使听力受损', 80, 100, "#f80000", "#9d0001"),
  ];
  gaugeColors: [LinearGradient, number][] = [] // 存储仪表颜色的数组
 
  // 组件即将出现时调用
  aboutToAppear(): void {
    // 初始化仪表颜色
    for (let i = 0; i < this.typeArray.length; i++) {
      this.gaugeColors.push([new LinearGradient([{ color: this.typeArray[i].colorStart, offset: 0 },
        { color: this.typeArray[i].colorEnd, offset: 1 }]), 1])
    }
  }
 
  // 请求用户权限
  requestPermissionsFromUser() {
    const context = getContext(this) as common.UIAbilityContext; // 获取上下文
    const atManager = abilityAccessCtrl.createAtManager(); // 创建权限管理器
    // 请求权限
    atManager.requestPermissionsFromUser(context, this.requiredPermissions, (err, data) => {
      const grantStatus: Array<number> = data.authResults; // 获取授权结果
      if (grantStatus.toString() == "-1") { // 用户拒绝权限
        this.showAlertDialog(); // 显示提示对话框
      } else if (grantStatus.toString() == "0") { // 用户同意权限
        this.initialize(); // 初始化音频记录器
      }
    });
  }
 
  // 显示对话框提示用户开启权限
  showAlertDialog() {
    this.getUIContext().showAlertDialog({
      autoCancel: true, // 自动取消
      title: '权限申请', // 对话框标题
      message: '如需使用此功能,请前往设置页面开启麦克风权限。', // 对话框消息
      cancel: () => {
      },
      confirm: {
        defaultFocus: true, // 默认聚焦确认按钮
        value: '好的', // 确认按钮文本
        action: () => {
          this.openPermissionSettingsPage(); // 打开权限设置页面
        }
      },
      onWillDismiss: () => {
      },
      alignment: DialogAlignment.Center, // 对话框对齐方式
    });
  }
 
  // 打开权限设置页面
  openPermissionSettingsPage() {
    const context = getContext() as common.UIAbilityContext; // 获取上下文
    const bundleInfo =
      bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION); // 获取包信息
    context.startAbility({
      bundleName: 'com.huawei.hmos.settings', // 设置页面的包名
      abilityName: 'com.huawei.hmos.settings.MainAbility', // 设置页面的能力名
      uri: 'application_info_entry', // 打开设置->应用和元服务
      parameters: {
        pushParams: bundleInfo.name // 按照包名打开对应设置页
      }
    });
  }
 
  // 分贝计算
  calculateDecibel(pcm: ArrayBuffer): number {
    let sum = 0; // 初始化平方和
    const pcmView = new DataView(pcm); // 创建数据视图
    const numSamples = pcm.byteLength / 2; // 计算样本数量
 
    // 归一化样本值并计算平方和
    for (let i = 0; i < pcm.byteLength; i += 2) {
      const sample = pcmView.getInt16(i, true) / 32767.0; // 归一化样本值
      sum += sample * sample; // 计算平方和
    }
 
    // 计算平均平方值
    const meanSquare = sum / numSamples; // 计算均方
 
    // 计算RMS(均方根)振幅
    const rmsAmplitude = Math.sqrt(meanSquare); // 计算RMS值
 
    // 使用标准参考压力值
    const referencePressure = 20e-6; // 20 μPa
 
    // 计算分贝值
    const decibels = 20 * Math.log10(rmsAmplitude / referencePressure); // 计算分贝
 
    // 处理NaN值
    if (isNaN(decibels)) {
      return -100; // 返回一个极小值表示静音
    }
 
    // 调整动态范围
    const minDb = 20; // 调整最小分贝值
    const maxDb = 100; // 调整最大分贝值
 
    // 将分贝值映射到0到100之间的范围
    const mappedValue = ((decibels - minDb) / (maxDb - minDb)) * 100; // 映射分贝值
 
    // 确保值在0到100之间
    return Math.max(0, Math.min(100, mappedValue)); // 返回映射后的值
  }
 
  // 初始化音频记录器
  initialize() {
    const streamInfo: audio.AudioStreamInfo = {
      samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100, // 采样率
      channels: audio.AudioChannel.CHANNEL_1, // 单声道
      sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式
      encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码类型
    };
    const recorderInfo: audio.AudioCapturerInfo = {
      source: audio.SourceType.SOURCE_TYPE_MIC, // 音频源为麦克风
      capturerFlags: 0 // 捕获标志
    };
    const recorderOptions: audio.AudioCapturerOptions = {
      streamInfo: streamInfo, // 音频流信息
      capturerInfo: recorderInfo // 记录器信息
    };
    // 创建音频记录器
    audio.createAudioCapturer(recorderOptions, (err, recorder) => {
      if (err) {
        console.error(`创建音频记录器失败, 错误码: ${err.code}, 错误信息: ${err.message}`); // 错误处理
        return;
      }
      console.info(`${this.TAG}: 音频记录器创建成功`); // 成功日志
      this.audioRecorder = recorder; // 保存记录器实例
      if (this.audioRecorder !== undefined) {
        // 监听音频数据
        this.audioRecorder.on('readData', (buffer: ArrayBuffer) => {
          this.currentDecibel = this.calculateDecibel(buffer); // 计算当前分贝值
          this.updateDisplay(); // 更新显示
        });
      }
      this.startRecording(); // 开始录音
    });
  }
 
  // 开始录音
  startRecording() {
    if (this.audioRecorder !== undefined) { // 检查音频记录器是否已定义
      this.audioRecorder.start((err: BusinessError) => { // 调用开始录音方法
        if (err) {
          console.error('开始录音失败'); // 记录错误信息
        } else {
          console.info('开始录音成功'); // 记录成功信息
        }
      });
    }
  }
 
  // 停止录音
  stopRecording() {
    if (this.audioRecorder !== undefined) { // 检查音频记录器是否已定义
      this.audioRecorder.stop((err: BusinessError) => { // 调用停止录音方法
        if (err) {
          console.error('停止录音失败'); // 记录错误信息
        } else {
          console.info('停止录音成功'); // 记录成功信息
        }
      });
    }
  }
 
  // 更新显示
  updateDisplay() {
    if (Date.now() - this.lastUpdateTimestamp > 1000) { // 每隔1秒更新一次显示
      this.lastUpdateTimestamp = Date.now(); // 更新最后更新时间戳
      this.displayedDecibel = Math.floor(this.currentDecibel); // 将当前分贝值取整并赋值给显示的分贝值
      // 遍历分贝类型数组,确定当前分贝值对应的类型
      for (const item of this.typeArray) {
        if (this.currentDecibel >= item.minDb && this.currentDecibel < item.maxDb) { // 检查当前分贝值是否在某个范围内
          this.displayType = item.label; // 设置当前显示类型
          break; // 找到对应类型后退出循环
        }
      }
    }
  }
 
  // 检查权限
  checkPermissions() {
    const atManager = abilityAccessCtrl.createAtManager(); // 创建权限管理器
    const bundleInfo =
      bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION); // 获取包信息
    const tokenId = bundleInfo.appInfo.accessTokenId; // 获取应用的唯一标识
    // 检查每个权限的授权状态
    const authResults =
      this.requiredPermissions.map((permission) => atManager.checkAccessTokenSync(tokenId, permission));
    return authResults.every(v => v === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED); // 返回是否所有权限都被授予
  }
 
  // 构建UI
  build() {
    Column() {
      Text("分贝仪")// 显示标题
        .width('100%')// 设置宽度为100%
        .height(44)// 设置高度为44
        .backgroundColor("#fe9900")// 设置背景颜色
        .textAlign(TextAlign.Center)// 设置文本对齐方式
        .fontColor(Color.White); // 设置字体颜色
 
      Row() {
        Gauge({ value: this.currentDecibel, min: 1, max: 100 }) { // 创建仪表,显示当前分贝值
          Column() {
            Text(`${this.displayedDecibel}分贝`)// 显示当前分贝值
              .fontSize(25)// 设置字体大小
              .fontWeight(FontWeight.Medium)// 设置字体粗细
              .fontColor("#323232")// 设置字体颜色
              .width('40%')// 设置宽度为40%
              .height('30%')// 设置高度为30%
              .textAlign(TextAlign.Center)// 设置文本对齐方式
              .margin({ top: '22.2%' })// 设置上边距
              .textOverflow({ overflow: TextOverflow.Ellipsis })// 设置文本溢出处理
              .maxLines(1); // 设置最大行数为1
 
            Text(`${this.displayType}`)// 显示当前类型
              .fontSize(16)// 设置字体大小
              .fontColor("#848484")// 设置字体颜色
              .fontWeight(FontWeight.Regular)// 设置字体粗细
              .width('47.4%')// 设置宽度为47.4%
              .height('15%')// 设置高度为15%
              .textAlign(TextAlign.Center)// 设置文本对齐方式
              .backgroundColor("#e4e4e4")// 设置背景颜色
              .borderRadius(5); // 设置圆角
          }.width('100%'); // 设置列宽度为100%
        }
        .startAngle(225) // 设置仪表起始角度
        .endAngle(135) // 设置仪表结束角度
        .colors(this.gaugeColors) // 设置仪表颜色
        .height(250) // 设置仪表高度
        .strokeWidth(18) // 设置仪表边框宽度
        .description(null) // 设置描述为null
        .trackShadow({ radius: 7, offsetX: 7, offsetY: 7 }) // 设置阴影效果
        .padding({ top: 30 }); // 设置内边距
      }.width('100%').justifyContent(FlexAlign.Center); // 设置行宽度为100%并居中对齐
 
      Column() {
        ForEach(this.typeArray, (item: ValueBean, index: number) => { // 遍历分贝类型数组
          Row() {
            Text(item.description)// 显示每个类型的描述
              .textAlign(TextAlign.Start)// 设置文本对齐方式
              .fontColor("#3d3d3d"); // 设置字体颜色
          }.width(250) // 设置行宽度为250
          .padding({ bottom: 10, top: 10 }) // 设置上下内边距
          .borderWidth({ bottom: 1 }) // 设置下边框宽度
          .borderColor("#737977"); // 设置下边框颜色
        });
      }.width('100%'); // 设置列宽度为100%
 
      Row() {
        Button('开始检测').clickEffect({ level: ClickEffectLevel.LIGHT }).onClick(() => { // 创建开始检测按钮
          if (this.audioRecorder) { // 检查音频记录器是否已定义
            this.startRecording(); // 开始录音
          } else {
            this.requestPermissionsFromUser(); // 请求用户权限
          }
        });
 
        Button('停止检测').clickEffect({ level: ClickEffectLevel.LIGHT }).onClick(() => { // 创建停止检测按钮
          if (this.audioRecorder) { // 检查音频记录器是否已定义
            this.stopRecording(); // 停止录音
          }
        });
      }.width('100%') // 设置行宽度为100%
      .justifyContent(FlexAlign.SpaceEvenly) // 设置内容均匀分布
      .padding({
        // 设置内边距
        left: 20,
        right: 20,
        top: 40,
        bottom: 40
      });
    }.height('100%').width('100%'); // 设置列高度和宽度为100%
  }
 
  // 页面显示时的处理
  onPageShow(): void {
    const hasPermission = this.checkPermissions(); // 检查权限
    console.info(`麦克风权限状态: ${hasPermission ? '已开启' : '未开启'}`); // 打印权限状态
    if (hasPermission) { // 如果权限已开启
      if (this.audioRecorder) { // 检查音频记录器是否已定义
        this.startRecording(); // 开始录音
      } else {
        this.requestPermissionsFromUser(); // 请求用户权限
      }
    }
  }
}

  

 

posted @   zhongcx  阅读(131)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示