0

Just need to speak a text string from the ViewModel (inherited from MVVM Light ViewModelBase) to the MediaElement on the XAML page.

var synthesisStream = await synthesizer.SynthesizeSsmlToStreamAsync(text);
media.AutoPlay = true;
media.SetSource(synthesisStream, synthesisStream.ContentType);
media.Play();

The code above has no separation of ViewModel. We see media is directly handled in code-behind.

In my ViewModel, I stopped at

var synthesisStream = await synthesizer.SynthesizeSsmlToStreamAsync(text);

var msg=new PlaySpeechSynthesisStreamMessage(synthesisStream);
Messenger.Default.Send<PlaySpeechSynthesisStreamMessage>(msg);

For the message:

public class PlaySpeechSynthesisStreamMessage
{
    public SpeechSynthesisStream Stream { get; set; }

    public PlaySpeechSynthesisStreamMessage(SpeechSynthesisStream stream)
    {
        Stream = stream;
    }
}

Is Messenger the right way to handle this situation? How can we write a RelayCommand or something to pass the stream to media?

A related article MVVM pattern violation: MediaElement.Play() seems to address this issue, but it is not in MVVM Light and there is no way to pass the stream, either.

Community
  • 1
  • 1
Blaise
  • 21,314
  • 28
  • 108
  • 169

1 Answers1

0

I think a message is a good solution to handle this kind of situation. You just have to complement the sending of the message in the ViewModel with the handling of it in the View:

Messenger.Default.Register<PlaySpeechSynthesisStreamMessage>(this, msg => {
    media.AutoPlay = true;
    media.SetSource(msg.Stream, msg.Stream.ContentType);
    media.Play();
});

Alternatively, you can use the event approach described in the question you cited. In this case you will have to define a class that inherits from EventArgs with a property of type SpeechSynthesisStream, then define your event as follow:

public event EventHandler<YourEventArgsClass> PlaySpeechSynthesisStreamEvent;

and raise it this way:

var synthesisStream = await synthesizer.SynthesizeSsmlToStreamAsync(text);
var eventArgs = new YourEventArgsClass(synthesisStream);
if (PlaySpeechSynthesisStreamEvent != null)
    PlaySpeechSynthesisStreamEvent(this, eventArgs);

In this case of course you will have to handle the event in the View.

I find the solution with the event handler a little bit trickier than the one with messages, because you will have to wire the event handling to the DataContext of the View and, depending of how the application is structured, the DataContext property of the View could not always be available from the beginning of the View lifetime: for example, in many cases I tend to set it via a Navigation Service and / or Bootstrapper during the navigation to the view: in this case, DataContext is null in the costructor of the View so it is impossible to wire the event handler there. So, you have to find another place to wire it, remembering that methods such OnNavigatedFrom (e.g., in Windows 10 UWP apps) can be called more than once in the life cycle of the view and surely we don't want to wire the event handler more than once. If the framework exposes it (such in Windows 10 UWP), the DataContextChanged event could be a good place to wire event handlers related to the ViewModel (and possibly to remove previous ones, if an instance of a View can be used with different instance of the ViewModel class during the lifetime of the application).

Francesco Blue
  • 476
  • 3
  • 10