四、穿越媒体迷宫
Abstract
媒体体验长期以来一直是微软平台的一个弱点,但这并不是因为这些功能不是系统固有的。Windows 提供了广泛的内置播放和管理功能。该平台的不足之处在于向最终用户公开媒体内容创建和管理工具。如果您了解 DirectX 或任何底层 API,那么您就可以继续这样做了。但是传统上,这种技术的应用范围并没有远离那些有资金雇佣具有这种知识的资源的组织。
媒体体验长期以来一直是微软平台的一个弱点,但这并不是因为这些功能不是系统固有的。Windows 提供了广泛的内置播放和管理功能。该平台的不足之处在于向最终用户公开媒体内容创建和管理工具。如果您了解 DirectX 或任何底层 API,那么您就可以继续这样做了。但是传统上,这种技术的应用范围并没有远离那些有资金雇佣具有这种知识的资源的组织。
微软通过 Windows Store 应用发布了 Windows 8,改变了这一局面。Windows Store 应用的编程界面旨在确保提供现代、快速和流畅的应用。作为一个全方位的内容创建和消费平台,Windows 8 凭借其广泛的媒体支持功能脱颖而出,这些功能通过 Windows Runtime (Win RT)和 Windows Runtime for JavaScript 向开发人员公开。
媒体播放
如果您曾经从事过 Windows 窗体开发,您就会知道媒体播放曾经是多么可怕。在 Windows Presentation Foundation/Silverlight、Flash 和 HTML 5 之前,Windows 平台上的媒体播放涉及使用一种称为 ActiveX 的技术将 Windows Media Player 播放用户界面嵌入到播放目标中。无论是在网页中还是在桌面应用中,Windows media playback 都是一种完全断开的体验,甚至需要最终用户下载并安装附加组件才能访问媒体。
在 Windows 8 JavaScript 应用中,微软选择保持该语言完全符合 HTML 5。为此,媒体播放遵循使用熟悉的video
标签的模式。从上一章对页面和导航的讨论中,你可能已经注意到,当你需要从应用中引用 HTML 时,你使用相对路径来实现。这也符合标准的 HTML 模式和实践。可以想象,回放媒体至少需要指定要回放的媒体的位置。在 Windows 应用中,用于回放的媒体可以来自两个地方(一般来说):它可以是机器本地的,也可以来自某个远程源 web 资源。本地媒体以与 HTML 内容(以及应用中需要引用的任何其他类型的内容)相同的方式放置在项目结构中。在图 4-1 中,我已经将我计划播放的视频内容放在了我的项目结构中。本练习使用了 www.bigbuckbunny.org
中的大巴克兔子视频。你可以直接从 www.bigbuckbunny.org/index.php/download/
下载一份录音。
图 4-1。
Project structure with video added
这当然是一个比前一章中使用的更加复杂的项目结构。尽管它有更多的文件夹,但模式保持不变。即使主 HTML 文档不再是主要内容区域,您也可以使用应用级导航来帮助您在页面之间移动。(当然,从概念上讲,它仍然是主要文档;其他“页面”仅仅是被注入其中。)本节并不详细介绍每个示例,而是为将托管其余应用的应用提供基准。让我们从应用的主框架开始。
设置项目
在第三章中,当你深入研究导航机制时,你设计了主页面default.html
以两种视图显示其内容。在最上面的部分是承载所有页面的框架;在它的下面是可以用来从一个例子到另一个例子导航的按钮。您在这个项目中重用这个公式来创建一个布局和导航结构,允许您在一个项目中构建所有的示例。当然,您可以自由地以对您有意义的方式组织您的示例项目。不管它们是在同一个项目中还是分开的,它们都应该运行相同的程序。(请注意,您需要在为不同的示例创建不同的项目的场景中包含所有适当的引用。)清单 4-1 显示了示例浏览器项目的 HTML 布局。
Listing 4-1. HTML Layout for Example Browser
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>AllLearnings_html</title>
<!-- WinJS references -->
<link href="//Microsoft.WinJS.1.0/css/ui-dark.css" rel="stylesheet" />
<script src="//Microsoft.WinJS.1.0/js/base.js"></script>
<script src="//Microsoft.WinJS.1.0/js/ui.js"></script>
<!-- AllLearnings_html references -->
<link href="/css/default.css" rel="stylesheet" />
<script src="/js/default.js"></script>
</head>
<body>
<div style="height: 150px; background-color: #6E1313;">
<div style="height: 50px;"></div>
<header aria-label="Header content" role="banner" style="margin-top: 0px;
vertical-align: bottom;">
<h1 class="titlearea win-type-ellipsis" style="margin-left: 100px;">
<button id="btn_back" class="win-backbutton" aria-label="Back" type="button"
style="margin-left: 0px; margin-right: 20px; visibility: collapse;"></button>
<span id="txt_title" class="pagetitle">Select a sample</span>
</h1>
</header>
</div>
<div id="frame" style="margin-top: 10px;">
</div>
</body>
</html>
在您看到这段代码的用户界面之前,您需要将清单 4-2 中的代码添加到应用启动部分。这确保了示例页面的内容宿主在应用启动时加载一些内容。在这种情况下,它加载了一个页面,这个页面将所有的例子都作为页面上的按钮列出。
Listing 4-2. Application Startup Handler for the Example Browser
if (args.detail.kind === activation.ActivationKind.launch)
{
args.setPromise(WinJS.UI.processAll().then(function ()
{
WinJS.Navigation.navigate("/samplelist.html");
}));
}
基本上是一个按钮列表,代表你的应用可以显示的每个例子。从本章开始,这些按钮中只有一个是适用的,但是您仍然要将它们都添加到这里。每当你用一个新的例子开始一个新的章节时,应用这里强调的模式取决于你。你知道基于主题要激活的按钮。清单 4-3 显示了示例列表页面的用户界面。
Listing 4-3. UI for the Example Listing Page
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>SampleList</title>
<!-- WinJS references -->
<link href="//Microsoft.WinJS.1.0/css/ui-dark.css" rel="stylesheet" />
<script src="//Microsoft.WinJS.1.0/js/base.js"></script>
<script src="//Microsoft.WinJS.1.0/js/ui.js"></script>
<link href="SampleList.css" rel="stylesheet" />
<script src="SampleList.js"></script>
<script src="sampleData.js" type="text/javascript"></script>
</head>
<body>
<section aria-label="Main content" role="main" style="margin-top: 5px; margin-left: 100px;">
<div class="sampleListViewItemTemplate" data-win-control="WinJS.Binding.Template">
<div>
<div data-win-bind="textContent:name"></div>
</div>
</div>
<div style="width: auto; height: auto;">
<div style="height: 0px;"></div>
<div style="width: auto; height: 39.04px; top: 0px; margin-top: 0px;">
<input id="btn_navsamples" type="button" value="Navigation Samples"
style="width: 353.55px; height: 35.89px;" />
</div>
<div style="width: auto; height: 38.32px; ">
<input id="btn_appbarsamples" type="button" value="AppBar samples">
</div>
<div style="width: auto; height: 41.64px;">
<input id="btn_backgroundtasks" type="button" value="Background Tasks">
</div>
<div style="width: auto; height: 41.64px;">
<input id="btn_jquery" type="button" value="jQuery Samples">
</div>
<div style="width: auto; height: 41.64px;">
<input id="btn_fileio" type="button" value="FileIO Samples">
</div>
<div style="width: auto; height: 41.64px;">
<input id="btn_lockscreen" type="button" value="LockScreen Samples">
</div>
<div style="width: auto; height: 41.64px;">
<input id="btn_video" type="button" value="Video Samples">
</div>
<div style="width: auto; height: 41.64px;">
<input id="btn_search" type="button" value="Search Samples">
</div>
<div style="width: auto; height: 41.64px;">
<input id="btn_share" type="button" value="Share Samples">
</div>
<div style="width: auto; height: 41.64px;">
<input id="btn_settings" type="button" value="Settings Samples">
</div>
<div style="width: auto; height: 41.64px;">
<input id="btn_printing" type="button" value="Printing Samples">
</div>
<div style="width: auto; height: 41.64px;">
<input id="btn_dialogs" type="button" value="Dialog Samples">
</div>
<div style="width: auto; height: 41.64px;">
<input id="btn_notifications" type="button" value="Notification Samples">
</div>
</div>
</section>
</body>
</html>
图 4-2 显示了应用运行时的样子。
图 4-2。
App UI for the example browser
清单 4-4 显示了这个页面的 JavaScript 代码,用于点击按钮启动视频示例。像这样的事件处理程序应该放在正在执行的页面的ready
函数中。在这种情况下,那将是samplelist.js
。
Listing 4-4. Event Handler Code for When the Video Button Is Clicked
btn_video.onclick = function ()
{
WinJS.Navigation.navigate("/samples/videosample/testvideo.html"
,ⅵ
"Testing Video Tasks");
};
注意到这个navigate
用法的有趣之处了吗?在第三章的中,当你使用navigate
时,你只传递了一个参数:你要导航到的 HTML 文件的 URL。现在你要传递一个额外的参数:字符串"Testing Video Tasks"
。第二个参数用于将状态信息从导航页面传递到被导航的页面。这是在两个页面之间传递上下文信息的好方法,而不必创建全局状态。清单 4-5 展示了如何使用WinJS.Navigation
的navigated
事件将这个值传递给页面呈现函数。如果你还记得第三章的话,全局导航需要在根主机页面的 JavaScript 代码中。这是托管所有其他页面的主 HTML 页面的 JavaScript(类似于框架在基于 web 的 HTML 页面中的工作方式)。对于这个项目,这是default.js
文件。在主应用函数中,您可以将其插入到主声明之后和调用app.start()
之前的任何地方。注意,在默认项目配置中,default.js
位于项目的js
文件夹中(见图 4-1 )。
Listing 4-5. JavaScript for the Main HTML Page
WinJS.Navigation.addEventListener("navigated", function (args)
{
//find the frame
var frame = document.getElementById("frame");
//clear the frame
WinJS.Utilities.empty(frame);
if (WinJS.Navigation.canGoBack)
btn_back.style.visibility = "visible";
else
btn_back.style.visibility = "collapse";
if (args.detail.state != null)
txt_title.textContent = args.detail.state;
//render the location onto the frame
args.detail.setPromise(WinJS.UI.Pages.render(args.detail.location, frame
,ⅵ
args.detail.state));
});
您从这段代码中调用了两件事。首先,使用state
的值在根页面上设置一个标题——这个想法是用户总是知道他们在哪里。其次,根据导航栈是否允许后退来设置后退按钮的visibility
状态(最初是不可见的)。为了支持向后导航,将清单 4-6 中的代码添加到应用的activated
事件处理程序中(应用启动代码位于default.js
)。
Listing 4-6. Code to Handle Backward Navigation
var btn_back = document.getElementById("btn_back");
btn_back.onclick = function ()
{
WinJS.Navigation.back();
};
媒体播放
当您点击视频样本按钮时,您应该会看到如图 4-3 所示的屏幕。
图 4-3。
Video sample user interface
你能猜出生成这个页面的 HTML 是什么吗?当然,对于经验丰富的 web 开发人员来说,这样的页面是微不足道的(这样做是为了将重点主要放在 Windows 8 开发上,而不是 HTML 布局实践)。清单 4-7 显示了它是如何组成的。注意,到目前为止,还没有使用 WinJS,所以这个页面理论上可以毫不费力地移植到 web 上。
Listing 4-7. HTML for the Video Sample
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>TestVideo</title>
<!-- WinJS references -->
<link href="//Microsoft.WinJS.1.0/css/ui-dark.css" rel="stylesheet" />
<script src="//Microsoft.WinJS.1.0/js/base.js"></script>
<script src="//Microsoft.WinJS.1.0/js/ui.js"></script>
<link href="TestVideo.css" rel="stylesheet" />
<script src="TestVideo.js"></script>
<style>
div
{
margin-left: 5px;
}
span
{
font-size: 22pt;
font-family: Calibri;
}
span.notification
{
background-color: red;
}
input[type=file]
{
width: 100%;
}
.centered
{
margin-left: auto;
margin-right: auto;
width: 800px;
box-shadow: 0px 0px 5px #000;
}
</style>
<script type="text/javascript">
</script>
</head>
<body>
<div class="TestVideo fragment">
<div class="centered">
<div>
<span class="heading">Video Test Actions</span>
</div>
<div>
<video id="player_video" style="width: 500px; height: 400px"
src="/samples/videosample/big_buck_bunny_720p_surround.avi" autoplay></video>
</div>
<div>
<div style="display: table-cell">
<div>
<button id="btn_playvideo">Play video</button>
</div>
<div>
<button id="btn_capture">Camera Capture</button>
</div>
<div>
<button id="btn_advancedcapture">Advanced Media Capture</button>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
现在不要担心按钮btn_capture
和btn_advancecapture
;当你进入 Windows 8 应用中的媒体捕获机制时,会详细讨论它们。在本例中,最终用户无法控制回放开始的时间。由于autoplay
属性,回放在这里立即开始。您可以将回放控制委托给用户,如清单 4-8 所示(当然也去掉了autoplay
反馈)。
Listing 4-8. Video Playback
<video id="player_video" style="width:500px; height:400px"
src="/samples/videosample/big_buck_bunny_720p_surround.avi"``controls
当然,您也可以使用 JavaScript 通过应用控制回放。清单 4-9 使用 JavaScript 来播放视频(您也可以使用 JavaScript 的pause()
函数来停止或暂停视频)。
Listing 4-9. JavaScript Video Playback (Playing Video)
btn_playvideo.onclick = function ()
{
player_video.play();
};
除了autoplay
和controls
之外,您还可以对video
标签应用其他各种属性。这些包括但当然不限于以下内容:
muted
:告诉视频控制静音poster
:允许您指定一个 URL,该 URL 指向视频不播放时显示的图像loop
:告诉视频控制在视频结束后重启视频
清单 4-10 显示了poster
属性的用法。可以想象,在用户决定播放视频之前,它对描述视频内容非常有用。你可能在 YouTube、AOL 和 MSN 等热门网站上看到过这种技术,诱使你播放视频。
Listing 4-10. Adding a Poster to the Video Tag
<video id="player_video" style="width: 500px; height: 400px"
src="/samples/videosample/big_buck_bunny_720p_surround.avi"
poster="/samples/videosample/Big_buck_bunny_poster_big.jpg"
controls></video>
在这种情况下,您使用项目文件夹中的文件作为图像(该图像文件也可以来自任何 web 资源)。我选择使用大巴克兔子电影的海报,可以在下面的维基共享网址找到: https://commons.wikimedia.org/wiki/File:Big_buck_bunny_poster_big.jpg
。你当然欢迎使用任何对你有意义的图像。
运行此示例时,可以看到图像直接显示在视频控件中。只要视频处于停止状态(像这样的媒体可以播放、暂停或停止),它就会一直保持在那里。图 4-4 显示了播放开始前视频控件的外观。
图 4-4。
Poster associated with the video
tag
使用video
标签,您还可以直接从后端 web 服务器播放媒体。清单 4-11 显示了这一点。
Listing 4-11. Server-side Video Playback
<video id="player_video" style="width:500px; height:400px"
src="
http://sample.com/samples/videosample/big_buck_bunny_720p_surround.avi
"
controls></video>
为了让这个示例工作,您必须将 Internet 客户端功能添加到您的应用清单中,如第一章中所述。互联网客户端授予 Windows 8 应用访问互联网的权限。没有它,你就不能通过任何 API 连接到任何基于云的资源。
Windows 8 中的video
控件是HTMLMediaElement
的子类。它可以用来播放视频和音频。如果你确定除了音频(也许是一个游戏介绍序列或一个音乐流应用)之外没有其他需要,那么你可以使用audio
标签。像video
标签一样,audio
标签可以用来回放视频和音频内容,但需要注意的是audio
标签只能回放音频(不呈现视频)。尝试用清单 4-12 中的代码替换清单 4-11 中的 HTML。
Listing 4-12. Playing Only Audio
<audio id="player_video" src="/samples/videosample/big_buck_bunny_720p_surround.avi"
controls></audio>
现在,当你点击播放视频按钮,只有与大巴克兔子视频相关的声音播放。您可能还会注意到,现在只有视频控件的底部(带有播放/暂停按钮的部分)是可见的。当我说audio
标签不播放任何视频时,我是认真的。
当然,如清单 4-13 所示,audio
标签也可以用于从远程服务器传输音频,方式与video
标签相同。
Listing 4-13. Streaming Only Audio from a Remote Server
<audio id="player_video" src="
http://sample.com/samples/videosample/
ⅳ
big_buck_bunny_720p_surround.avi" controls></audio>
到目前为止的例子使用了一个视频,大巴克兔子,它使用 AVI 文件格式。Windows 8 支持音频和视频播放的多种编码类型和文件格式。来自 MSDN 的表 4-1 显示了所有支持的媒体格式。
表 4-1。
Supported Media Playback Formats
| 媒体文件容器或文件格式 | 文件扩展名 | 媒体流格式(编解码器) | | --- | --- | --- | | 录像 | 声音的 | | --- | --- | | MPEG-4 | `.3g2` | H.263 H.264(基线、主、高)MPEG-4 第二部分 SP 和 ASP | AAC(拉加、HE) | | `.3gp2` | | `.3gp` | | `.3gpp` | | `.m4a` | 不适用的 | AAC (LC、HE) MP3 AC3 (DD、DD+) | | `.m4v` | H.263 H.264(基线、主、高)MPEG-4 第二部分 SP 和 ASP | | `.mp4v` | | `.mp4` | | `.mov` | | MPEG-2 | `.m2ts`(如 AVCHD) | H.264 | MPEG-2 (L1,L2,立体声仅限)MPEG-1 (L1,L2) AAC (LC,HE) AC3 (DD,DD+) | | 格式 | `.asf` | VC-1 WMV9 战斗机 | WMA 标准 WMA 语音 WMA 无损 WMA 专业 AC3 (DD,DD+) | | `.wm` | | `.wmv` | | `.wma` | 不适用的 | | 阿德特 | `.aac` | 不适用的 | AAC(拉加、HE) | | `.adt` | | `.adts` | | MP3 文件 | `.mp3` | 不适用的 | MP3 文件 | | 声音资源文件 | `.wav` | 不适用的 | PCM MP3 MS ADPCM IMA ADPCM MS CCITT g . 711 MS GSM 6.10 AC3(日、月+) | | 影片格式 | `.avi` | MPEG-4 第二部分 SP 和 ASP 运动-JPG H.263 未压缩 | PCM MP3 MS ADPCM IMA ADPCM MS CCITT g . 711 MS GSM 6.10 AC3(日、月+) | | AC-3 | `.ac3` `.ec3` | 不适用的 | AC3 (DD、DD+) |您可以在 http://msdn.microsoft.com/en-us/library/windows/apps/hh986969.aspx
直接访问该表。
视频/音频效果
您可以为视频播放器中播放的视频或音频添加效果。要添加任何效果,可以使用video
类的msInsertVideoEffect
方法。清单 4-14 在btn_playvideo
事件处理程序中增加了一行。
Listing 4-14. Adding a Video Stabilization Effect to a Video Being Played Back
btn_playvideo.onclick = function ()
{
player_video.msInsertVideoEffect
("Windows.Media.VideoEffects.videoStabilization", false);
player_video.play();
};
清单 4-14 应用了框架内置的视频稳定效果。这种效果可以在指定的命名空间中找到。注意,videoStabilization
不是一个类,而是一个字符串,它表示映射到这个效果的唯一标识符(ClassID
)。在这样的动画视频中,稳定自然是无效的。但是,如果您有摄像机或照相机拍摄的视频,并且可以在开发 PC 上访问,欢迎您在 PC 上尝试。
背景音频
前一节开始深入研究audio
元素的复杂性。您看到了如何使用audio
标签来播放位于用户机器上或来自远程服务器的音频数据。然而,当这些音频文件播放时,它们被默认设计为仅在前台播放。在 Windows 8 的世界里,这意味着一旦你离开应用,声音就会停止。现在,您可以通过向您一直使用的示例代码中添加一个新的音频控件来尝试一下。(您先前将video
标签修改为audio
标签,以查看两者之间的区别;您不需要为音频创建单独的专用标签。)清单 4-15 显示了到目前为止你一直在使用的修改过的用户界面的摘录,现在有了一个专用的音频控件。该控件以声明方式配置为在加载页面时自动播放。视频控件的尺寸也减小了。
Listing 4-15. UI with a Dedicated Audio Control
<div>
style="width: 300px; height: 200px"``style="width: 300px; height: 200px"
src="/samples/videosample/big_buck_bunny_720p_surround.avi"
poster="/samples/videosample/Big_buck_bunny_poster_big.jpg" controls></video>
</div>
<div>
<audio id="player_audio" src="/samples/videosample/big_buck_bunny_720p_surround.avi"
controls autoplay></audio>
</div>
这段代码应该替换前面清单中的部分,它只包含了元素video
的div
。或者,您可以添加第二个音频div
并修改视频div
以适应列表。在这两种情况下,您都应该有两个媒体控制,一个在另一个上面,所有内建回放控制都在这两个媒体控制上启用。图 4-5 显示了该用户界面的外观。请注意,当您运行它时,音频会立即开始播放(基于您分配给它的autoplay
属性)。另请注意,当您切换到另一个应用或 Windows 8 开始屏幕时,音频会迅速淡出。
图 4-5。
Test video UI with both video and audio controls
导航回应用,音频再次开始。如果您在导航离开时密切关注媒体的播放时间,请注意,它的值从您导航离开的点开始增加(并且还注意到媒体是从稍后的位置播放的)。这是因为音频一直在播放,而你的示例应用被转移到背景。但是,因为以这种方式配置的音频在托管它的应用对用户不再可见时听不到,所以当您切换到不同的应用时什么也听不到。
启用后台回放需要三个步骤。首先,您需要声明性地告诉 Windows 音频播放器是为背景音频播放而设计的。然后,您需要通知 Windows 您打算让应用在后台运行(为了播放音频)。最后,您需要将应用挂接到背景音频播放基础结构,以便用户可以使用 Windows media 播放控件来控制背景音频。我们开始吧。
在这个audio
标签中,您设置了几个属性中的一个,如果在video
元素上设置,这些属性将是无用的:msAudioCategory
属性。系统使用该属性来帮助识别和管理音频的性能和集成。当它的值被设置为BackgroundCapableMedia
时,它将通过audio
元素播放的音频标记为可供背景音频播放器使用。清单 4-16 显示了修改后的audio
标签。
Listing 4-16. audio
Tag Configured for Background Audio Playback
<audio id="player_audio" src="/samples/videosample/big_buck_bunny_720p_surround.avi"
msAudioCategory="BackgroundCapableMedia"
controls autoplay></audio>
现在您已经完成了必要的用户界面修改,下一步是转到项目的package.appxmanifest
配置文件的便捷的声明选项卡。您正在向应用添加一个新的后台任务声明,它支持音频任务。务必在起始页文本框中指定项目起始页,如图 4-6 所示。
图 4-6。
Background audio declaration
你快完成了。现在,您必须修改后面的代码,以启用后台音频子系统的挂钩。这是必需的,以便用户可以在使用另一个应用时识别和控制音频。清单 4-17 添加了管理它的代码。注意一些audio
元素事件的使用,比如onplay
和onstop
。这些都是强大的事件,用于判断视频控件何时播放、暂停、出错以及处于许多其他状态,所以如果您计划构建富媒体应用,请了解它们。
Listing 4-17. Enabling Background Audio
(function ()
{
"use strict";
var media_control = null;
WinJS.UI.Pages.define("/samples/VideoSample/TestVideo.html", {
ready: function (element, options)
{
media_control = Windows.Media.MediaControl;
media_control.onplaypressed = function ()
{
player_audio.play();
}
media_control.onpausepressed = function ()
{
player_audio.pause();
}
media_control.onstoppressed = function ()
{
player_audio.pause();
}
media_control.onplaypausetogglepressed = function ()
{
if (media_control.isPlaying)
{
player_audio.pause();
} else {
player_audio.play();
}
}
player_audio.onplaying = function ()
{
media_control.isPlaying = true;
}
player_audio.onpause = function ()
{
media_control.isPlaying = false;
}
player_audio.onended = function ()
{
media_control.isPlaying = false;
}
Windows.Media.MediaControl.isPlaying = false;
Windows.Media.MediaControl.artistName = "The Peach Open Movie Project";
Windows.Media.MediaControl.trackName = "Big Buck Bunny";
btn_playvideo.onclick = function ()
{
player_audio.play();
};
}
,
});
})();
为了让后台回放工作,您的代码必须至少为onplaypressed
、onpausepressed
、onstoppressed
和onplaypausetoggle
提供一个事件处理程序。如果这些事件中的任何一个没有被处理,后台回放将不会工作。图 4-7 显示了大巴克兔子音频在后台运行时的背景音频控制器。
图 4-7。
Audio being controlled through the background audio controller
向其他设备传输流媒体
您可以将您在应用中回放的媒体连接到任何支持您想要流化的媒体类型的数字生活网络联盟(DLNA)兼容设备。DLNA 是一个非营利性的贸易组织,它定义和管理支持设备间数字内容共享的互操作性标准。例如,支持 DLNA 的电视可以通过家庭网络无线接收来自移动设备的输入。Play To 是一种允许用户使用这些 DLNA 标准将媒体内容从他们的设备流式传输到他们选择的屏幕上的技术。
当用户在显示或播放媒体的应用中选择设备魅力时,一个潜在的播放设备列表就会呈现在他们面前。选择一个播放目标会向已配置为将内容流式传输到播放基础架构的应用发送请求,通知应用呈现它想要流式传输的流。当应用附加流时,编程接口就完成了。清单 4-18 修改了前一个例子的用户界面,引入了一个播放音频和视频的专用按钮。
Listing 4-18. New UI for the Example App
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>TestVideo</title>
<!-- WinJS references -->
<link href="//Microsoft.WinJS.1.0/css/ui-dark.css" rel="stylesheet" />
<script src="//Microsoft.WinJS.1.0/js/base.js"></script>
<script src="//Microsoft.WinJS.1.0/js/ui.js"></script>
<link href="TestVideo.css" rel="stylesheet" />
<script src="TestVideo.js"></script>
<style>
div {
margin-left: 5px;
}
span {
font-size: 22pt;
font-family: Calibri;
}
span.notification {
background-color: red;
}
input[type=file] {
width: 100%;
}
.centered {
margin-left: auto;
margin-right: auto;
width: 800px;
box-shadow: 0px 0px 5px #000;
}
</style>
<script type="text/javascript">
</script>
</head>
<body>
<div class="TestVideo fragment">
<div class="centered">
<div>
<span class="heading">Video Test Actions</span>
</div>
<div>
<video id="player_video" style="width: 300px; height: 200px"
src="/samples/videosample/big_buck_bunny_720p_surround.avi"
poster="/samples/videosample/Big_buck_bunny_poster_big.jpg" controls>
</video>
</div>
<div>
<audio id="player_audio"
src="/samples/videosample/big_buck_bunny_720p_surround.avi"
msaudiocategory="BackgroundCapableMedia" controls>
</audio>
</div>
<div>
<div style="display: table-cell">
<div>
<button id="btn_playvideo">Play video</button>
<span style="width: 10px" />
<button id="btn_playaudio">Play audio</button>
</div>
<div>
<button id="btn_capture">Camera Capture</button>
</div>
<div>
<button id="btn_advancedcapture">Advanced Media Capture</button>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
清单 4-19 显示了 JavaScript 代码,包括合并 Play To 功能的代码。
Listing 4-19. Implementing Play To in the Example App
(function ()
{
"use strict";
var media_control = null;
var manager = null;
WinJS.UI.Pages.define("/samples/VideoSample/TestVideo.html", {
ready: function (element, options)
{
media_control = Windows.Media.MediaControl;
media_control.onplaypressed = function ()
{
player_audio.play();
}
media_control.onpausepressed = function ()
{
player_audio.pause();
}
media_control.onstoppressed = function ()
{
player_audio.pause();
}
media_control.onplaypausetogglepressed = function ()
{
if (media_control.isPlaying)
{
player_audio.pause();
} else
{
player_audio.play();
}
}
player_audio.onplaying = function ()
{
media_control.isPlaying = true;
}
player_audio.onpause = function ()
{
media_control.isPlaying = false;
}
player_audio.onended = function ()
{
media_control.isPlaying = false;
}
Windows.Media.MediaControl.isPlaying = false;
Windows.Media.MediaControl.artistName = "The Peach Open Movie Project";
Windows.Media.MediaControl.trackName = "Big Buck Bunny";
btn_playvideo.onclick = function ()
{
player_video.play();
};
btn_playaudio.onclick = function ()
{
player_audio.play();
}
manager = Windows.Media.PlayTo.PlayToManager.getForCurrentView();
manager.onsourcerequested = function (e)
{
e.sourceRequest.setSource(player_video.msPlayToSource);
}
}
,
});
})();
使游戏成为可能的关键因素是PlayToManager
类。为了讨论这个问题,我必须暂时深入一下魅力条和魅力条编程的主题(在第五章中有更详细的介绍)。一般来说,当用户在你的应用处于活动状态时通过点击一个 charms bar 按钮来选择一个动作时(还有其他高级场景,其中你的应用是不活动的,如第五章中所讨论的),Windows 会询问你的应用,看它是否支持用户请求的功能。正如您在介绍性章节中看到的,这可以是搜索、共享、设置或设备。通过多功能设备请求播放功能。当用户在你的应用运行时点击设备图标时,会向你的应用发出一个查询,看看它是否有一个播放视频,它愿意使用 Play To 通过网络发送。Windows 通过你的应用处理前面提到的PlayToManager
类的onsourcerequested
事件来确定你至少对 Play To 感兴趣。要发布你的内容,只需调用清单 4-19 所示的setSource
函数,通过调用它的msPlayToSource
属性,传递一个你想要的源媒体的句柄。
如果你决定在应用中使用(或不使用)Play To,这里有一些临别指南。微软希望,如果你的应用功能中有回放媒体,你就可以将 Play 暴露给 contract(这是一个将 Play 与你听到的其他 charms 结合使用的花哨词)。目前,这是一个请求,而不是命令。此外,由于 Play To 是 Windows 的一项功能,用户可以通过 charms 栏随时请求播放,因此建议在应用的整个生命周期内将媒体播放器保留在范围内。在您创建的示例中,用户在到达视频示例页面之前不会意识到播放功能;当他们离开该页面时,他们就失去了播放《大兔兔》电影的能力,因为该页面已从内存中删除。然而,如果您在用于播放目的的default.html
页面上有一个可用的根video
标记,那么用户可以自由地浏览示例应用,而不会出现任何问题。
媒体捕获
Windows 8 捕获框架为应用开发人员提供了在应用中捕获照片、音频记录和视频记录形式的媒体的能力。对于任何拥有摄像头的 Windows 8 设备,您都可以使用摄像头捕捉编程接口来录制视频或拍照。最简单的方法是使用内置的CameraCaptureUI
对话框。
在你进入这个对话框做什么之前,我应该提一下,与 Windows 8 的大多数事情一样,你的应用需要用户许可才能访问运行它的设备的板载摄像头。这意味着您需要向您的package.appxmanifest
文件添加一些功能。要使用摄像头、麦克风和录像机,必须启用网络摄像头功能。我还建议申请权限,将图片存储在图片库中,并附带网络摄像头功能。这就是所谓的图片库功能。(您需要这些功能以及音乐库功能来运行本节中的示例,所以一定要启用它们。)
CameraCaptureUI
打开代表相机取景器的全屏模式对话框。当这个对话框被激活时,用户的屏幕看起来如图 4-8 所示。
图 4-8。
Camera capture dialog
您可以指定当通过启动相机捕捉对话框的功能启动相机捕捉对话框时,用户可以使用哪些选项:
- 仅对于图片,选择
Windows.Media.Capture.CameraCaptureUIMode.photo
。 - 对于视频,选择
Windows.Media.Capture.CameraCaptureUIMode.video
。 - 如果你想让用户选择使用对话框来捕捉图片或视频,还有一个选项:
Windows.Media.Capture.CameraCaptureUIMode.photoOrVideo
。
CameraCaptureUI
还公开了一个名为PhotoSettings
的属性,它属于CameraCaptureUIVideoCaptureSettings
类型(相同的名称空间)。此属性可用于在对话框上执行附加配置。例如,您可以使用此设置来切换是否希望用户启用裁剪。我们来看一个用CameraCaptureUI
类拍照的例子。
首先,回想一下前面的例子中有额外的btn_capture
和btn_advancecapture
按钮,您忽略了它们。在清单 4-20 中,你最后为btn_capture
添加了一个事件处理程序来展示捕捉媒体是多么容易。
Listing 4-20. Using the Camera Capture Interface to Capture an Image and Place It in the Pictures Library
btn_capture.onclick = function ()
{
var capture = Windows.Media.Capture.CameraCaptureUI();
capture.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photoOrVideo)
.then(function (file)
{
if (file != null)
file.copyAsync(Windows.Storage.KnownFolders.picturesLibrary);
});
};
对于简单明了的场景来说,这是很棒的,但是它有两个缺点。首先,全屏模式对话框覆盖了整个应用用户界面,基于您正在构建的应用的类型,您可能不希望这样。其次,它只允许视频和图像捕捉。如果你想更深入地控制你的应用捕获媒体的能力——例如,如果你需要捕获音频——你可以使用MediaCapture
类来实现。您可以使用清单 4-21 中的代码在 JavaScript 中创建和初始化一个MediaCapture
对象。
Listing 4-21. Capturing Media Using the MediaCapture
Object
// Create and initialize the MediaCapture object
。
function initMediaCapture() {
var capture = null;
capture = new Windows.Media.Capture.MediaCapture();
capture.initializeAsync().then (function (result) {
}, errorHandler);
}
如果您需要稍微调整一下媒体捕获机制,WinRT 通过MediaCaptureInitializationSettings
对象提供了这样的机制。配置此设置可让您的应用指定您希望捕获如何发生的详细信息。您可以使用它进行音频采集而不是视频采集,并设定采集的格式。因为某些用例在媒体捕获时是常见的,API 还提供了编码配置文件,可用于快速设置记录格式和结构。清单 4-22 为btn_advancedcapture
按钮添加了一个事件处理程序,显示了如何使用概要文件来设置音频记录。
Listing 4-22. Capturing an Audio Recording
btn_advancedcapture.onclick = function ()
{
var capture = new Windows.Media.Capture.MediaCapture();
var profile = Windows.Media.MediaProperties.MediaEncodingProfile.createMp3
(Windows.Media.MediaProperties.AudioEncodingQuality.High);
Windows.Storage.KnownFolders.musicLibrary.createFileAsync("recordings.mp3"
,ⅵ
Windows.Storage.CreationCollisionOption.generateUniqueName).then(function (file)
{
capture.initializeAsync().then(function ()
{
capture.startRecordToStorageFileAsync(profile, file);
});
});
};
清单 4-23 使用同样的模式创建一个使用 WMV 格式捕获视频的概要文件。
Listing 4-23. Capturing Video
btn_advancedcapture.onclick = function ()
{
var capture = new Windows.Media.Capture.MediaCapture();
var profile = Windows.Media.MediaProperties.MediaEncodingProfile.createWmv
(Windows.Media.MediaProperties.VideoEncodingPropertiesHigh);
Windows.Storage.KnownFolders.musicLibrary.createFileAsync("video.wmv"
,ⅵ
Windows.Storage.CreationCollisionOption.generateUniqueName).then(function (file)
{
capture.initializeAsync().then(function ()
{
capture.startRecordToStorageFileAsync(profile, file);
});
});
};
如果您将清单 4-23 中的代码嵌入到一个应用中,您可能会注意到缺少了一些东西。当应用捕捉视频时,你没有取景器可以看到摄像机当前指向的方向。当您使用MediaCapture
类进行视频捕获时,没有用于显示取景器内容的内置用户界面(本质上是预览正在录制的内容)。JavaScript 应用的 WinRT 不同于传统的。这是因为它们没有用于呈现媒体捕获预览的内置控件(称为CaptureElement
)。相反,WinJS 重用了video
标签来完成这个任务。清单 4-24 中的例子显示了如何在应用中启用视频捕获预览。
Listing 4-24. Enabling Video Preview
var capture = new Windows.Media.Capture.MediaCapture();
..
。
var myVideo = document.getElementById("player_video");
myVideo.src = URL.createObjectURL(capture);
myVideo.play();
在前面的例子中,一旦捕获被初始化,页面上的一个video
标记的源就被设置为那个MediaCapture
对象的对象 URL,它是通过调用URL.createObjectURL
获得的。将这段代码添加到清单 4-23 中,可以在video
标签的框架中预览捕获的内容(之前大巴克兔子在这里展示)。
摘要
在本章中,您了解了在应用中使用媒体的许多方式。本章的研究结果包括:
- 将本地来源和远程网站的媒体播放集成到您的应用中
- 媒体捕获和在您的应用中实现它的许多方法,以及使用
CameraCaptureUI
可以做的许多事情 - 更强大的
MediaCapture
类,它提供了捕获音频和创建用于捕获视频的定制取景器的功能 - 在保持应用用户界面的重要性的场景中使用
MediaCapture
API(当然,在高级场景中,当您需要捕获管道的较低层次的改进时)
五、充分利用魅力和契约
Abstract
契约和魅力是 Windows 8 中引入的两个新概念,它们不仅彻底改变了应用之间的通信活动,还为开发人员引入了新的使用场景。使用 charms,用户可以在整个设备以及应用中进行搜索,向其他应用或设备发送内容,并以标准化的方式访问设置。使用契约可以进一步增强应用之间的互操作性。这种方法的美妙之处在于,安装在 Windows 8 系统上的每个应用都通过对正式交互(如打开文件或从联系人存储中选择联系人)进行良好定义的扩展来增强系统功能。作为 Windows 8 应用开发人员,您使用契约作为一种机制来处理用户通过 charms 进行的交互;您还可以使用契约来促进应用之间的这种隐式交互。这一章将在后面详细讨论契约。我们开始吧。
契约和魅力是 Windows 8 中引入的两个新概念,它们不仅彻底改变了应用之间的通信活动,还为开发人员引入了新的使用场景。使用 charms,用户可以在整个设备以及应用中进行搜索,将内容发送到其他应用或设备,并以标准化的方式访问设置。使用契约可以进一步增强应用之间的互操作性。这种方法的美妙之处在于,安装在 Windows 8 系统上的每个应用都通过对正式交互(如打开文件或从联系人存储中选择联系人)进行良好定义的扩展来增强系统功能。作为 Windows 8 应用开发人员,您使用契约作为一种机制来处理用户通过 charms 进行的交互;您还可以使用契约来促进应用之间的这种隐式交互。这一章将在后面详细讨论契约。我们开始吧。
吸引力
第一章介绍了 charms 的概念,介绍了终端用户可用的核心活动。如果你记得,在任何时候从右边滑动都会产生 charms 菜单,你可以用它来执行五个基本活动(在 Windows 8 上,尽管默认情况下不在 Windows Server 2012 上):搜索、共享、开始、设备和设置。图 5-1 显示了当魅力菜单可见时的样子。
图 5-1。
Windows charms
搜索魅力
本节将向您介绍 Windows 8 中的搜索魅力功能,详细介绍作为应用开发人员,您如何将系统范围内的搜索整合到您的应用中,甚至提供自动完成提示。如果你过去使用过 Windows 7,那么你应该熟悉 Search charm 的工作方式,因为它取代了 Windows 7 开始菜单中的搜索框。使用搜索功能,您可以在电脑上找到应用、设置和文件。
搜索魅力用法
Search charm 用于跨所有应用、文件和设置执行系统范围的搜索。此外,Windows 8 提供了一个可扩展性框架,应用可以使用该框架显式插入搜索基础架构。当你在开始屏幕上开始输入时,你可以随时看到搜索的魅力。默认情况下,Windows 8 会按名称搜索所有应用,并将应用列表过滤为与您键入的文本匹配的应用。您还应该注意到,在搜索文本框处于焦点时,会出现“搜索魅力”弹出窗口(参见图 5-2 )。
图 5-2。
Search charm at work
搜索体验
如前所述,搜索体验为应用提供了许多可扩展点。首先,让我们看看如何直接从应用启动搜索体验。从 JavaScript 执行此操作的调用非常简单:
Windows.ApplicationModel.Search.SearchPane.getForCurrentView().show();
然而,正如你在新的 Windows 8 模型中多次看到的那样,进行这种呼叫需要用户在某种程度上的参与。就搜索而言,这种参与是通过应用清单实现的,它在 Windows 应用商店中表现为给定应用所需的一组功能。
图 5-3 显示了从最终用户的角度看应用的权限。在这里,用户可以决定是否要下载和安装该应用(考虑到在这种情况下,Skype 需要使用用户的网络摄像头)。
图 5-3。
Application permissions listed for the user in the Windows Store
这是一个愚蠢的例子;当然,鉴于 Skype 的受欢迎程度以及大多数用户已经对它的功能有所了解,一般用户会下载并安装它。事实上,它是目前(在撰写本文时)社交类的顶级应用。
清单 5-1 显示了一个名为 TestSearch 的应用的简单搜索用户界面,带有一个名为TestSearch.html
的默认页面。我们首先定义了一个简单的用户界面,它只有一个按钮,用于启动搜索窗格。虽然页面上还有其他用户界面元素,但是这里的讨论主要集中在按钮上(btn_startsearch
)。
Listing 5-1. Search Example UI (TestSearch.html
)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Test Search</title>
<!-- WinJS references -->
<link href="//Microsoft.WinJS.1.0/css/ui-dark.css" rel="stylesheet" />
<script src="//Microsoft.WinJS.1.0/js/base.js"></script>
<script src="//Microsoft.WinJS.1.0/js/ui.js"></script>
<link href="TestSearch.css" rel="stylesheet" />
<script src="TestSearch.js"></script>
</head>
<body>
<div class="TestSearch fragment">
<section aria-label="Main content" role="main">
<p id="txt_searchtext">Search Text Goes Here</p>
</section>
<section aria-label="Main content" role="main">
<p>Use the search charm to initiate a search.</p>
</section>
<section aria-label="Main content" role="main">
<p><input type="button" id="btn_startsearch" value="Open Search Pane" /></p>
</section>
</div>
</body>
</html>
我们现在为btn_search
的clicked
事件添加一个事件处理程序(见清单 5-2)。
Listing 5-2. Search Example JavaScript Code (TestSearch.js
)
(function () {
"use strict";
WinJS.UI.Pages.define("/samples/SearchSample/TestSearch.html", {
// This function is called whenever a user navigates to this page. It
// populates the page elements with the app's data
。
ready: function (element, options) {
btn_startsearch.onclick = function ()
{
Windows.ApplicationModel.Search.SearchPane.getForCurrentView().show();
};
}
,
});
})();
如果我们编译并运行这个例子,在我们真正点击这个按钮之前,一切都应该运行良好。如果我们在没有调试的情况下运行,应用就会崩溃,单击按钮后我们会返回到 Windows 8 开始屏幕。如果我们使用 Visual Studio 2012 进行调试,窗口将切换到 IDE,并显示以下错误:
Unhandled exception at line 13, column 17 in ms-appx://8a2843c4-6e36-40f7-8966 85789a855fa8
/samples/searchsample/TestSearch.js
0x80070005 - JavaScript runtime error: Access is denied
。
WinRT information: The search extension must be specified in the manifest in order to use the Search Pane APIs
。
从错误消息中可以看出,应用默认情况下不能调用搜索基础设施。为了允许访问,应用必须首先明确声明它计划集成搜索。
无需以任何方式修改代码,我们可以通过向应用的包清单中添加正确的声明来让应用正常运行。首先,我们需要在标有package.appxmanifest
的项目中定位文件。图 5-4 显示了一个 Windows 8 应用项目的典型应用清单的位置。(习惯这个文件;这本书的很多例子都用到了它。)
图 5-4。
Application manifest in a Visual Studio 2012 project structure
搜索声明
如果您还记得,第二章在谈到已知文件夹和配置文件类型关联时讨论了一些包清单特性。图 5-5 显示了添加搜索声明后的“声明”选项卡(要添加声明,点击可用声明的下拉菜单,选择您想要的声明,然后点击“添加”按钮)。
图 5-5。
Declarations tab with Search declaration added
添加权限
既然您已经看到了最终用户是如何使用权限的,那么让我们看看如何向应用添加适当的权限。将可执行文件、入口点和起始页字段留空,重新构建应用并再次运行它。请注意,单击搜索按钮不再导致错误;相反,它会激活 Windows 8 搜索体验(见图 5-6 )。
图 5-6。
Search experience launched from an application
下表突出显示了SearchPane
类的所有成员:表 5-1 中的事件、表 5-2 中的属性以及表 5-3 中的方法。
表 5-3。
SearchPane
Methods
表 5-2。
SearchPane
Properties
表 5-1。
SearchPane
Events
我们之前创建的搜索应用很简单,但它可以作为一个基础来整合更多的功能。示例中输入的文本目前没有推回到应用,所以让我们改变一下。清单 5-3 修改了这个例子,增加了处理搜索窗格中的用户输入。现在,当我们单击按钮打开搜索窗格时,搜索文本框显示文本“输入搜索”。此外,当我们在搜索文本框中键入文本时,文本会出现在应用中。
Listing 5-3. Search Notification
(function () {
"use strict";
WinJS.UI.Pages.define("/samples/SearchSample/TestSearch.html", {
// This function is called whenever a user navigates to this page. It
// populates the page elements with the app's data
。
ready: function (element, options) {
btn_startsearch.onclick = function ()
{
var search =
Windows.ApplicationModel.Search.SearchPane.getForCurrentView();
search.placeholderText = "enter search";
search.onquerychanged = function (e)
{
txt_searchtext.innerText = e.queryText;
};
search.show();
};
}
,
});
})();
清单修改了我们开始的搜索示例,为queryChanged
事件添加了一个事件监听器。事件的结果打印在 HTML 界面的搜索内容区域中。图 5-7 显示了克隆的,也就是复制的应用。
图 5-7。
Cloning search input
搜索框架还允许应用注入自动建议值。清单 5-4 使用这个特性根据用户在全局搜索框中输入的第一个字符创建建议。请注意,在现实场景中,搜索建议理想情况下是从数据库或 web 服务等外部源异步加载的。
Listing 5-4. Search Auto-Suggestion Example
(function () {
"use strict";
WinJS.UI.Pages.define("/samples/SearchSample/TestSearch.html", {
// This function is called whenever a user navigates to this page. It
// populates the page elements with the app's data
。
ready: function (element, options) {
btn_startsearch.onclick = function ()
{
var search =
Windows.ApplicationModel.Search.SearchPane.getForCurrentView();
search.placeholderText = "enter search";
search.onquerychanged = function (args)
{
txt_searchtext.innerText = args.queryText;
};
search.onsuggestionsrequested = function (args)
{
if (search.queryText.charAt(0) == "a")
{
args.request.searchSuggestionCollection
.appendQuerySuggestion("Avengers");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Aliens");
args.request.searchSuggestionCollection
.appendQuerySuggestion("A Man Apart");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Anaconda");
} else if (search.queryText.charAt(0) == "b")
{
args.request.searchSuggestionCollection
.appendQuerySuggestion("Braveheart");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Birds");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Baby's Day Out");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Bridesmaids");
} else
{
args.request.searchSuggestionCollection
.appendQuerySuggestion("Catwoman");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Dances With Wolves");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Empire Strikes Back");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Ferris Bueller's Day Off");
}
};
search.show();
};
}
,
});
})();
图 5-8 显示了最终的用户体验。如您所见,根据在搜索文本框中输入的文本,显示了一个可能选项的列表。在该示例中,键入字母 d,这将产生包含 d 的建议列表。如前所述,在理想情况下,这些建议将从应用外部的源中检索,如数据库或 web 服务。请记住,在使用这种技术时,向用户提供的建议可能会有延迟。
图 5-8。
Auto-suggesting search
到目前为止,所有的例子都集中在应用运行时搜索应用。如果您检查这些示例,您会看到应用直到单击 search 按钮才订阅 Search。理想情况下,允许搜索的应用在全局级别订阅它,并根据搜索标准显示适当的界面。我们可以通过将搜索初始化代码移出btn_startsearch
click 处理程序来轻松解决这个问题,但是另一个问题呢?
在应用中搜索
如果你研究一下 Windows 8 的搜索体验,你会发现除了搜索应用、文件和设置内容之外,用户还可以直接在应用中搜索。清单 5-3 和 5-4 中的例子简单地提到了这一点;然而,前面的例子都是从应用运行开始的,并且在应用保持该状态时执行搜索。也可以在目前没有运行的应用中进行搜索。在这些场景中,您选择一个应用进行搜索;应用启动时,搜索信息已经存在,并且在许多情况下,搜索已经执行。用我们正在开发的示例应用来尝试一下。它会启动,但不会像您预期的那样响应搜索字符串。这是因为应用需要对其启动代码进行一些修改来支持这种情况。
通常,用户点击代表 Windows 8 应用的磁贴就可以启动该应用。然而,在搜索的情况下,应用是通过单击搜索窗格上代表它的图标来启动的。当这种情况发生时,理想的体验是直接进入特定于应用的搜索屏幕;但是正如你在例子中看到的,除非应用知道它是作为搜索的一部分被启动的(相对于正常激活),否则应用会继续显示其标准的激活用户体验。为了帮助解决这一问题,在启动过程中,Windows 8 应用会被传递识别激活源的参数。因此,当应用通过搜索启动时,它有一个搜索激活类型,而不是标准的启动激活。通过在应用的onactivated
事件处理程序中测试这一点,当应用使用 search 启动时,您可以用所有正确的信息引导应用。清单 5-5 说明了这一点。
Listing 5-5. Handling Search Activation (default.js
)
"use strict";
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
app.onactivated = function (args)
{
if (args.detail.kind === activation.ActivationKind.launch)
{
args.setPromise(WinJS.UI.processAll().then(function ()
{
WinJS.Navigation.navigate("samplelist.html");
}));
} else if (args.detail.kind === activation.ActivationKind.search)
{
WinJS.Navigation.navigate
ⅱ
("/samples/searchsample/testsearch.html", args.detail.detail[0].queryText);
}
};
在本例中,我们修改了示例应用的onactivated
事件处理程序,以测试搜索激活。虽然应用通常会启动页面samplelist.html
,但是在激活类型为search
的场景中,我们会启动testsearch.html
。如果我们发现应用已经通过搜索激活,我们将用户直接导航到搜索示例,并将queryText
值(当前在全局搜索框中的文本)传播到该页面。清单 5-6 显示了如何修改搜索页面来支持这一变化。
Listing 5-6. Handling Search Activation
(function () {
"use strict";
WinJS.UI.Pages.define("/samples/SearchSample/TestSearch.html", {
// This function is called whenever a user navigates to this page. It
// populates the page elements with the app's data
。
ready: function (element, options)
{
var state = WinJS.Navigation.state;
var search =
Windows.ApplicationModel.Search.SearchPane.getForCurrentView();
if (state != null)
{
txt_searchtext.innerText = state;
}
search.onquerychanged = function (args)
{
txt_searchtext.innerText = args.queryText;
//perform search against back end and present the results
};
search.onsuggestionsrequested = function (args)
{
if (search.queryText.charAt(0) == "a")
{
args.request.searchSuggestionCollection
.appendQuerySuggestion("Avengers");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Aliens");
args.request.searchSuggestionCollection
.appendQuerySuggestion("A Man Apart");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Anaconda");
} else if (search.queryText.charAt(0) == "b")
{
args.request.searchSuggestionCollection
.appendQuerySuggestion("Braveheart");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Birds");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Baby's Day Out");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Bridesmaids");
} else
{
args.request.searchSuggestionCollection
.appendQuerySuggestion("Catwoman");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Dances With Wolves");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Empire Strikes Back");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Ferris Bueller's Day Off");
}
};
btn_startsearch.onclick = function ()
{
search.placeholderText = "enter search";
search.show();
};
}
,
});
})();
分享魅力
Share charm 是应用之间最突出的共享方式。它专为这种功能而设计,因此是 Windows 8 的一流应用间通信机制。当应用订阅共享体验时,它可以发送和接收各种形式的内容,从纯文本和 HTML 内容到复杂的结构。
当涉及到权限时,共享遵循类似于搜索的模式——在这种情况下,需要Share Target
声明。与搜索一样,当需要声明时,它会以权限的形式出现,用户可以在决定是否下载和安装你的应用时进行探索。如果他们因为兴奋而错过了这个,他们只需要使用设置菜单来访问给定应用运行所需的相同权限列表。图 5-9 显示了非常受欢迎的 ESPN 应用,设置面板的权限屏幕暴露在外。
图 5-9。
Permissions available through Settings menu
共享魅力支持两种类型的契约:本质上,一种用于提供内容,另一种用于接收内容。因为等式的给予方只能由最终用户通过 Share charm 激活,所以不需要明确定义任何东西来启动它。
共享为源
就 Windows 8 应用而言,它总是可以“分享”它认为有意义分享的内容。让我们看一个简单的例子。我们将与 Windows Mail 应用共享示例应用中的一些文本(但实际上是与任何支持文本共享的应用)。清单 5-7 显示了启动这个的代码。
Listing 5-7. Basic Sharing
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>TestSharing</title>
<!-- WinJS references -->
<link href="//Microsoft.WinJS.1.0/css/ui-dark.css" rel="stylesheet" />
<script src="//Microsoft.WinJS.1.0/js/base.js"></script>
<script src="//Microsoft.WinJS.1.0/js/ui.js"></script>
<link href="TestSharing.css" rel="stylesheet" />
<script src="TestSharing.js"></script>
</head>
<body>
<div class="TestSharing fragment">
<section aria-label="Main content" role="main">
<p><input type="text" id="txt_content" /></p>
</section>
<section aria-label="Main content" role="main">
<p><input type="button" id="btn_openshare" value="Open Share" /></p>
</section>
</div>
</body>
</html>
(function () {
"use strict";
WinJS.UI.Pages.define("/samples/SharingSample/TestSharing.html", {
ready: function (element, options)
{
var transfer = Windows.ApplicationModel
。*本文件迟交
DataTransfer.DataTransferManager;
var share = transfer.getForCurrentView();
share.ondatarequested = function ( args)
{
args.request.data.properties.title = "Sample Sharing Text";
args.request.data.properties
.description = "Description of the text being sent";
args.request.data.setText(txt_content.value);
};
btn_openshare.onclick = function ()
{
transfer.showShareUI();
};
}
,
});
})();
当我们运行这个应用并点击 Open Share 按钮时,就会调用ondatarequested
中的代码。它用来自txt_content
文本框的文本填充共享上下文。基于指定的内容(在这种情况下,文本),可以处理接收该内容类型的应用列表出现在共享窗格中(参见图 5-10 )。
图 5-10。
Sharing pane with compatible applications for the shared content type
当单击其中一个应用时,该应用将在共享视图中启动,用户可以选择进行最后的编辑并完成共享过程。图 5-11 显示了点击 Windows Mail 应用时会发生什么。
图 5-11。
Sharing with Windows Mail
在清单 5-7 中你可以看到,共享遵循一个事件订阅模型来连接。这是因为,如上所述,共享仅通过 Windows 8 共享体验进行。每当用户暴露共享窗格(无论是通过 Share charm 还是通过showShareUI()
)时,都会调用事件处理程序ondatarequested
。它让应用有机会将任何信息加载到共享上下文中。
股份关系的给予通过三个主要的类来完成:DataTransferManager
、DataRequest
和DataPackage
。DataTransferManager
用于连接到共享上下文,监听共享按钮的点击。DataRequest
封装 Windows 8 为响应用户开始共享体验而发起的数据请求——通过args.request
属性公开。它通过ondatarequested
事件的事件处理程序传递给应用。DataPackage
代表在共享应用之间传输的数据的概念包;因此它控制数据的实际读写。它是通过args.request.data
属性暴露的。表 5-4 列出了DataPackage
类的一些方法。
表 5-4。
DataPackage
Methods
DataPackage
还通过其properties
属性公开了一个DataPackagePropertySet
类型的属性,您可以使用它将元数据应用到正在共享的内容。它包含title
和description
属性,但也是一个属性包,可用于以名称/值格式存储附加属性。
清单 5-8 使用数据包类提供的标准格式之一在应用之间发送数据:text
。正如您从表 5-4 中看到的,除此之外,您还可以跨应用发送图像、RTF 内容、HTML 内容,甚至原始数据(使用setData
)。清单 5-7 显示了如何在两个应用之间的DataPackage
中添加一个图像。
Listing 5-8. Sharing Images
(function ()
{
"use strict";
WinJS.UI.Pages.define("/samples/SharingSample/TestSharing.html", {
ready: function (element, options)
{
var transfer = Windows.ApplicationModel.DataTransfer
。*本文件迟交
DataTransferManager;
var share = transfer.getForCurrentView();
share.ondatarequested = function (args)
{
args.request.data.properties.title = "Sample Sharing Text";
args.request.data.properties
。*本文件迟交
description = "Description of the text being sent";
//set text
args.request.data.setText(txt_content.value);
//set image
var file = Windows.Storage.ApplicationData.current
.localFolder.getFileAsync("image_file.bmp");
var stream = Windows.Storage.Streams
.RandomAccessStreamReference.createFromFile(file);
args.request.data.setBitmap(stream);
};
btn_openshare.onclick = function ()
{
transfer.showShareUI();
};
}
,
});
})();
作为目标的共享
每个应用都有一组它支持的内容类型(一些是公共的,一些是私有的)。例如,Windows Mail 应用可以接受 HTML 来呈现从“giving”应用生成的消息。为了正确呈现内容,消息必须遵循特定的格式(在本例中由 Microsoft 控制)。这个故事的寓意是,与直接从应用作者那里获得的文档定义的特定应用需求相比,内容结构更不重要。
通过共享契约接收内容要比发送内容复杂得多。首先,与向其他应用发送内容不同,接收内容需要将应用注册为共享内容类型的目标。这是有意义的,因为当用户希望在应用之间传输内容时,您不会希望共享的每个应用都出现在共享目标列表中。相反,Windows 8 会过滤用户点击共享按钮时出现的应用列表,只包括那些可以使用共享格式内容的应用。不难理解为什么想要注册为共享目标的应用需要声明它们可以处理的内容类型。
毫不奇怪,这样做的模式类似于搜索的注册和处理方式。首先,您必须将共享目标功能添加到应用清单中。图 5-12 显示了该能力的节点。
图 5-12。
Share Target configuration
在图片中,我添加了一些应用支持的数据格式。通过为格式类型指定格式名字对象,可以添加数据格式。在这种情况下,我是说应用可以处理任何类型的文本。(对于更具体的格式,我建议使用带有格式名的setData
方法,该格式名可用于标识这种类型文档的布局和结构,而不仅仅是通过 MIME 类型。)让我们将简单的共享示例扩展为支持接收文本格式的内容。
如果您还记得搜索示例,当应用被激活时,它的激活类型由 Windows 注册并作为启动参数传递,这样应用就可以为给定的场景选择显示哪个页面。您还可以使用文件类型关联来启用共享。这样做允许应用使用非标准格式通过StorageItems
属性共享数据。如果使用的文件格式与指定的数据格式相匹配,它将被视为匹配;否则,共享源必须使用args.Request.Data.SetStorageItems
方法将非标准格式(作为存储文件)添加到共享上下文中。图 5-13 显示了包含数据格式和文件类型关联的共享目标配置页面的放大视图。
图 5-13。
Share Target configuration with file-type association
清单 5-5 使用了从 Windows 8 传递给它的 activation kind 参数,当应用从 Search charm 启动时,它直接指向 TestSearch 页面。清单 5-9 修改了清单 5-5,增加了一个新的else-if
块来过滤掉那些ActivationKind
是shareTarget
的应用初创公司。
Listing 5-9. shareTarget
Activation Kind at Work
"use strict";
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
app.onactivated = function (args)
{
if (args.detail.kind === activation.ActivationKind.launch)
{
args.setPromise(WinJS.UI.processAll().then(function ()
{
WinJS.Navigation.navigate("samplelist.html");
}));
} else if (args.detail.kind == activation.ActivationKind.search)
{
WinJS.Navigation.navigate
("/samples/searchsample/testsearch.html", args.detail.detail[0].queryText);
}
else if (args.detail.kind === activation.ActivationKind.shareTarget)
{
var search_target_activated_args = args.detail.detail[0];
if (search_target_activated_args.shareOperation.data
.contains(Windows.ApplicationModel.DataTransfer.StandardDataFormats.text))
{
search_target_activated_args.shareOperation
.data.getTextAsync().then(function (text)
{ WinJS.Navigation.navigate("/samples/sharingsample/testsharing.html", text)
});
}
}
};
如您所见,这个过程与搜索所用的方法非常相似。我们首先从args
对象中检索特定于共享的对象实例。(在 JavaScript 中,这是一个动态对象,所以直到运行时才知道实际公开的类型。这种行为可能很难处理。)接下来,我们确保分享的内容是应用支持的格式;如果是这样,我们使用适当的方法来检索它(在本例中是getTextAsync
)。当然,因为这是一个异步调用,我们知道必须使用then
延续来处理调用的结果。最后,我们导航到testsharing.html
,传递共享的内容。清单 5-10 显示了修改后的testsharing.html
代码。基本上,我们所做的就是检查state
属性是否包含值;如果是,我们将该值放入页面上的文本框中。
Listing 5-10. Presenting Shared Content
"use strict";
WinJS.UI.Pages.define("/samples/SharingSample/TestSharing.html", {
ready: function (element, options)
{
var transfer = Windows.ApplicationModel.DataTransfer.DataTransferManager;
var share = transfer.getForCurrentView();
var state = WinJS.Navigation.state;
if (state != null)
{
txt_content.value = state;
}
share.ondatarequested = function (args)
{
args.request.data.properties.title = "Sample Sharing Text";
args.request.data.properties
.description = "Description of the text being sent";
//set text
args.request.data.setText(txt_content.value);
};
btn_openshare.onclick = function ()
{
transfer.showShareUI();
};
}
,
为了验证这一点,我去 google.com
搜索了这本书。当我复制谷歌搜索文本框中的内容,然后点击共享时,该应用显示为可能的共享目标之一。点击后显示如图 5-14 所示的视图。
图 5-14。
Sharing between IE and the example application
需要注意的一点是,共享目标的宽度不同于普通应用。共享视图的布局必须考虑到这一点,否则用户体验会受到影响。将用户导航到正常尺寸的标准页面(像素或边距定位)可能会使用户无法共享。
开始魅力
Start charm 并不是一个扩展点,而是为了完整性而在这里列出的。它的功能类似于键盘上的硬件 Windows 键(并非所有键盘都有 Windows 键)。单击此按钮可以在新的开始屏幕和当前运行的应用之间切换,或者根据上下文在 Windows 桌面之间切换。
设备魅力
任何内置到使用外部设备的应用中的功能都通过 Devices charm 滑出菜单显示出来。如果应用订阅了适当的 Windows 8 机制(稍后将讨论),单击 Devices charm 会显示功能和内容可以导出到的目标设备。
视频播放和数字生活网络联盟(DLNA)设备就是一个很好的例子。在应用中回放视频时,您可以选择将视频流式传输到任何兼容 DLNA 的屏幕(例如,兼容 Xbox 或 DLNA 的电视)。另一个很好的例子——也是第六章的基础——是打印。当在应用屏幕中启用打印时,用户可以在设备魅力的滑出式属性页中看到潜在的打印设备。后一种情况的有趣之处在于,它在某种程度上是间接起作用的。与 Share charm 不同,“设备”菜单显示一组设备,这些设备专门映射到当前应用视图显示的行为类型。在打印示例的情况下,打印功能的使用(通过PrintManager
类)向 Windows 展示了要在列表中显示的一组适当的设备:打印机。与共享一样,该列表会筛选出有必要列出的设备。图 5-15 显示了我点击设备图标时的 Internet Explorer 应用(不是桌面版)。
图 5-15。
Devices pane when IE is the running app
设置魅力
设置魅力圆了魅力菜单扩展性的故事。毫不奇怪,它提供了一个扩展点,您可以将应用设置和配置用户界面附加到该扩展点。这些挂钩在设置菜单中显示为链接;单击时,它们会触发可以在应用中处理的事件。设置链接最常见的用途是启动应用中的配置页面,否则这些页面将无法导航。清单 5-11 是一个挂钩 Windows 8 设置魅力的基本例子。
Listing 5-11. Connecting to the Settings Charm
(function () {
"use strict";
WinJS.UI.Pages.define("/samples/SettingsSample/TestSettings.html", {
ready: function (element, options) {
var settings = Windows.UI.ApplicationSettings
.SettingsPane.getForCurrentView();
settings.oncommandsrequested = function (args)
{
var privacy_command = new Windows.UI.ApplicationSettings
.SettingsCommand("privacy_statement", "Privacy Policy", function (command)
{
var msg = new Windows.UI.Popups
.MessageDialog("This app does nothing so there is no policy.");
msg.showAsync();
});
var help_command = new Windows.UI.ApplicationSettings
.SettingsCommand("helo", "Help", function (command)
{
var msg = new Windows.UI.Popups
.MessageDialog("This app does nothing so there is need for help.");
msg.showAsync();
});
var about_command = new Windows.UI.ApplicationSettings
.SettingsCommand("about", "About", function (command)
{
var msg = new Windows.UI.Popups
.MessageDialog("This app was developed by the collective we.");
msg.showAsync();
});
var options_command = new Windows.UI.ApplicationSettings
.SettingsCommand("options", "Options", function (command)
{
var msg = new Windows.UI.Popups
.MessageDialog("This app does nothing so there are no options.");
msg.showAsync();
});
args.request.applicationCommands.clear();
args.request.applicationCommands.append(options_command);
args.request.applicationCommands.append(about_command);
args.request.applicationCommands.append(help_command);
args.request.applicationCommands.append(privacy_command);
};
}
,
});
btn_settings.onclick = function ()
{
Windows.UI.ApplicationSettings.SettingsPane.show();
};
})();
添加到applicationCommands
属性中的每个命令实际上都包含一个标签、唯一的名称和一个处理函数,当单击命令的链接时会调用这个函数。图 5-16 显示了这个应用运行时的样子。
图 5-16。
Settings pane with additional settings added
隐性契约
与魅力驱动的契约一样,隐式契约通过允许每次额外安装应用时使用模式的新组合,扩展了 Windows 8 的开箱即用体验。然而,与它们的对手不同,与隐式契约的集成点更加无缝:应用甚至可能颠覆前面描述的以用户为中心的权限模型。例如,一个应用可以通过在其应用清单中指定它处理的文件类型的文件扩展名来声明它处理某种类型的文件。从 Windows 上的任何地方(甚至在另一个应用中)激活该类型的文件会立即启动声明的应用(或者提供一个界面,在该界面中,用户可以选择适当的应用,以便在多个应用声明它们可以处理指定文件类型的情况下使用)。
隐式契约包括扩展内置拣选器的契约:FileSavePicker
、FileOpenPicker
和ContactPicker
。像搜索契约和共享目标契约一样,像这样的挑选者依赖于读取激活类型。表 5-5 列出了应用可用的所有可能的激活类型。
表 5-5。
ActivationKind
Members
清单 5-12 展示了使用激活类型来决定应用应该如何表现自己的策略。
Listing 5-12. More Contracts
(function ()
{
"use strict";
var activation = Windows.ApplicationModel.Activation;
var stack = new Stack();
app.onactivated = function (args)
{
if (args.detail.kind === activation.ActivationKind.launch)
{
args.setPromise(WinJS.UI.processAll().then(function ()
{
WinJS.Navigation.navigate("samplelist.html");
}));
} else if (args.detail.kind == activation.ActivationKind.search)
{
//TODO: target the page you want here
}
else if (args.detail.kind === activation.ActivationKind.shareTarget)
{
//TODO: target the page you want here
}
else if (args.detail.kind === activation.ActivationKind.fileOpenPicker)
{
//TODO: target the page you want here
}
else if (args.detail.kind === activation.ActivationKind.fileSavePicker)
{
//TODO: target the page you want here
}
else if (args.detail.kind === activation.ActivationKind.contactPicker)
{
//TODO: target the page you want here
}
else if (args.detail.kind === activation.ActivationKind.file)
{
//TODO: target the page you want here
}
else if (args.detail.kind === activation.ActivationKind.cameraSettings)
{
//TODO: target the page you want here
}
};
})();
摘要
随着本章的结束,您应该会对 Windows 8 的精神和环境有更深入的了解,以便在 Windows 8 的 JavaScript 开发中为您服务。Charms 使用户能够在整个设备和应用中进行搜索,向其他应用或设备发送内容,并以标准化的方式访问设置。通过使用契约,可以进一步增强应用之间的互操作性。在 Windows 8 平台中访问五个 charms。这些包括开始和本章所涉及的四个:搜索、共享、设备和设置。以下是这些魅力的一些要点:
- Search charm 可用于跨所有应用、文件和设置执行系统范围的搜索。
- Share charm 是应用之间最突出的共享方式。当应用订阅共享体验时,它可以以从简单到复杂的各种形式发送和接收内容。
- 内置于使用外部设备的应用中的功能通过设备魅力显现出来。设备魅力表面的目标设备的功能和内容可以导出。
- Settings charm 提供了一个扩展点,应用设置和配置用户界面可以附加到这个扩展点上。它最常见的用途是在应用中启动原本无法导航的配置页面。
- 隐式契约通过允许使用模式的新组合与每个额外的应用安装相结合,扩展了 Windows 8 的开箱即用体验。