Skip to content

Commit 34776f1

Browse files
committed
WebAssembly specific extension methods causing issues in shared bits
1 parent caf922e commit 34776f1

File tree

8 files changed

+77
-70
lines changed

8 files changed

+77
-70
lines changed

app/SharedWebComponents/Components/VoiceDialog.razor.cs

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ public sealed partial class VoiceDialog : IDisposable
1212

1313
[Inject] public required ISpeechSynthesisService SpeechSynthesis { get; set; }
1414

15+
[Inject] public required IVoiceChangesListener VoiceChangesListener { get; set; }
16+
1517
[Inject] public required ILocalStorageService LocalStorage { get; set; }
1618

1719
[CascadingParameter] public required MudDialogInstance Dialog { get; set; }
@@ -22,19 +24,7 @@ protected override async Task OnInitializedAsync()
2224

2325
await GetVoicesAsync();
2426

25-
try
26-
{
27-
SpeechSynthesis.OnVoicesChanged(() => GetVoicesAsync(true));
28-
}
29-
catch
30-
{
31-
// TODO: Find a better way to do this
32-
// The Blazor.SpeechSynthesis.WebAssembly API supports listening to changes,
33-
// however the underlying code does not do this using a DI-friendly way.
34-
// The code assumes the concrete implementation for the ISpeechSynthesisService
35-
// service is the concrete Web Assembly type which is not valid.
36-
// There is no alternative API that MAUI apps can use.
37-
}
27+
VoiceChangesListener.OnListenForVoiceChanges(() => GetVoicesAsync(true));
3828

3929
_voicePreferences = new VoicePreferences(LocalStorage);
4030

@@ -70,19 +60,7 @@ private void OnValueChanged(string selectedVoice) => _voicePreferences = _voiceP
7060

7161
public void Dispose()
7262
{
73-
try
74-
{
75-
SpeechSynthesis.UnsubscribeFromVoicesChanged();
76-
}
77-
catch
78-
{
79-
// TODO: Find a better way to do this
80-
// The Blazor.SpeechSynthesis.WebAssembly API supports listening to changes,
81-
// however the underlying code does not do this using a DI-friendly way.
82-
// The code assumes the concrete implementation for the ISpeechSynthesisService
83-
// service is the concrete Web Assembly type which is not valid.
84-
// There is no alternative API that MAUI apps can use.
85-
}
63+
VoiceChangesListener.UnsubscribeFromVoiceChanges();
8664
}
8765
}
8866

app/SharedWebComponents/Pages/VoiceChat.razor.cs

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -79,28 +79,11 @@ private void OnSendPrompt()
7979
Name = voice
8080
};
8181
}
82-
try
83-
{
84-
SpeechSynthesis.Speak(utterance, duration =>
85-
{
86-
_isReadingResponse = false;
87-
StateHasChanged();
88-
});
89-
}
90-
catch
91-
{
92-
// TODO: Find a better way to do this
93-
// The Blazor.SpeechSynthesis.WebAssembly API supports the callback,
94-
// however the underlying code does not do this using a DI-friendly way.
95-
// The code assumes the concrete implementation for the ISpeechSynthesisService
96-
// service is the concrete Web Assembly type which is not valid. However, the
97-
// alternate API is something MAUI can implement.
9882

99-
SpeechSynthesis.Speak(utterance);
83+
SpeechSynthesis.Speak(utterance);
10084

101-
_isReadingResponse = false;
102-
StateHasChanged();
103-
}
85+
_isReadingResponse = false;
86+
StateHasChanged();
10487
}
10588
}
10689

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
namespace SharedWebComponents.Services;
4+
5+
public interface IVoiceChangesListener
6+
{
7+
void OnListenForVoiceChanges(Func<Task> onVoicesChanged);
8+
9+
void UnsubscribeFromVoiceChanges();
10+
}

app/frontend/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
builder.Services.AddSessionStorageServices();
1919
builder.Services.AddSpeechSynthesisServices();
2020
builder.Services.AddSpeechRecognitionServices();
21+
builder.Services.AddSingleton<IVoiceChangesListener, VoiceChangesListenerService>();
2122
builder.Services.AddMudServices();
2223
builder.Services.AddTransient<IPdfViewer, WebPdfViewer>();
2324

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using Microsoft.JSInterop;
4+
5+
namespace ClientApp.Services;
6+
7+
public sealed class VoiceChangesListenerService(
8+
ISpeechSynthesisService speechSynthesisService) : IVoiceChangesListener
9+
{
10+
public void OnListenForVoiceChanges(Func<Task> onVoicesChanged) =>
11+
speechSynthesisService.OnVoicesChanged(onVoicesChanged);
12+
13+
public void UnsubscribeFromVoiceChanges() =>
14+
speechSynthesisService.UnsubscribeFromVoicesChanged();
15+
}

app/maui-blazor/MauiProgram.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public static MauiApp CreateMauiApp()
3333
builder.Services.AddSingleton<ISessionStorageService, MauiSessionStorageService>();
3434
builder.Services.AddSingleton<ISpeechRecognitionService, MauiSpeechRecognitionService>();
3535
builder.Services.AddSingleton<ISpeechSynthesisService, MauiSpeechSynthesisService>();
36+
builder.Services.AddSingleton<IVoiceChangesListener, MauiSpeechSynthesisService>();
3637
builder.Services.AddTransient<IPdfViewer, MauiPdfViewer>();
3738

3839
builder.Services.AddMudServices();

app/maui-blazor/Services/MauiSpeechRecognitionService.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22

33
namespace MauiBlazor.Services;
44

5-
public class MauiSpeechRecognitionService(ISpeechToText speechToText) : ISpeechRecognitionService
5+
public sealed class MauiSpeechRecognitionService(ISpeechToText speechToText) : ISpeechRecognitionService
66
{
77
private SpeechRecognitionOperation? _current;
88

99
public void CancelSpeechRecognition(bool isAborted)
1010
{
11+
_ = isAborted;
1112
_current?.Dispose();
1213
}
1314

@@ -19,6 +20,7 @@ public ValueTask DisposeAsync()
1920

2021
public Task InitializeModuleAsync(bool logModuleDetails = true)
2122
{
23+
_ = logModuleDetails;
2224
return Task.CompletedTask;
2325
}
2426

app/maui-blazor/Services/MauiSpeechSynthesisService.cs

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
namespace MauiBlazor.Services;
44

5-
public class MauiSpeechSynthesisService : ISpeechSynthesisService
5+
public class MauiSpeechSynthesisService(ITextToSpeech textToSpeech)
6+
: ISpeechSynthesisService, IVoiceChangesListener
67
{
78
private CancellationTokenSource? _cts;
9+
private Task? _speakTask;
810

911
public bool Paused => throw new NotImplementedException();
1012

@@ -28,6 +30,11 @@ public ValueTask<SpeechSynthesisVoice[]> GetVoicesAsync()
2830
return ValueTask.FromResult<SpeechSynthesisVoice[]>([voice]);
2931
}
3032

33+
public void OnListenForVoiceChanges(Func<Task> onVoicesChanged)
34+
{
35+
_ = onVoicesChanged;
36+
}
37+
3138
public void Pause()
3239
{
3340
Cancel();
@@ -40,35 +47,45 @@ public void Resume()
4047
// TODO: support pause & resume
4148
}
4249

43-
public async void Speak(SpeechSynthesisUtterance utterance)
50+
public void Speak(SpeechSynthesisUtterance utterance)
4451
{
52+
_cts?.Cancel();
4553
_cts = new();
4654

47-
var current = CultureInfo.CurrentUICulture.Name;
48-
49-
var locales = await TextToSpeech.Default.GetLocalesAsync();
50-
var localeArray = locales.ToArray();
51-
var locale = localeArray.FirstOrDefault(l => current == $"{l.Language}-{l.Country}");
52-
if (locale is null)
55+
_speakTask = Task.Run(async () =>
5356
{
54-
// an exact match was not found, try just the lang
55-
var split = current.Split('-');
56-
if (split.Length == 1 || split.Length == 2)
57+
var current = CultureInfo.CurrentUICulture.Name;
58+
59+
var locales = await textToSpeech.GetLocalesAsync();
60+
var localeArray = locales.ToArray();
61+
var locale = localeArray.FirstOrDefault(l => current == $"{l.Language}-{l.Country}");
62+
if (locale is null)
5763
{
58-
// try the first part (or the whole thing if it is just lang)
59-
locale = localeArray.FirstOrDefault(l => split[0] == $"{l.Language}");
64+
// an exact match was not found, try just the lang
65+
var split = current.Split('-');
66+
if (split.Length is 1 or 2)
67+
{
68+
// try the first part (or the whole thing if it is just lang)
69+
locale = localeArray.FirstOrDefault(l => split[0] == $"{l.Language}");
70+
}
71+
else
72+
{
73+
// just go with the first one
74+
locale = localeArray.FirstOrDefault();
75+
}
6076
}
61-
else
77+
78+
var options = new SpeechOptions
6279
{
63-
// just go with the first one
64-
locale = localeArray.FirstOrDefault();
65-
}
66-
}
80+
Locale = locale
81+
};
6782

68-
var options = new SpeechOptions
69-
{
70-
Locale = locale
71-
};
72-
await TextToSpeech.Default.SpeakAsync(utterance.Text, options, _cts.Token);
83+
await textToSpeech.SpeakAsync(utterance.Text, options, _cts.Token);
84+
}, _cts.Token);
85+
}
86+
87+
public void UnsubscribeFromVoiceChanges()
88+
{
89+
7390
}
7491
}

0 commit comments

Comments
 (0)