Architecture du système de notifications
1. Déclaration des permissions et configuration initiale
Pour interagir avec les APIs de notification et les modules audio, il est impératif de définir les permissions requises dans le fichier module.json5 :
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.NOTIFICATION_CONTROLLER",
"reason": "$string:req_notification_access",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
},
{
"name": "ohos.permission.MICROPHONE",
"reason": "$string:req_audio_capture",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
},
{
"name": "ohos.permission.VIBRATE",
"reason": "$string:req_haptic_feedback",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
}
}
2. Provisionnement des canaux de notification
La segmentation des notifications en différents canaux permet d'adapter le comportement du système selon le contexte d'utilisation :
import notificationManager from '@ohos.notificationManager';
import { BusinessError } from '@ohos.base';
export class ChannelConfigurator {
async configureSlots(): Promise<void> {
try {
const criticalSlot: notificationManager.NotificationSlot = {
id: 'slot_critical',
name: 'Alertes Critiques',
type: notificationManager.SlotType.ALARM,
level: notificationManager.SlotLevel.LEVEL_HIGH,
vibrationEnabled: true,
vibrationValues: [500, 500, 1000],
soundEnabled: true,
sound: 'system://critical_alarm.mp3',
bypassDnd: true,
description: 'Notifications de sécurité urgentes'
};
await notificationManager.addSlot(criticalSlot);
const socialSlot: notificationManager.NotificationSlot = {
id: 'slot_social',
name: 'Interactions Sociales',
type: notificationManager.SlotType.SOCIAL_COMMUNICATION,
level: notificationManager.SlotLevel.LEVEL_DEFAULT,
vibrationEnabled: true,
vibrationValues: [300, 300],
soundEnabled: true,
sound: 'system://message_tone.mp3',
bypassDnd: false,
description: 'Messages et mises à jour sociales'
};
await notificationManager.addSlot(socialSlot);
const backgroundSlot: notificationManager.NotificationSlot = {
id: 'slot_background',
name: 'Tâches de fond',
type: notificationManager.SlotType.OTHER_TYPES,
level: notificationManager.SlotLevel.LEVEL_LOW,
vibrationEnabled: false,
soundEnabled: false,
bypassDnd: false,
description: 'Mises à jour silencieuses du système'
};
await notificationManager.addSlot(backgroundSlot);
console.info('Provisionnement des canaux terminé.');
} catch (err) {
console.error(`Échec de configuration : ${(err as BusinessError).message}`);
}
}
}</void>
3. Diffusion de notifications hétérogènes
Implémentation des mécanismes de publication pour divers cas d'usage métier :
export class NotificationPusher {
private counter: number = 100;
async pushBasicAlert(title: string, body: string, channel: string = 'slot_background'): Promise<boolean> {
try {
const payload: notificationManager.NotificationRequest = {
id: this.counter++,
slotId: channel,
content: {
contentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
normal: {
title: title,
text: body,
additionalText: 'Système applicatif'
}
},
deliveryTime: Date.now(),
autoDeletedTime: Date.now() + 7200000,
tapDismissed: true
};
await notificationManager.publish(payload);
return true;
} catch (err) {
console.error(`Échec de publication : ${(err as BusinessError).message}`);
return false;
}
}
async pushInteractiveAlert(
title: string,
body: string,
actions: notificationManager.NotificationActionButton[]
): Promise<boolean> {
try {
const payload: notificationManager.NotificationRequest = {
id: this.counter++,
slotId: 'slot_social',
content: {
contentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
normal: { title, text: body }
},
actionButtons: actions,
intent: {
bundleName: 'com.enterprise.app',
abilityName: 'InteractionAbility',
parameters: { refId: this.counter.toString() }
}
};
await notificationManager.publish(payload);
return true;
} catch (err) {
console.error(`Échec de l'alerte interactive : ${(err as BusinessError).message}`);
return false;
}
}
async pushProgressTracker(title: string, current: number, total: number = 100): Promise<void> {
try {
const payload: notificationManager.NotificationRequest = {
id: 9999,
slotId: 'slot_background',
content: {
contentType: notificationManager.ContentType.NOTIFICATION_CONTENT_PROGRESS,
progress: {
title: title,
progressValue: current,
progressMaxValue: total,
statusText: `${Math.round((current/total)*100)}% traité`
}
},
isOngoing: true
};
await notificationManager.publish(payload);
if (current >= total) {
setTimeout(async () => {
await this.pushBasicAlert(title, 'Opération finalisée', 'slot_background');
await notificationManager.cancel(9999);
}, 1500);
}
} catch (err) {
console.error(`Erreur de suivi de progression : ${(err as BusinessError).message}`);
}
}
}</void></boolean></boolean>
4. Cycle de vie et interception des événements
Gestion de l'état des notifications et routage des interactions utilisateur :
@Component
struct NotificationLifecycleHandler {
@State activeAlerts: notificationManager.NotificationRequest[] = [];
private subscriber: notificationManager.NotificationSubscriber | null = null;
aboutToAppear() {
this.bindSubscriber();
}
private bindSubscriber(): void {
try {
this.subscriber = {
onNotificationPublished: (alert: notificationManager.NotificationRequest) => {
console.info(`Alerte émise : ${alert.id}`);
this.refreshState();
},
onNotificationCancelled: (id: number, slotId: string, reason: number) => {
console.info(`Alerte révoquée : ${id}`);
this.refreshState();
},
onNotificationUpdated: (alert: notificationManager.NotificationRequest) => {
console.info(`Alerte modifiée : ${alert.id}`);
this.refreshState();
}
};
notificationManager.subscribe(this.subscriber);
} catch (err) {
console.error(`Erreur d'abonnement : ${(err as BusinessError).message}`);
}
}
private async refreshState(): Promise<void> {
try {
this.activeAlerts = await notificationManager.getActiveNotifications();
} catch (err) {
console.error(`Impossible de récupérer l'état : ${(err as BusinessError).message}`);
}
}
routeInteraction(alertId: number): void {
switch (alertId) {
case 9999:
router.push({ url: 'pages/TransferStatus' });
break;
case 100:
router.push({ url: 'pages/ChatView' });
break;
default:
router.push({ url: 'pages/AlertHistory' });
}
}
aboutToDisappear() {
if (this.subscriber) {
notificationManager.unsubscribe(this.subscriber);
}
}
build() {
// Composant d'interface utilisateur pour l'historique
}
}</void>
Module de reconanissance vocale (ASR)
1. Initialisation du moteur de transcription
Configuration du pipeline audio pour la conversion parole-texte :
import speechRecognizer from '@ohos.speechRecognizer';
import { BusinessError } from '@ohos.base';
export class AsrEngineManager {
private engine: speechRecognizer.SpeechRecognizer | null = null;
@State isActive: boolean = false;
@State transcript: string = '';
@State accuracy: number = 0;
async prepareEngine(): Promise<boolean> {
try {
this.engine = speechRecognizer.createSpeechRecognizer();
const parameters: speechRecognizer.RecognizerConfig = {
language: 'zh-CN',
country: 'CN',
punctuation: true,
mode: speechRecognizer.RecognizeMode.FREE_FORM,
audioSource: speechRecognizer.AudioSource.MICROPHONE
};
await this.engine.setConfig(parameters);
this.engine.on('result', (output: speechRecognizer.RecognizerResult) => {
this.transcript = output.text;
this.accuracy = output.confidence;
});
this.engine.on('error', (err: BusinessError) => {
console.error(`Défaillance ASR : ${err.message}`);
this.isActive = false;
});
return true;
} catch (err) {
console.error(`Initialisation ASR échouée : ${(err as BusinessError).message}`);
return false;
}
}
async beginListening(): Promise<void> {
if (!this.engine) await this.prepareEngine();
try {
await this.engine.start();
this.isActive = true;
} catch (err) {
console.error(`Démarrage impossible : ${(err as BusinessError).message}`);
}
}
async endListening(): Promise<void> {
if (this.engine && this.isActive) {
try {
await this.engine.stop();
this.isActive = false;
} catch (err) {
console.error(`Arrêt impossible : ${(err as BusinessError).message}`);
}
}
}
async teardown(): Promise<void> {
if (this.engine) {
await this.endListening();
this.engine.destroy();
this.engine = null;
}
}
}</void></void></void></boolean>
2. Interface utilisateur de capture audio
Composant visuel réactif pour le contrôle de l'enregistrement :
@Component
struct AsrUserInterface {
private asrManager: AsrEngineManager = new AsrEngineManager();
@State displayText: string = 'Appuyez pour dicter...';
@State recording: boolean = false;
@State confidenceScore: number = 0;
aboutToAppear() {
this.asrManager.prepareEngine();
}
async toggleCapture(): Promise<void> {
if (this.recording) {
await this.asrManager.endListening();
this.recording = false;
} else {
await this.asrManager.beginListening();
this.recording = true;
this.asrManager.onResult((text: string, score: number) => {
this.displayText = text;
this.confidenceScore = score;
});
}
}
build() {
Column() {
if (this.recording) {
VoiceWaveAnimation().height(80).margin({ bottom: 20 })
}
Text(this.displayText)
.fontSize(18)
.textAlign(TextAlign.Center)
.margin({ bottom: 16 })
.minHeight(100)
.width('90%')
if (this.confidenceScore > 0) {
Text(`Fiabilité : ${(this.confidenceScore * 100).toFixed(1)}%`)
.fontSize(14)
.fontColor('#888')
.margin({ bottom: 20 })
}
Button(this.recording ? 'Terminer' : 'Démarrer la dictée')
.width(200)
.height(60)
.backgroundColor(this.recording ? '#D32F2F' : '#1976D2')
.onClick(() => this.toggleCapture())
.margin({ bottom: 30 })
}
.width('100%')
.height('100%')
.padding(20)
.alignItems(HorizontalAlign.Center)
}
}</void>
Module de synthèse vocale (TTS)
1. Orchestration de la synthèse audio
Transformation des chaînes de caractères en flux audio :
import textToSpeech from '@ohos.textToSpeech';
import { BusinessError } from '@ohos.base';
export class TtsEngineManager {
private synthesizer: textToSpeech.TtsEngine | null = null;
@State speaking: boolean = false;
@State voices: textToSpeech.VoiceInfo[] = [];
@State activeVoice: string = '';
async prepareEngine(): Promise<boolean> {
try {
this.synthesizer = textToSpeech.createTtsEngine();
this.voices = await this.synthesizer.getVoices();
this.activeVoice = this.voices[0]?.voiceId || '';
const settings: textToSpeech.TtsConfig = {
voiceId: this.activeVoice,
speed: 1.0,
pitch: 1.0,
volume: 0.8,
audioStreamType: textToSpeech.AudioStreamType.STREAM_MUSIC
};
await this.synthesizer.setConfig(settings);
return true;
} catch (err) {
console.error(`Échec TTS : ${(err as BusinessError).message}`);
return false;
}
}
async vocalize(content: string, customSettings?: textToSpeech.TtsConfig): Promise<void> {
if (!this.synthesizer) await this.prepareEngine();
try {
if (customSettings) await this.synthesizer.setConfig(customSettings);
this.speaking = true;
await this.synthesizer.speak(content);
this.synthesizer.on('finish', () => {
this.speaking = false;
});
this.synthesizer.on('error', (err: BusinessError) => {
this.speaking = false;
console.error(`Erreur de lecture : ${err.message}`);
});
} catch (err) {
console.error(`Échec de vocalisation : ${(err as BusinessError).message}`);
}
}
async haltPlayback(): Promise<void> {
if (this.synthesizer && this.speaking) {
try {
await this.synthesizer.stop();
this.speaking = false;
} catch (err) {
console.error(`Interruption échouée : ${(err as BusinessError).message}`);
}
}
}
}</void></void></boolean>
2. Panneau de contrôle de lecture
Interface permettant l'ajustement dynamique des paramètres vocaux :
@Component
struct TtsUserInterface {
private ttsManager: TtsEngineManager = new TtsEngineManager();
@State inputText: string = '';
@State playing: boolean = false;
@State rate: number = 1.0;
@State tone: number = 1.0;
aboutToAppear() {
this.ttsManager.prepareEngine();
}
async triggerSpeech(): Promise<void> {
if (this.inputText.trim() === '') {
prompt.showToast({ message: 'Texte requis' });
return;
}
const audioProfile: textToSpeech.TtsConfig = {
speed: this.rate,
pitch: this.tone,
volume: 0.8
};
if (this.playing) {
await this.ttsManager.haltPlayback();
this.playing = false;
} else {
await this.ttsManager.vocalize(this.inputText, audioProfile);
this.playing = true;
}
}
build() {
Column() {
TextArea({ text: this.inputText, placeholder: 'Saisissez le texte à synthétiser...' })
.height(150)
.width('90%')
.margin({ bottom: 20 })
.onChange((val: string) => { this.inputText = val; })
Column() {
Text('Paramètres acoustiques').fontSize(16).fontWeight(FontWeight.Bold).margin({ bottom: 12 })
Row() {
Text('Vitesse:').width(80)
Slider({ value: this.rate, min: 0.5, max: 2.0, step: 0.1 })
.layoutWeight(1)
.onChange((val: number) => { this.rate = val; })
Text(this.rate.toFixed(1)).width(40)
}.margin({ bottom: 12 })
Row() {
Text('Tonalité:').width(80)
Slider({ value: this.tone, min: 0.5, max: 2.0, step: 0.1 })
.layoutWeight(1)
.onChange((val: number) => { this.tone = val; })
Text(this.tone.toFixed(1)).width(40)
}
}
.width('90%')
.padding(16)
.backgroundColor('#ECEFF1')
.borderRadius(12)
.margin({ bottom: 20 })
Button(this.playing ? 'Interrompre' : 'Synthétiser')
.width(200)
.height(50)
.backgroundColor(this.playing ? '#D32F2F' : '#388E3C')
.onClick(() => this.triggerSpeech())
}
.width('100%')
.height('100%')
.padding(20)
.alignItems(HorizontalAlign.Center)
}
}</void>
Synergie entre notificatoins et capacités vocales
1. Annonce automatique des alertes
Liaison du flux de notifications avec le moteur de synthèse pour une restitution auditive :
export class NotificationAnnouncer {
private tts: TtsEngineManager;
constructor() {
this.tts = new TtsEngineManager();
this.bindListener();
}
private bindListener(): void {
notificationManager.subscribe({
onNotificationPublished: async (alert: notificationManager.NotificationRequest) => {
if (alert.slotId === 'slot_critical' || alert.slotId === 'slot_social') {
const heading = alert.content.normal?.title || '';
const body = alert.content.normal?.text || '';
await this.announce(heading, body);
}
}
});
}
private async announce(heading: string, body: string): Promise<void> {
const script = `Nouvelle alerte : ${heading}. ${body}`;
await this.tts.vocalize(script, {
speed: 1.0,
pitch: 1.1,
volume: 0.9
});
}
async processVoiceDirective(directive: string): Promise<void> {
const cmd = directive.toLowerCase();
if (cmd.includes('lire les alertes')) {
await this.readAll();
} else if (cmd.includes('effacer les alertes')) {
await notificationManager.cancelAll();
await this.tts.vocalize('File d\'attente vidée');
}
}
private async readAll(): Promise<void> {
try {
const queue = await notificationManager.getActiveNotifications();
if (queue.length === 0) {
await this.tts.vocalize('Aucune alerte en attente');
return;
}
await this.tts.vocalize(`${queue.length} éléments en attente`);
for (const item of queue) {
await this.announce(item.content.normal?.title || '', item.content.normal?.text || '');
await new Promise(r => setTimeout(r, 800));
}
} catch (err) {
console.error(`Lecture séquentielle échouée : ${(err as BusinessError).message}`);
}
}
}</void></void></void>
2. Agrégation au sein d'un assistant intelligent
Composant unifié combinant l'écoute, le traitement sémantique et la restitution :
@Component
struct SmartVoiceAssistant {
private asr: AsrEngineManager = new AsrEngineManager();
private tts: TtsEngineManager = new TtsEngineManager();
private announcer: NotificationAnnouncer = new NotificationAnnouncer();
@State operational: boolean = false;
@State history: string[] = [];
aboutToAppear() {
this.bootServices();
}
async bootServices(): Promise<void> {
await this.asr.prepareEngine();
await this.tts.prepareEngine();
this.asr.onResult((text: string, score: number) => {
if (score > 0.75) {
this.executeDirective(text);
this.history = [text, ...this.history.slice(0, 4)];
}
});
}
private async executeDirective(cmd: string): Promise<void> {
if (cmd.includes('alerte') || cmd.includes('notification')) {
await this.announcer.processVoiceDirective(cmd);
} else if (cmd.includes('heure')) {
const now = new Date();
await this.tts.vocalize(`Il est ${now.getHours()} heures et ${now.getMinutes()} minutes`);
} else {
await this.tts.vocalize(`Commande exécutée : ${cmd}`);
}
}
async toggleState(): Promise<void> {
if (this.operational) {
await this.asr.endListening();
await this.tts.haltPlayback();
this.operational = false;
} else {
await this.asr.beginListening();
await this.tts.vocalize('Assistant en écoute');
this.operational = true;
}
}
build() {
Column() {
Text(this.operational ? 'Écoute active...' : 'Assistant en veille')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 20 })
Button(this.operational ? 'Désactiver' : 'Activer l\'assistant')
.width(250)
.height(60)
.backgroundColor(this.operational ? '#D32F2F' : '#1976D2')
.onClick(() => this.toggleState())
.margin({ bottom: 30 })
if (this.history.length > 0) {
Column() {
Text('Historique récent').fontSize(16).fontWeight(FontWeight.Bold).margin({ bottom: 12 })
ForEach(this.history, (cmd: string) => {
Text(`> ${cmd}`).fontSize(14).width('90%').margin({ bottom: 8 })
})
}
.width('90%')
.padding(16)
.backgroundColor('#ECEFF1')
.borderRadius(12)
}
}
.width('100%')
.height('100%')
.padding(20)
.alignItems(HorizontalAlign.Center)
}
}</void></void></void>
Optimisation des ressources et de l'énergie
1. Allocation dynamique des instances
Contrôle strict de la mémoire et des threads audio pour éviter les fuites :
export class ResourceAllocator {
private static ttsCount: number = 0;
private static readonly LIMIT: number = 2;
static async acquireTts(): Promise<texttospeech.ttsengine> {
if (this.ttsCount >= this.LIMIT) {
throw new Error('Quota d\'instances TTS dépassé');
}
const instance = textToSpeech.createTtsEngine();
this.ttsCount++;
instance.on('destroy', () => {
this.ttsCount--;
});
return instance;
}
static tuneAsrProfile(): speechRecognizer.RecognizerConfig {
return {
language: 'zh-CN',
mode: speechRecognizer.RecognizeMode.FREE_FORM,
audioSource: speechRecognizer.AudioSource.MICROPHONE,
bufferSize: 4096,
sampleRate: 16000,
encoding: speechRecognizer.AudioEncoding.ENCODING_PCM_16BIT
};
}
}</texttospeech.ttsengine>
2. Adaptation contextuelle au réseau et à la batterie
Modulation de la qualité audio et du payload des notifications selon l'environnement :
export class EnergyAndNetworkManager {
static checkBatteryStatus(): boolean {
const level = deviceInfo.getBatteryLevel();
return level < 15;
}
static adaptTtsProfile(): textToSpeech.TtsConfig {
const depleted = this.checkBatteryStatus();
return {
speed: depleted ? 1.3 : 1.0,
pitch: 1.0,
volume: 0.7,
audioStreamType: depleted ?
textToSpeech.AudioStreamType.STREAM_VOICE_CALL :
textToSpeech.AudioStreamType.STREAM_MUSIC
};
}
static async dispatchSmartAlert(payload: notificationManager.NotificationRequest): Promise<void> {
const connection = network.getNetworkType();
if (connection === network.NetworkType.NETWORK_MOBILE) {
if (payload.content.normal && payload.content.normal.text.length > 80) {
payload.content.normal.text = payload.content.normal.text.substring(0, 80) + '...';
}
}
await notificationManager.publish(payload);
}
}</void>