Skip to content

Commit 33d2689

Browse files
SpeechToText #808
1 parent 0df52ee commit 33d2689

18 files changed

+796
-1
lines changed

samples/CommunityToolkit.Maui.Sample/AppShell.xaml.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ public partial class AppShell : Shell
8484
// Add Essentials View Models
8585
CreateViewModelMapping<FileSaverPage, FileSaverViewModel, EssentialsGalleryPage, EssentialsGalleryViewModel>(),
8686
CreateViewModelMapping<FolderPickerPage, FolderPickerViewModel, EssentialsGalleryPage, EssentialsGalleryViewModel>(),
87+
CreateViewModelMapping<SpeechToTextPage, SpeechToTextViewModel, EssentialsGalleryPage, EssentialsGalleryViewModel>(),
8788

8889
// Add Extensions View Models
8990
CreateViewModelMapping<ColorAnimationExtensionsPage, ColorAnimationExtensionsViewModel, ExtensionsGalleryPage, ExtensionsGalleryViewModel>(),

samples/CommunityToolkit.Maui.Sample/MauiProgram.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
using CommunityToolkit.Maui.Sample.ViewModels.Layouts;
2020
using CommunityToolkit.Maui.Sample.ViewModels.Views;
2121
using CommunityToolkit.Maui.Sample.ViewModels.Views.AvatarView;
22+
using CommunityToolkit.Maui.SpeechToText;
2223
using CommunityToolkit.Maui.Storage;
2324
using Microsoft.Extensions.Logging;
2425
using Polly;
@@ -153,6 +154,7 @@ static void RegisterViewsAndViewModels(in IServiceCollection services)
153154
// Add Essentials Pages + ViewModels
154155
services.AddTransientWithShellRoute<FileSaverPage, FileSaverViewModel>();
155156
services.AddTransientWithShellRoute<FolderPickerPage, FolderPickerViewModel>();
157+
services.AddTransientWithShellRoute<SpeechToTextPage, SpeechToTextViewModel>();
156158

157159
// Add Extensions Pages + ViewModels
158160
services.AddTransientWithShellRoute<ColorAnimationExtensionsPage, ColorAnimationExtensionsViewModel>();
@@ -187,6 +189,8 @@ static void RegisterEssentials(in IServiceCollection services)
187189
services.AddSingleton<IDeviceDisplay>(DeviceDisplay.Current);
188190
services.AddSingleton<IFileSaver>(FileSaver.Default);
189191
services.AddSingleton<IFolderPicker>(FolderPicker.Default);
192+
services.AddSingleton<ITextToSpeech>(TextToSpeech.Default);
193+
services.AddSingleton<ISpeechToText>(SpeechToText.SpeechToText.Default);
190194
}
191195

192196
static IServiceCollection AddTransientWithShellRoute<TPage, TViewModel>(this IServiceCollection services) where TPage : BasePage<TViewModel>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<pages:BasePage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
4+
xmlns:pages="clr-namespace:CommunityToolkit.Maui.Sample.Pages"
5+
x:Class="CommunityToolkit.Maui.Sample.Pages.Essentials.SpeechToTextPage"
6+
xmlns:vm="clr-namespace:CommunityToolkit.Maui.Sample.ViewModels.Essentials"
7+
xmlns:essentials="clr-namespace:CommunityToolkit.Maui.Sample.Pages.Essentials"
8+
x:TypeArguments="vm:SpeechToTextViewModel"
9+
x:DataType="vm:SpeechToTextViewModel"
10+
Title="SpeechToText">
11+
12+
<ContentPage.Resources>
13+
<essentials:PickerDisplayConverter x:Key="PickerDisplayConverter" />
14+
</ContentPage.Resources>
15+
16+
<ScrollView>
17+
<VerticalStackLayout
18+
Spacing="25"
19+
Padding="30,0"
20+
VerticalOptions="Center">
21+
22+
<Picker ItemsSource="{Binding Locales}"
23+
SelectedItem="{Binding Locale}"
24+
ItemDisplayBinding="{Binding ., Converter={StaticResource PickerDisplayConverter}}">
25+
</Picker>
26+
27+
<Editor
28+
Text="{Binding Text}"
29+
FontSize="18"
30+
HorizontalOptions="Center" />
31+
32+
<Button
33+
Text="Play"
34+
Command="{Binding PlayCommand}"
35+
HorizontalOptions="Center" />
36+
37+
<Label
38+
Text="{Binding RecognitionText}"
39+
FontSize="18"
40+
HorizontalOptions="Center" />
41+
42+
<Button
43+
Text="Listen"
44+
Command="{Binding ListenCommand}"
45+
HorizontalOptions="Center" />
46+
47+
<Button
48+
Text="Listen Cancel"
49+
Command="{Binding ListenCancelCommand}"
50+
HorizontalOptions="Center" />
51+
52+
</VerticalStackLayout>
53+
</ScrollView>
54+
55+
</pages:BasePage>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System.Globalization;
2+
using CommunityToolkit.Maui.Sample.ViewModels.Essentials;
3+
4+
namespace CommunityToolkit.Maui.Sample.Pages.Essentials;
5+
6+
public partial class SpeechToTextPage : BasePage<SpeechToTextViewModel>
7+
{
8+
public SpeechToTextPage(SpeechToTextViewModel viewModel) : base(viewModel)
9+
{
10+
InitializeComponent();
11+
}
12+
}
13+
14+
public class PickerDisplayConverter : IValueConverter
15+
{
16+
public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
17+
{
18+
if (value is Locale locale)
19+
{
20+
return $"{locale.Language} {locale.Name}";
21+
}
22+
23+
return null;
24+
}
25+
26+
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
27+
{
28+
throw new NotImplementedException();
29+
}
30+
}

samples/CommunityToolkit.Maui.Sample/Platforms/Android/AndroidManifest.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@
44
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
55
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
66
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
7+
8+
<uses-permission android:name="android.permission.RECORD_AUDIO" />
79
</manifest>

samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/EssentialsGalleryViewModel.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ public EssentialsGalleryViewModel()
88
: base(new[]
99
{
1010
SectionModel.Create<FileSaverViewModel>("FileSaver", "Allows the user to save files to the filesystem"),
11-
SectionModel.Create<FolderPickerViewModel>("FolderPicker", "Allows picking folders from the file system")
11+
SectionModel.Create<FolderPickerViewModel>("FolderPicker", "Allows picking folders from the file system"),
12+
SectionModel.Create<SpeechToTextViewModel>("SpeechToText", "Allows converts speech to text"),
1213
})
1314
{
1415
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using System.Globalization;
2+
using CommunityToolkit.Maui.Alerts;
3+
using CommunityToolkit.Maui.SpeechToText;
4+
using CommunityToolkit.Mvvm.ComponentModel;
5+
using CommunityToolkit.Mvvm.Input;
6+
7+
namespace CommunityToolkit.Maui.Sample.ViewModels.Essentials;
8+
9+
public partial class SpeechToTextViewModel : BaseViewModel
10+
{
11+
readonly ITextToSpeech textToSpeech;
12+
readonly ISpeechToText speechToText;
13+
[ObservableProperty]
14+
List<Locale>? locales;
15+
16+
[ObservableProperty]
17+
Locale? locale;
18+
19+
[ObservableProperty]
20+
string text;
21+
22+
[ObservableProperty]
23+
string? recognitionText;
24+
25+
public SpeechToTextViewModel(ITextToSpeech textToSpeech, ISpeechToText speechToText)
26+
{
27+
this.textToSpeech = textToSpeech;
28+
this.speechToText = speechToText;
29+
Locales = new();
30+
text = @"Welcome to .NET MAUI Community Toolkit!";
31+
SetLocalesCommand.Execute(null);
32+
}
33+
34+
[RelayCommand]
35+
async Task SetLocales()
36+
{
37+
Locales = (await textToSpeech.GetLocalesAsync()).ToList();
38+
Locale = Locales.FirstOrDefault();
39+
}
40+
41+
[RelayCommand]
42+
async Task Play(CancellationToken cancellationToken)
43+
{
44+
await textToSpeech.SpeakAsync(Text, new SpeechOptions()
45+
{
46+
Locale = Locale,
47+
Pitch = 2,
48+
Volume = 1
49+
}, cancellationToken);
50+
}
51+
52+
[RelayCommand(IncludeCancelCommand = true)]
53+
async Task Listen(CancellationToken cancellationToken)
54+
{
55+
try
56+
{
57+
RecognitionText = await speechToText.Listen(CultureInfo.GetCultureInfo(Locale?.Language ?? "en-us"), new Progress<string>(partialText =>
58+
{
59+
RecognitionText += partialText + " ";
60+
}), cancellationToken);
61+
}
62+
catch (Exception ex)
63+
{
64+
await Toast.Make(ex.Message).Show(CancellationToken.None);
65+
}
66+
}
67+
}

src/CommunityToolkit.Maui.Core/CommunityToolkit.Maui.Core.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,7 @@
5555
<PrivateAssets>all</PrivateAssets>
5656
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
5757
</PackageReference>
58+
59+
<PackageReference Include="System.Speech" Version="7.0.0" Condition="'$(TargetFramework)' == 'net7.0-windows10.0.19041.0'" />
5860
</ItemGroup>
5961
</Project>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System.Globalization;
2+
3+
namespace CommunityToolkit.Maui.SpeechToText;
4+
5+
/// <summary>
6+
/// Allows the user to convert speech to text in real time.
7+
/// </summary>
8+
public interface ISpeechToText : IAsyncDisposable
9+
{
10+
/// <summary>
11+
/// Converts speech to text in real time.
12+
/// </summary>
13+
/// <param name="culture">Speak language</param>
14+
/// <param name="recognitionResult">Intermediate convertion result.</param>
15+
/// <param name="cancellationToken"><see cref="CancellationToken"/></param>
16+
/// <returns>Final convertion result</returns>
17+
Task<string> Listen(CultureInfo culture, IProgress<string>? recognitionResult, CancellationToken cancellationToken);
18+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System.Globalization;
2+
3+
namespace CommunityToolkit.Maui.SpeechToText;
4+
5+
/// <inheritdoc cref="ISpeechToText"/>
6+
public static class SpeechToText
7+
{
8+
static Lazy<ISpeechToText> defaultImplementation = new(new SpeechToTextImplementation());
9+
10+
/// <summary>
11+
/// Default implementation of <see cref="ISpeechToText"/>
12+
/// </summary>
13+
public static ISpeechToText Default => defaultImplementation.Value;
14+
15+
/// <inheritdoc cref="ISpeechToText.Listen"/>
16+
public static Task<string> Listen(CultureInfo culture, IProgress<string>? recognitionResult, CancellationToken cancellationToken) =>
17+
Default.Listen(culture, recognitionResult, cancellationToken);
18+
19+
internal static void SetDefault(ISpeechToText implementation) =>
20+
defaultImplementation = new(implementation);
21+
}

0 commit comments

Comments
 (0)