【Win10 應用開發】語音命令與App Service集成


昨天,老周演示了語音命令集成這一高大上功能,今天咱們來點更高級的語音命令。

在昨天的例子中,響應語音命令是需要啟動應用程序的,那么如果可以不啟動應用程序,就直接在小娜面板上進行交互,是不是會更高大小呢。

面向Win 10的API給應用程序增加了一種叫App Service的技術,應用程序可以通過App Service公開服務來讓其他應用程序調用。App Service是通過后台任務來處理的,故不需要啟動應用程序,調用者只需要知道提供服務的應用程序的程序包名稱,以及要調用的服務名稱即可以進行調用了。關於App Service,老周曾做過相關視頻,有時間的話再補上博文。

正因為App Service是通過后台任務來處理的,再與小娜語音命令一集成,應用程序就可以在后台響應語音操作,而不必在前台啟動。

 

好了,基本理論依據有了,接下來,老規矩,老周向來不喜歡講XYZ理論的,還是直接說說如何用吧。

 

1、定義語音命令文件。老周寫了個新的文件。

<VoiceCommands xmlns="http://schemas.microsoft.com/voicecommands/1.2">
  <CommandSet xml:lang="zh-hans">
    <AppName>樂器收藏</AppName>
    <Example>“樂器收藏 展現列表”,或者“樂器收藏 顯示列表”</Example>
    <Command Name="show">
      <Example>展現列表,或者 顯示列表</Example>
      <ListenFor>[展現]列表</ListenFor>
      <ListenFor>顯示列表</ListenFor>
      <VoiceCommandService Target="vcfav"/>
    </Command>
  </CommandSet>
</VoiceCommands>

其他元素我在上一篇爛文中已經介紹過,不過大家會發現有個家伙比較陌生——VoiceCommandService元素。對,實現語音命令和App Service集成,這個元素的配很關鍵,Target屬性就是你要集成的App Service的名字,本例子應用待會要公開的一個App Service名字叫vcfav,記好了。

這個應用的用途是向大家Show一下老周收藏的幾款樂器,都是高大上的樂器,能奏出醉人心弦的仙樂。注意,這里的命令文件用了VoiceCommandService元素,就不需要用Navigate元素。

 

2、實現后台任務。在解決方案中添加一個Runtime組件項目,記得老周N年前說過,實現后台的類型是放到一個運行時組件項目中的。

    public sealed class BTask : IBackgroundTask
    {
        BackgroundTaskDeferral taskDerral = null;
        VoiceCommandServiceConnection serviceConnection = null;
        public async void Run(IBackgroundTaskInstance taskInstance)
        {


后台任務的功能當然是響應小娜收到的語音命令,因為可以通過App service來觸發,所以我們就能在后台任務中進行交互。

要與小娜面板進行交互,我們需要一個連接類——VoiceCommandServiceConnection類,它的實例可以從后台任務實例的觸發器數據中獲得,就是這樣:

        public async void Run(IBackgroundTaskInstance taskInstance)
        {
            taskDerral = taskInstance.GetDeferral();
            AppServiceTriggerDetails details = taskInstance.TriggerDetails as AppServiceTriggerDetails;

            // 驗證是否調用了正確的app service
            if (details == null || details.Name != "vcfav")
            {
                taskDerral.Complete();
                return;
            }

            serviceConnection = VoiceCommandServiceConnection.FromAppServiceTriggerDetails(details);

 

關鍵是這句:serviceConnection = VoiceCommandServiceConnection.FromAppServiceTriggerDetails(details);
連接對象就是這樣獲取的。

 

3、之后,我們就可以在代碼中與小娜交互了。在交互過程中,發送到小娜面板的消息都由VoiceCommandUserMessage類來封裝,它有兩個屬性:

DisplayMessage:要顯示在小娜面板上的文本。

SpokenMessage:希望小娜說出來的文本。

如果你希望小娜說出的內容和面板上顯示的內容相同,也可以把這兩個屬性設置為相同的文本。

 

與小娜交互的操作自然是由VoiceCommandServiceConnection實例來完成了,不然我們上面獲取它干嗎呢,就是為了在后面的交互操作中使用。

VoiceCommandServiceConnection通過以下幾個方法來跟小娜交互:

ReportSuccessAsync:告訴小娜,處理已經完成,並返回一條消息,傳遞給小娜面板。

ReportFailureAsync:向小娜反饋錯誤信息。

ReportProgressAsync:報告進度,不指定具體進度值,只是在小娜面板上會顯示長達5秒鍾的進度條,你的代碼處理不應該超過這個時間,不然用戶體驗不好。最好控制在2秒鍾之內。

RequestAppLaunchAsync:請求小娜啟動當前應用。

RequestConfirmationAsync:向小娜面板發送一條需要用戶確認的消息。比如讓小娜問用戶:“你確定還沒吃飯?”或者:“你認為老周很帥嗎?”,用戶只需回答Yes or No。后台任務會等待用戶的確認結果,以決定下一步做什么。

RequestDisambiguationAsync:同樣,也是向用戶發出一條詢問消息,與上面的方法不同的是,這個方法會在小娜面板上列出一串東西,讓用戶說出選擇哪一項。比如,“請選擇你要看的電影:”,然后選項有:《弱智仙俠》、《花錢骨》、《燒腦時代》、《菊花傳奇》,你說出要選擇的項,或者點擊對應的項,小娜會把用戶選擇的項返回給應用程序后台任務,以做進一步處理。

 

在老周這個示例中,如果語音命令被識別,就會在小娜面板上列出老周收藏的五件樂器,然后你可以選擇一件進行收藏,當然是不包郵的,你還要付等價費。

            if (serviceConnection != null)
            {
                serviceConnection.VoiceCommandCompleted += ServiceConnection_VoiceCommandCompleted;
                // 獲取被識別的語音命令
                VoiceCommand cmd = await serviceConnection.GetVoiceCommandAsync();
                if (cmd.CommandName == "show")
                {
                    // 獲取測試數據,用於生成磁塊列表
                    var tiles = await BaseData.GetData();

                    // 定義返回給小娜面板的消息
                    VoiceCommandUserMessage msgback = new VoiceCommandUserMessage();
                    msgback.DisplayMessage = msgback.SpokenMessage = "請選擇要收藏的樂器。";

                    // 第二消息,必須項
                    VoiceCommandUserMessage msgRepeat = new VoiceCommandUserMessage();
                    msgRepeat.DisplayMessage = msgRepeat.SpokenMessage = "請選擇你要收藏的樂器。";

                    // 把消息發回到小娜面板,待用戶選擇
                    VoiceCommandResponse response = VoiceCommandResponse.CreateResponseForPrompt(msgback, msgRepeat, tiles);
                    VoiceCommandDisambiguationResult selectedRes = await serviceConnection.RequestDisambiguationAsync(response);

                    // 看看用戶選了什么
                    VoiceCommandContentTile selecteditem = selectedRes.SelectedItem;
                    // 保存已選擇的樂器
                    SaveSettings(selecteditem.Title);
                    // 回傳給小娜面板,報告本次操作完成
                    msgback.DisplayMessage = msgback.SpokenMessage = "好了,你收藏了" + selecteditem.Title + "";
                    response = VoiceCommandResponse.CreateResponse(msgback);
                    await serviceConnection.ReportSuccessAsync(response);
                    taskDerral.Complete();
                }
            }

 

SaveSettings方法是把用戶選擇的收藏保存到應用程序本地設置中,以便在前台應用中訪問。

        private void SaveSettings(object value)
        {
            ApplicationDataContainer data = ApplicationData.Current.LocalSettings;
            data.Values["fav"] = value;
        }

 

在調用RequestDisambiguationAsync方法向小娜面板添加可供選擇的列表項時,一定要注意一點,作為方法參數的VoiceCommandResponse實例一定要使用CreateResponseForPrompt靜態方法來創建,因為上面說過,提供有待用戶確認的交互有兩類:一類是yes or no,另一類就是從列表中選一項。此處就是后者。

這里老周也定義了一個BaseData類,用來產生顯示在小娜面板上的項的圖標,用VoiceCommandContentTile類來封裝,每個VoiceCommandContentTile實例就是一個列表項,顯示的格式由ContentTileType屬性來指定,比如顯示純文本,還是顯示圖標加文本,為了讓大家看清楚老周的收藏品,此處選用圖標 + 文本的方式呈現。

    internal class BaseData
    {
        public static async Task<IEnumerable<VoiceCommandContentTile>> GetData()
        {
            IList<VoiceCommandContentTile> tiles = new List<VoiceCommandContentTile>();
            // 獲取數據
            var filedatas = await GetFiles();

            // 添加磁塊列表
            for (uint n = 0; n < filedatas.Length; n++)
            {
                if (tiles.Count >= VoiceCommandResponse.MaxSupportedVoiceCommandContentTiles)
                {
                    break;
                }
                VoiceCommandContentTile tile = new VoiceCommandContentTile();
                tile.ContentTileType = VoiceCommandContentTileType.TitleWith68x68IconAndText;
                tile.Image = filedatas[n].Item1;
                tile.Title = filedatas[n].Item2;
                tile.TextLine1 = filedatas[n].Item3;
                tiles.Add(tile);
            }

            return tiles.ToArray(); ;
        }


        private async static Task<Tuple<StorageFile, string, string>[]> GetFiles()
        {
            string uh = "ms-appx:///Assets/";
            // 笛子
            Uri u1 = new Uri(uh + "笛子.png");
            //
            Uri u2 = new Uri(uh + "鼓.png");
            // 大提琴
            Uri u3 = new Uri(uh + "大提琴.png");
            // 二胡
            Uri u4 = new Uri(uh + "二胡.png");
            // 古琴
            Uri u5 = new Uri(uh + "古琴.png");

            // 獲取文件對象
            StorageFile imgFile1 = await StorageFile.GetFileFromApplicationUriAsync(u1);
            StorageFile imgFile2 = await StorageFile.GetFileFromApplicationUriAsync(u2);
            StorageFile imgFile3 = await StorageFile.GetFileFromApplicationUriAsync(u3);
            StorageFile imgFile4 = await StorageFile.GetFileFromApplicationUriAsync(u4);
            StorageFile imgFile5 = await StorageFile.GetFileFromApplicationUriAsync(u5);

            // 創建三元組列表
            Tuple<StorageFile, string, string> item1 = new Tuple<StorageFile, string, string>(imgFile1, "笛子", "聲音空遠悠揚,靈動飄逸。");
            Tuple<StorageFile, string, string> item2 = new Tuple<StorageFile, string, string>(imgFile2, "", "樂聲雄渾,勁力深透。");
            Tuple<StorageFile, string, string> item3 = new Tuple<StorageFile, string, string>(imgFile3, "大提琴", "音質宏厚。");
            Tuple<StorageFile, string, string> item4 = new Tuple<StorageFile, string, string>(imgFile4, "二胡", "意綿綿,略帶凄婉。");
            Tuple<StorageFile, string, string> item5 = new Tuple<StorageFile, string, string>(imgFile5, "古琴", "音質沉厚,古朴淡雅,可傳情達意。");

            return new Tuple<StorageFile, string, string>[] { item1, item2, item3, item4, item5 };
        }
    }


4、回到主項目,引用剛才寫完的后台任務。有的朋友說后台任務不起作用,如果后台類沒問題的話,可能的兩個問題是:a、主項目沒有引用后台任務類所在的項目;b、清單文件沒有配置好。

 

5、最后,不要忘了配置清單文件,打開Package.appxmanifest文件,找到Application節點。

      <Extensions>
        <uap:Extension Category="windows.appService" EntryPoint="BgService.BTask">
          <uap:AppService Name="vcfav"/>
        </uap:Extension>
      </Extensions>

擴展點的Category屬性要指定windows.appService,表示擴展類型為App Service,EntryPoint指定入口點,即后台任務類的名字,包括命名空間和類型名。

在App類的OnLaunched方法中,記得安裝VCD文件。

            StorageFile vcdfile = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///vcd.xml"));
            await VoiceCommandDefinitionManager.InstallCommandDefinitionsFromStorageFileAsync(vcdfile);


現在,你可以測試了。運行應用程序,然后對着小娜說“收藏樂器 顯示列表”,然后給出選擇列表。

 

識別后,顯示操作結果。

 

源代碼下載地址:http://files.cnblogs.com/files/tcjiaan/VoicecmdWithrespApp.zip

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM