File: TextToSpeech\TextToSpeech.ios.tvos.watchos.cs
Web Access
Project: src\src\Essentials\src\Essentials.csproj (Microsoft.Maui.Essentials)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AVFoundation;
 
namespace Microsoft.Maui.Media
{
	partial class TextToSpeechImplementation : ITextToSpeech
	{
#pragma warning disable CA1416 // https://github.com/xamarin/xamarin-macios/issues/14619
		readonly Lazy<AVSpeechSynthesizer> speechSynthesizer = new(() => new AVSpeechSynthesizer());
 
		Task<IEnumerable<Locale>> PlatformGetLocalesAsync() =>
			Task.FromResult(AVSpeechSynthesisVoice.GetSpeechVoices()
				.Select(v => new Locale(v.Language, null, v.Name, v.Identifier)));
 
		async Task PlatformSpeakAsync(string text, SpeechOptions options, CancellationToken cancelToken)
		{
			using var speechUtterance = GetSpeechUtterance(text, options);
			await SpeakUtterance(speechUtterance, cancelToken);
		}
 
		static AVSpeechUtterance GetSpeechUtterance(string text, SpeechOptions options)
		{
			var speechUtterance = new AVSpeechUtterance(text);
 
			if (options != null)
			{
				// null voice if fine - it is the default
				speechUtterance.Voice =
					AVSpeechSynthesisVoice.FromLanguage(options.Locale?.Language) ??
					AVSpeechSynthesisVoice.FromLanguage(AVSpeechSynthesisVoice.CurrentLanguageCode);
 
				// the platform has a range of 0.5 - 2.0
				// anything lower than 0.5 is set to 0.5
				if (options.Pitch.HasValue)
					speechUtterance.PitchMultiplier = options.Pitch.Value;
 
				if (options.Volume.HasValue)
					speechUtterance.Volume = options.Volume.Value;
			}
 
			return speechUtterance;
		}
 
		async Task SpeakUtterance(AVSpeechUtterance speechUtterance, CancellationToken cancelToken)
		{
			var tcsUtterance = new TaskCompletionSource<bool>();
			try
			{
				speechSynthesizer.Value.DidFinishSpeechUtterance += OnFinishedSpeechUtterance;
				speechSynthesizer.Value.SpeakUtterance(speechUtterance);
				using (cancelToken.Register(TryCancel))
				{
					await tcsUtterance.Task;
				}
			}
			finally
			{
				speechSynthesizer.Value.DidFinishSpeechUtterance -= OnFinishedSpeechUtterance;
			}
 
			void TryCancel()
			{
				speechSynthesizer.Value?.StopSpeaking(AVSpeechBoundary.Immediate);
				tcsUtterance?.TrySetResult(true);
			}
 
			void OnFinishedSpeechUtterance(object sender, AVSpeechSynthesizerUteranceEventArgs args)
			{
				if (speechUtterance == args.Utterance)
					tcsUtterance?.TrySetResult(true);
			}
		}
#pragma warning restore CA1416
	}
}