Trois étapes pour intégrer facilement votre application Windows Phone à Cortana

Bonjour, je suis Cortana.

Cortana_FirstRun_Hello_01_15x9_fr-frC’était promis depuis son lancement à l’évènement //build/ en avril dernier, Cortana a pris des cours de français et arrive sur notre vieux continent. Elle est disponible depuis le 5 décembre, en version Alpha, pour la France, l’Espagne, l’Italie et l’Allemagne. Cette mise à jour est principalement adressée aux développeurs pour 1/ participer à l’amélioration du service et nous proposer vos retours et 2/ en profiter pour intégrer vos applications avec Cortana. Pour découvrir la voix française  de Cortana et bénéficier de ses services, il suffit simplement d’être inscrit au programme Windows Phone Preview pour les développeurs que vous connaissez déjà certainement !

Version alpha ?

Pour développer Cortana, nous utilisons des techniques d’apprentissage automatique (machine learning) afin de construire des modèles de reconnaissance vocale et de compréhension de langage naturel. Pour les améliorer, nous avons besoin d’utilisateurs comme vous pour discuter avec Cortana ; qu’elle suive, en quelque sorte, des cours accélérés de français.

Cortana ne s’améliorera que si vous interagissez avec elle. En apprenant à vous connaitre au fil du temps, Cortana vous fera des suggestions de plus en plus pertinentes et précises et vous offrira une expérience personnalisée. Si vous souhaitez en découvrir davantage sur Cortana et ses services, rendez-vous sur Découvrez Cortana.

Votre avis compte, n’hésitez pas à nous transmettre vos commentaires : https://cortana.uservoice.com/.

Quel rapport avec les développeurs ?

Windows Phone propose des commandes vocales. Cette fonctionnalité n’est pas récente, elle a été introduite sous Windows Phone 8.0 et permettait le lancement de votre application par reconnaissance vocale. Même si quelques applications ont joué le jeu, le concept n’a pas forcément été des plus suivi par la communauté et la majorité des applications n’utilisait simplement pas (ou mal) cette fonctionnalité.

Pour quelles raisons ? Et bien il faut avouer que pour l’utilisateur, la fonctionnalité manquait un peu de visibilité : si un appui long sur le bouton Windows permettait bien de lancer la reconnaissance vocale, ce n’était clairement pas très intuitif. L’activation de nos commandes vocales était donc un peu difficile à dénicher.

Cortana change la donne, elle prend désormais directement les opérations en main.  Et elle ne se cache pas ! Elle est accessible  par une vignette dynamique ou directement par le bouton recherche du téléphone, elle devient donc la nouvelle interface pour accéder aux données de Bing. A noter au passage que Cortana n’est pas une assistante uniquement vocale, vous pouvez la questionner ou interagir avec elle en mode textuel si vous préférez plus d’intimité ^^.

imageMais revenons aux applications. Cortana permet d’actionner simplement, et en langage naturel, les principales fonctionnalités du téléphone (passer un appel, prendre une note, créer une alarme, …). Et bien vous pouvez dès aujourd’hui étendre ces scénarios et lancer votre application par le biais de nos fameuses commandes vocales.

Dans le principe, les commandes vocales se mettent en place très rapidement en passant par les trois étapes suivantes:

image 1. Création d’un fichier pour définir les commandes vocales
image 2. Enregistrer les commandes vocales au démarrage de l’application
image 3. Gérer l’activation des commandes vocales

Etape 1. Création d’un fichier pour définir les commandes vocales

Ajouter un nouvel élément de type ‘Définition des commandes vocales’ à votre projet.

image

Que vous développiez en Silverlight 8.1 ou en Windows Runtime 8.1 (Application Universelle), la structure du fichier est identique.

 <?xml version="1.0" encoding="utf-8"?>
<VoiceCommands xmlns="https://schemas.microsoft.com/voicecommands/1.1">
  <CommandSet xml:lang="en-us">
    <CommandPrefix>tickets</CommandPrefix>
    <Example>show tonight's events at Olympia</Example>

    <Command Name="showEventsAt">
      <Example>show tonight's events at Olympia</Example>
      <ListenFor> [Show] {eventsViews} at {eventsLocations} </ListenFor>
      <Feedback> Showing {eventsViews} at {eventsLocations} </Feedback>
      <Navigate Target="HomePage.xaml"/>
    </Command>

    <Command Name="showEventsFrom">
      <Example>show tonight's events from Shaka Ponk</Example>
      <ListenFor> [Show] {eventsViews} from {eventsArtists} </ListenFor>
      <Feedback> Showing {eventsViews} from {eventsArtists} </Feedback>
      <Navigate Target="HomePage.xaml"/>
    </Command>

    <PhraseList Label="eventsViews">
      <Item> tonight's events </Item>
      <Item> best sellers </Item>
    </PhraseList>

    <PhraseList Label="eventsLocations">
      <Item> Olympia </Item>
      <Item> Bercy </Item>
    </PhraseList>

    <PhraseTopic Label="eventsArtists" Scenario="Natural Language">
      <Subject>Music</Subject>
      <Subject>Artist</Subject>
    </PhraseTopic>
  </CommandSet>

  <!-- Other CommandSets for other languages -->
</VoiceCommands>

Pour faire rapide, voici les principaux éléments qui composent la structure du fichier. Si vous préférez une documentation de référence, ça se passe ici.

 

-
CommandSet : Liste les commandes vocales pour une langue spécifique. Spécifiez l’attribut xml:lang=”fr-fr” pour supporter le français. Il est possible de supporter 15 langues différentes.

-
CommandPrefix : Par défaut, vous pouvez lancer l’application directement par son nom. Toutefois, si le nom est trop compliqué ou trop long, vous pouvez lui attribuer un diminutif plus convivial à utiliser.

-
Command : Définie chaque commande vocale individuelle que l’application supporte. Vous pouvez enrichir l’application avec un maximum de 100 commandes. Ces commandes sont elles mêmes définies par les éléments enfants suivants:

  -   
    ListenFor : Expression qui va être reconnue par Cortana, et actionner l’interaction avec l’application. La syntaxe \[…\] définie un terme optionnel, alors que la syntaxe {…} pointe sur un label de type PhraseList ou PhraseTopic (nouveauté en 8.1).
      
  -   
    Feedback : Définie comment Cortana doit répondre à l’utilisateur pendant le lancement de l’application.
      
  -   
    Navigate : Précise la page que l’on souhaite afficher au lancement de l’application quand cette commande est invoquée. Ce point est obligatoire en Silverlight, optionnel en Universal.
      
  
  • PhraseList : Une liste de termes pour aider à la compréhension. Un dictionnaire de 2000 éléments peut être renseigné. Comme c’est généralement fastidieux, je vous rassure, ces termes peuvent être dynamiquement ajoutés par code.

  • PhraseTopic : C’est la nouveautés de Windows Phone 8.1. La commande vocale peut s’appuyer sur un label plus ouvert qu’un dictionnaire. Vous demander à Cortana/Bing de reconnaître simplement le mot. Comme la reconnaissance d’un terme est souvent lié à son contexte, je vous invite à préciser l’attribut Scenario et les enfants Subject pour faciliter le travail de reconnaissance et obtenir un résultat pertinent.

Pour résumer, mon exemple propose donc deux commandes, showEventAt et showEventFrom, dont le but est de lister les évènement musicaux par lieux et par artistes. Mes lieux sont définis par un dictionnaire fermé, alors que les artistes sont interprétés directement par le couple Cortana/Bing, dans un mode plus ouvert.

Etape 2. Enregistrer les commandes vocales au démarrage de l’application

L’application doit s’exécuter au moins une fois pour enregistrer les commandes et le rendre disponibles auprès de Cortana. Les quelques lignes suivantes suffisent à enregistrer le fichier vcd.xml et les commandes associées.

Projet Windows Phone 8.1 Xaml/C# (Universal):

 private async void RegisterVoiceCommands()
{
    Uri uriVoiceCommands = new Uri("ms-appx:///vcd.xml", UriKind.Absolute);
    StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(uriVoiceCommands);
    await VoiceCommandManager.InstallCommandSetsFromStorageFileAsync(file);
}

Prérequis: N’oubliez pas d’activer la capacité Microphone dans le fichier Package.appxmanifest.

Projet Windows Phone Silverlight 8.1:

 private async void RegisterVoiceCommands()
{
    Uri uriVoiceCommands = new Uri("ms-appx:///vcd.xml", UriKind.Absolute));
    await VoiceCommandService.InstallCommandSetsFromFileAsync(uriVoiceCommands);
}

Prérequis: N’oubliez pas d’activer les capacités ID_CAP_SPEECH_RECOGNITION, ID_CAP_MICROPHONE, et ID_CAP_NETWORKING dans le fichier WMAppManifest.xml.

 

Etape 3. Gérer l’activation des commandes vocales

Une fois la commande reconnue et interprétée par le système. Cortana se charge de lancer l’application et en profite pour transmettre les informations vocales dictées par l’utilisateur. C’est sur ce point que l’architecture Silverlight vs Universal est la plus différente.

Projet Windows Phone Silverlight 8.1:

image

1.
Le système lance l’application et affiche la page précisée dans l’élément Navigate du fichier de commandes vocales.

2.
Toutes les infos vocales sont transmises par Query String dans le contexte de navigation. Vous pouvez donc extraire ces infos dans la page et proposer un rendu approprié.

 protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
  base.OnNavigatedTo(e);

  // Is this a new activation or a resurrection from tombstone?
  if (e.NavigationMode == System.Windows.Navigation.NavigationMode.New)
  {

    // Was the app launched using a voice command?
    if (NavigationContext.QueryString.ContainsKey("voiceCommandName"))
    {

      // If so, get the name of the voice command.
      string voiceCommandName 
        = NavigationContext.QueryString["voiceCommandName"];

      // Define app actions for each voice command name.
      switch (voiceCommandName)
      {
        case "showWidgets":
          string viewWidget = NavigationContext.QueryString["widgetViews"];
                   
          // Add code to display specified widgets.
          break;
 
        // Add cases for other voice commands. 
        default:

          // There is no match for the voice command name.
          break;
      }
    }
  }
}

 

Projet Windows Phone 8.1 Xaml/C# (Universal):

image

Contrairement à Silverlight, les informations peuvent être interceptés directement dans la classe App via l’évènement OnActivated avant d’être transmise à la page (d’où le caractère optionnel de l’élément Navigate dans le fichier de commandes vocales). C’est donc à vous de rediriger l’utilisateur vers la page de votre choix !

IActivatedEventArgs permet de vérifier 1/ si l’activation de notre application provient bien de Cortana (args.Kind == ActivationKind.VoiceCommand) et 2/ d’extraire toutes les infos vocales dans un bel objet tout propre (plus classe qu’une Query String quand même) : SpeechRecognitionResult.

Code App.xaml.cs

 protected override void OnActivated(IActivatedEventArgs args)
{
    base.OnActivated(args);

    Frame rootFrame = Window.Current.Content as Frame;

    // Was the app activated by a voice command?
    if (args.Kind == ActivationKind.VoiceCommand)
    {
        var commandArgs = args as VoiceCommandActivatedEventArgs;
        SpeechRecognitionResult speechRecognitionResult = commandArgs.Result;

        // If so, get the name of the voice command, the actual text spoken, and the value of Command/Navigate@Target.
        string voiceCommandName = speechRecognitionResult.RulePath[0];
        string textSpoken = speechRecognitionResult.Text;
        string navigationTarget = speechRecognitionResult.SemanticInterpretation.Properties["NavigationTarget"][0];

        switch (voiceCommandName)
        {
            case "showEventsAt":
                rootFrame.Navigate(typeof(HomePage), commandArgs.Result);
                break;

            case "showEventsFrom":
                rootFrame.Navigate(typeof(HomePage), commandArgs.Result);
                break;

            // Cases for other voice commands.
            default:
                // There is no match for the voice command name.
                break;
        }
    }
}

Code HomePage.xaml.cs

 /// <summary>
/// Invoked when this page is about to be displayed in a Frame.
/// </summary>
/// <param name="e">Event data that describes how this page was reached.
/// This parameter is typically used to configure the page.</param>
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    ApplicationView.GetForCurrentView().SetDesiredBoundsMode(ApplicationViewBoundsMode.UseCoreWindow);
    RegisterVoiceCommands();

    // Get recognition result from parameter passed in frame.Navigate call
    SpeechRecognitionResult speechRecognitionResult = e.Parameter as SpeechRecognitionResult;

    if (speechRecognitionResult != null)
    {
        string commandMode = speechRecognitionResult.SemanticInterpretation.Properties["commandMode"][0];

        if (commandMode == "voice") // Did the user speak or type the command?
        {
            //SpeakText(audioPlayer, String.Format("MSDN app heard you say {0}", vcResult.Text));
            HandleCommand(speechRecognitionResult);
        }
        else if (commandMode == "text")
        {
            //messageTextBox.Text = string.Format("Working on your request \"{0}\"", vcResult.Text);
            HandleCommand(speechRecognitionResult);
        }
    }

    SongkickService service = new SongkickService();
    ContentResponse response = await service.EventSearch(48.856930, 2.341200);

    lstLocations.ItemsSource = response.resultsPage.results.events;
}

private void HandleCommand(SpeechRecognitionResult speechRecognitionResult)
{
    string voiceCommandName = speechRecognitionResult.RulePath[0];
    string textSpoken = speechRecognitionResult.Text;
    string navigationTarget = speechRecognitionResult.SemanticInterpretation.Properties["NavigationTarget"][0];

    string eventsViews = speechRecognitionResult.SemanticInterpretation.Properties["eventsViews"][0];
    
    switch (voiceCommandName)
    {
        case "showEventsAt":
            string eventsAt = speechRecognitionResult.SemanticInterpretation.Properties["eventsLocations"][0];
            break;

        case "showEventsFrom":
            string eventsFrom = speechRecognitionResult.SemanticInterpretation.Properties["eventsArtists"][0];
            break;

        // Cases for other voice commands.
        default:
            // There is no match for the voice command name.
            break;
    }
}

Ressources

 

circlelogo