【Win10 应用开发】语音命令与App Service集成

昨天,老周演示了语音命令集成这一高大上功能,今天我们来点更高级的语音命令。windows

在昨天的例子中,响应语音命令是须要启动应用程序的,那么若是能够不启动应用程序,就直接在小娜面板上进行交互,是否是会更高大小呢。app

面向Win 10的API给应用程序增长了一种叫App Service的技术,应用程序能够经过App Service公开服务来让其余应用程序调用。App Service是经过后台任务来处理的,故不须要启动应用程序,调用者只须要知道提供服务的应用程序的程序包名称,以及要调用的服务名称便可以进行调用了。关于App Service,老周曾作过相关视频,有时间的话再补上博文。async

正由于App Service是经过后台任务来处理的,再与小娜语音命令一集成,应用程序就能够在后台响应语音操做,而没必要在前台启动。测试

 

好了,基本理论依据有了,接下来,老规矩,老周向来不喜欢讲XYZ理论的,仍是直接说说如何用吧。ui

 

一、定义语音命令文件。老周写了个新的文件。spa

<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,记好了。code

这个应用的用途是向你们Show一下老周收藏的几款乐器,都是高大上的乐器,能奏出醉人心弦的仙乐。注意,这里的命令文件用了VoiceCommandService元素,就不须要用Navigate元素。视频

 

二、实现后台任务。在解决方案中添加一个Runtime组件项目,记得老周N年前说过,实现后台的类型是放到一个运行时组件项目中的。xml

    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);
链接对象就是这样获取的。

 

三、以后,咱们就能够在代码中与小娜交互了。在交互过程当中,发送到小娜面板的消息都由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 };
        }
    }


四、回到主项目,引用刚才写完的后台任务。有的朋友说后台任务不起做用,若是后台类没问题的话,可能的两个问题是:a、主项目没有引用后台任务类所在的项目;b、清单文件没有配置好。

 

五、最后,不要忘了配置清单文件,打开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

相关文章
相关标签/搜索