昨天,老周示範了語音指令內建這一高大上功能,今天咱們來點更進階的語音指令。
在昨天的例子中,響應語音指令是需要啟動應用程式的,那麼如果可以不啟動應用程式,就直接在小娜面闆上進行互動,是不是會更高大小呢。
面向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);
現在,你可以測試了。運作應用程式,然後對着小娜說“收藏樂器 顯示清單”,然後給出選擇清單。
識别後,顯示操作結果。
源代碼下載下傳位址:https://files.cnblogs.com/files/tcjiaan/VoicecmdWithrespApp.zip