博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
UWP集成Cortana
阅读量:5119 次
发布时间:2019-06-13

本文共 8927 字,大约阅读时间需要 29 分钟。

Windows 10中引入了Cortana语音助手,同时开放了一些新的API以供开发者接入。

最近在做Cortana接入,实现一些简单的交互操作。期间遇到了一些问题,目前关于UWP的资料也比较少,还好最后问题都解决了。

目前Windows 10中Cortana支持前台集成(跳转到应用)和后台集成(在Cortana中完成任务),下面将针对两种模式分别介绍。

 

1、VCD文件介绍

VCD文件是一个命令配置文件,可以在VCD文件中定义命令等信息。VCD文件有以下几个元素组成:

VoiceCommands:VoiceCommands的xmlns属性值必须为 http://schemas.microsoft.com/voicecommands/1.2,WP的是1.0 WP8.1的是1.1(没记错的话),VoiceCommands有1-15个CommandSet,对应着不同的语言

CommandSet:是针对不同语言定义的一组命令,xml:lang=“zh-cn”标识这组命令对应着中文。

CommandPrefix:是CommandSet的子节点,定义了命令的前缀,通常命令组成是以 CommandPrefix开头+Command来实现命令。

Command:定义了单个命令。

ListenFor:表示需要接听的命令,如 搜索{scenery} 可以识别所有  CommandPrefix+搜索+景区 的命令。{scenery}可以在PhraseTopic中定义类型。可以同时定义多组ListenFor,如

<ListenFor>搜索{

scenery}</ListenFor>
<ListenFor>我要去{
scenery}</ListenFor>
<ListenFor>{
scenery}的门票</ListenFor>

Feedback:表示Cortana识别命令后,在执行应用的代码之前给用户的一个反馈。

VoiceCommandService、Navigate:两个可选,但是必须有一个。Navigate用于启动App来执行操作(前台集成),VoiceCommandService用于在Cortana中完成任务(后台集成)

PhraseTopic:是CommandSet的子节点,有点类似占位符的意思,可以定义PhraseTopic的场景来Subject来提高识别率。比如 我要去{city}

<PhraseTopic Label="city" Scenario="Natural Language">

<Subject>City/State</Subject>
</PhraseTopic>

更多关于VCD文件的介绍可以查看MSDN中的文档:https://msdn.microsoft.com/zh-cn/library/windows/apps/xaml/dn706593.aspx

2、后台集成

后台集成可以不打开应用,直接在Cortana中完成任务。下面将针对实例场景来说明如何后台集成Cortana。

首先新建一个UWP应用CortanaDemo,新增一个Windows Runtime Component工程CortanaService。新建一个CortanaCommandService,继承IBackgroundTask接口。在CortanaDemo中引用CortanaService,打开Package.appxmanifest文件,在Application节点添加如下节点:

1 
2
3
4
5
6

同时在CortanaDemo中新增一个VCD文件CortanaCommand.xml ,定义一个命令:

我要玩
我要玩
搜索 苏州乐园
搜索{destination}
我要去{destination}
{destination}的门票
{destination}
查找{destination}
正在搜索{destination}...
City/State
City/State
City/State
City/State
Date/Time

同时在App.xaml.cs中添加如下代码:

var vcdStorageFile = await Package.Current.InstalledLocation.GetFileAsync(@"CortanaCommand.xml");await Windows.ApplicationModel.VoiceCommands.VoiceCommandDefinitionManager.InstallCommandDefinitionsFromStorageFileAsync(vcdStorageFile);

到此,项目就搭建完成了。下面来实现IBackgroundTask接口。

public async void Run(IBackgroundTaskInstance taskInstance)        {
//异步任务需要获取Deferral。 serviceDeferral = taskInstance.GetDeferral(); taskInstance.Canceled += OnTaskCanceled; var triggerDetails = taskInstance.TriggerDetails as AppServiceTriggerDetails; if (triggerDetails != null && triggerDetails.Name == "CortanaCommandService") { try { voiceServiceConnection = VoiceCommandServiceConnection.FromAppServiceTriggerDetails( triggerDetails); voiceServiceConnection.VoiceCommandCompleted += OnVoiceCommandCompleted; VoiceCommand voiceCommand = await voiceServiceConnection.GetVoiceCommandAsync(); // perform the appropriate command. switch (voiceCommand.CommandName) { case "ScenerySearch": var destination = voiceCommand.Properties["destination"][0]; await SearchSceneryByKey(destination); break; default: LaunchAppInForeground(); break; } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine("Handling Voice Command failed " + ex.ToString()); } } }

 

private async Task SearchSceneryByKey(string destination){#region 查询//通过destination来从网络查询数据.....#endregionvar destinationsContentTiles = new List
();foreach (var scenery in result.Item2){destinationsContentTiles.Add(new VoiceCommandContentTile { Title = scenery.sceneryName, TextLine1 = "¥" + scenery.tcPrice, TextLine2 = scenery.address, ContentTileType = VoiceCommandContentTileType.TitleWithText });}var userMessage = new VoiceCommandUserMessage{DisplayMessage = string.Format("{0}有关的景点", destination),SpokenMessage = string.Format("找到了{0}条信息,请问您想查看哪一条?", result.Item2.Count)};var repeat = new VoiceCommandUserMessage{DisplayMessage = "请再说一遍",SpokenMessage = "不好意思,没听清楚"};var response = VoiceCommandResponse.CreateResponseForPrompt(userMessage, repeat, destinationsContentTiles);var resultd = await voiceServiceConnection.RequestDisambiguationAsync(response);var selectedScenery = resultd.SelectedItem.AppContext as resScenerysModel;SceneryVMLocator.SceneryVM.CurrentScenery = new resScenerysModel{sceneryId = selectedScenery.sceneryId};userMessage = new VoiceCommandUserMessage{DisplayMessage = "正在显示景点详情...",SpokenMessage = "正在显示景点详情..."};response = VoiceCommandResponse.CreateResponse(userMessage);response.AppLaunchArgument = InternalUrlBuilder.Build(ProductType.Scenery, InternalUrlBuilder.Action.details, selectedScenery.sceneryId);await voiceServiceConnection.RequestAppLaunchAsync(response);}

这点代码有以下功能:

1、获取命令类型

2、查询网络数据

3、展示并询问用户需要查看哪一条

4、启动应用显示详情

至此基本完成了Cortana后台集成。

VoiceCommandServiceConnection有以下几个方法:

1、GetVoiceCommandAsync:获取命令

2、ReportFailureAsync:返回失败

3、ReportProgressAsync:向用户报告进度,如果需要长耗时操作,需要向用户返回当前的操作信息。

4、ReportSuccessAsync:返回操作成功而不需要接收返回。

5、RequestAppLaunchAsync:从Cortana中启动应用。

6、RequestConfirmationAsync:向用户发送Yes/No请求

7、RequestDisambiguationAsync:当含有多条数据时,供用户选择,如返回若干条景区信息,询问用户需要查看哪一条。

 

不知道大家有没有发现代码中有两处被加粗加大了。这两处是我在开发时遇到的坑。

1、ContentTileType = VoiceCommandContentTileType.TitleWithText。

在添加数据时,可以展示多种类型的列表,如仅标题、图片等,具体信息可以查看VoiceCommandContentTileType。

在开发的时候,只需要展示一些基本的景区信息,而没有展示景区图片,所以最初只定义了

new VoiceCommandContentTile { Title = scenery.sceneryName, TextLine1 = "¥" + scenery.tcPrice, TextLine2 = scenery.address}

在部署操作的时候,Cortana经常返回失败,偶尔会成功展示几次信息,而去没有任何报错信息。当时对着Cortana喊了一下午,各种调试都不行(旁边的iOS开发已经默默的戴上了耳机)。后来设置了ContentTileType = VoiceCommandContentTileType.TitleWithText才成功,具体原因还不清楚,不知道是不是ContentTileType默认值是带icon的,导致image找不到才提示错误。

2、<uap:Extension Category="windows.personalAssistantLaunch"/>

当用户选择要查看哪个景区的时候,此时下单流程比较麻烦,需要回到客户端中进行操作,此时可以调用RequestAppLaunchAsync来启动客户端,在App.xaml.cs里override OnActivated方法来接收启动参数

protected async override void OnActivated(IActivatedEventArgs args)        {            base.OnActivated(args);            shell = Window.Current.Content as AppShell;            // Do not repeat app initialization when the Window already has content,            // just ensure that the window is active            if (shell == null)            {                // Create a AppShell to act as the navigation context and navigate to the first page                shell = new AppShell();                // Set the default language                shell.Language = Windows.Globalization.ApplicationLanguages.Languages[0];                shell.AppFrame.NavigationFailed += OnNavigationFailed;            }            // Place our app shell in the current Window            Window.Current.Content = shell;            //读写文件一般比较快,几乎不会影响启动性能            CurrentCity = await IsolatedStorageHelper.Instance.GetLastCity();            Window.Current.Activate();            switch (args.Kind)            {                case ActivationKind.VoiceCommand:                    {                        break;                    }                case ActivationKind.Protocol:                    {                        var command = args as ProtocolActivatedEventArgs;                        Windows.Foundation.WwwFormUrlDecoder decoder = new Windows.Foundation.WwwFormUrlDecoder(command.Uri.Query);                        var destination = decoder.GetFirstValueByName("LaunchContext");                        new MessageDialog(destination).ShowAsync();                        InternalJumper.initNoticeUrl(destination);                        break;                    }            }                  }

RequestAppLaunchAsync的启动类型是Protocol,可以把args强制转换为ProtocolActivatedEventArgs来取到Uri参数。

那么问题来了,在开发过程中,每次调用RequestAppLaunchAsync来启动客户端的时候,应用都会闪退,各种google,bing也搜不到资料,直到与微软的demo一行行代码对比后才发现需要添加一个权限才能实现启动

<uap:Extension Category="windows.personalAssistantLaunch"/>

 <uap:Extension Category="windows.personalAssistantLaunch"/>

<uap:Extension Category="windows.personalAssistantLaunch"/>

重要的事情要说三遍、三遍、三遍。

添加后应用完美启动。

 

备注:

VoiceCommandContentTile的Title要有区分性,调用RequestDisambiguationAsync的时候会报错。

 

2、前台启动

待续...

转载于:https://www.cnblogs.com/NailClipper/articles/4765771.html

你可能感兴趣的文章
【Java】synchronized与lock的区别
查看>>
django高级应用(分页功能)
查看>>
【转】Linux之printf命令
查看>>
关于PHP会话:session和cookie
查看>>
Chrome development tools学习笔记(3)
查看>>
软件过程的守护神
查看>>
NAT配置
查看>>
【翻译】Brewer's CAP Theorem CAP定理
查看>>
undefined与null
查看>>
redis总结
查看>>
解决SQL Server 阻止了对组件 'Ad Hoc Distributed Queries' 的 STATEMENT 'OpenRowset/OpenDatasource' 的访问...
查看>>
STM32F10x_RTC秒中断
查看>>
[原创]网站HTML,XHTML,XML,WML,CSS等测试验证工具介绍
查看>>
display:none和visiblity:hidden区别
查看>>
C#double转化成字符串 保留小数位数, 不以科学计数法的形式出现。
查看>>
SpringMVC学习总结(三)——Controller接口详解(1)
查看>>
RGB色彩空间和HSV色彩空间的理解
查看>>
牛的障碍Cow Steeplechase
查看>>
Zookeeper选举算法原理
查看>>
嵌入式成长轨迹52 【Zigbee项目】【CC2430基础实验】【在PC用串口收数并发数】...
查看>>