课程内容
Ø Playing Video
Ø MediaElement
Subservient Cat是一个“虚拟宠物”的应用程序。与大多数猫不一样,Subservient Cat非常听从主人的指令!但是,用户需要知道哪些命令它是能够回应的。它又带点游戏的成分,因为用户必须通过自己的摸索来发现这些命令。
该应用程序使用一段黑色猫咪(名字为Boo)的视频剪辑作为主界面。因此,对于学习怎样通过MediaElement控件在应用程序中播放视频来说,这是一个很好的例子。
Playing Video with MediaElement
如果我们想要用户可以对视频进行播放、暂停和其他的控制操作,最好的选择就是使用Media Player 启动器。但是,如果我们想要在应用程序的页面中播放视频内容,就可以选择使用MediaElement。MediaElement是一个UI控件,它可以通过自身的Source属性来播放视频文件。例如:
<MediaElement Source=”cat.wmv”/>
视频源文件的路径可以指向工程中包含的文件,或者是一个在线的网络视频。默认情况下,MediaElement在加载时自动播放视频(对于网络视频来说,只要缓冲了足够的视频流,它就开始播放),但是,我们可以将AutoPlay属性设置为false,来更改这种设置。在背后代码中,我们可以使用MediaElement的Play、Pause 和 Stop方法。它还具有Position属性,用于指示当前的播放位置(用一个时间段的值来标识)。另外,如果视频支持查找的话,我们可以设置Position为一个播放的时间点。就和其他的Silverlight 元素一样,MediaElement支持转变和剪切操作,并且它还可以与其他元素混合。从界面上来看,使用MediaElement元素很直接。但是,也有很多需要说明的地方。下面例举了5个需要注意的点:
1. 一个应用程序的frame只能包含一个MediaElement!
在一个frame中使用多个MediaElement的做法是不被支持的,而且程序会返回失败。注意,这种限制比一个页面使用一个MediaElement还要严格;任何时候,只能有一个MediaElement加载到frame上(无论MediaElement是处于停止、暂停或者是播放状态)。因此,多个页面只有不同时出现在navigation堆栈的情况下,才能使每个页面包含各自的MediaElement。否则,如果我们需要播放多个视频,那么我们需要复用同一个MediaElement,或者将不使用的MediaElement从element tree中移除。
2. 在将视频包含到应用程序时,确定其Build Action属性值设置为Content,而非Resource!
这样做可以提高视频启动的性能。在视频文件作为资源嵌入时,在其播放前,应用程序会先对其进行加压缩,然后暂时存放到隔离存储空间(对于MediaElement使用的音频文件来说,也同样需要注意这个问题)。
3. 在MediaElement开始播放时,任何后台的音频播放(比如Zune播放的音乐)会暂停!
这正是为什么MediaElement不被用于播放音效的主要原因。另外,即使视频文件中没有包含音频,这一点也是要注意的。
4. MediaElement在模拟器的light主题下存在Bug!
这听上去很奇怪,但确实是事实。在模拟器上测试MediaElement,我们必须确保它在dark主题下运行。但是别担心,这个问题在真机中不存在。
5. MediaElement无法渲染完全不透明的效果!
如果MediaElement中存在其他元素,我们可以通过视频清楚的看到它们,甚至是MediaElement的Opacity属性设置为1(该属性的默认值就是1)。这个是手机的media player(在其内部进行MediaElement的视频渲染)和Silverlight之间合成的异常。详见“Windows Phone支持的媒体文件格式”(链接为http://goo.gl/6NhuD),查看MediaElement支持的视频格式;以及“推荐的视频编码参数设置”(链接为http://goo.gl/ttPkO),查看哪种编码形式最合适我们的应用。如果我们正在使用Expression Encoder,就可以使用其中专门针对Windows Phone (和 Zune HD)平台已经预设值的参数来进行视频编码。
The User Interface
除了简介页面以外(这里不作介绍),Subservient Cat应用程序使用了另一个页面,也就是主页面。主页面的的root grid包含了三个不同的用户控件,如图33.1所示。
1. 包含视频播放的 MediaElement元素
2. 一个简单的“intro screen”,介绍猫咪能够执行的指令,之后应用程序会播放命令相对应的视频片段。
3. 具有text box的panel,让用户猜测新的指令。
图33.1 主页面中三个主要的用户控件
注意:
视频播放时,手机处于横屏模式,所以它只是一个横屏模式的页面。但是,text box元素被用来给用户猜测新的指令,所以背后的代码暂时改变页面的SupportedOrientations属性值,使得焦点位于text box时,两种屏幕模式都可以使用。这样一来,具有硬件键盘的手机就可以让用户获得更好的体验。
应用程序栏具有三个按钮:一个用于展示指令输入面板,一个用于导航到简介页面,一个用于指示用户已经发现的指令数量(在背后代码中更新)。点击最后一个按钮还可以提示我们,是否有更多的指令等待我们去发现,因为对于我们用户来说,指令的总数,是一个谜。应用程序栏菜单是通过代码进行动态添加的,它包含了用户已经发现的指令清单,在用户点击其中任何一个指令时,猫咪就会做相应的动作。详见图33.2。
图33.2 应用程序栏菜单提供快速获取已发现的指令清单,例如“yawn”。
虽然应用程序可以播放不同的视频片段,但从性能的角度来看,事实上它使用了单个较长的视频文件(cat.wmv)。背后的代码会负责选择其中合适的视频片段进行播放。
通过CompositeTransform 来实现MediaElement的移动和扩大,因为“cat.wmv”源文件的头和尾具有一些我们不想看到的黑条。
MediaElement的Volume属性(值的范围为0~1)被设置为1(表示最大音量),因为其默认值是0.85。虽然播放的声音最大只能达到用户设置的值,但是这就确保了视频文件中的细节部分(短暂的“喵”叫声)也能够被听到。
注意:确保给MediaElement元素命名!
如果我们没有给其命名,有可能marketplace发布审核流程不会发现你使用了MediaElement,因此就不会确保我们的应用程序具有“media library”能力,而这个能力对于本应用程序来说是必须的。
The Code-Behind
主页面的构造函数利用possibleCommands方法产生猫咪能够识别的指令集,该指令集伴随的声音用其在“cat.wmv”文件中的起始和终止时间表示。
在 OnNavigatedTo 事件处理中,使用之前已经发现的指令来填充应用程序栏菜单。这些指令存放在discoveredCommands中,而discoveredCommands又是作为一个设置保存起来。
为了在应用程序栏按钮图标中展示已经发现的指令数量,该应用程序工程中包含了一些图片,包括appbar.1.png、appbar.2.png和appbar.3.png等等。至于选用哪一个图片,就需要根据discoveredCommands集合的数量来确定。
在页面加载时,视频就自动开始播放(因为代码中的AutoPlay属性没有设置为false),但是我们不想播放整个视频来展示猫咪的所有动作。相反,我们只应该播放视频的前1.5秒。因此,在MediaElement的MediaOpened事件处理函数中(该事件在媒体文件加载并准备播放时触发),我们利用videoTimer在视频播放1.48秒以后进行暂停。该暂停在videoTimer的Tick事件处理函数“VideoTimer_Tick”中完成。
注意:直到MediaOpened事件触发,我们才能够在MediaElement中播放视频!
在设置MediaElement的源文件后(在XAML或者背后代码中都可以完成),我们不能立即与媒体文件进行交互。相反,我们必须等待MediaOpened事件的触发。如果由于某些原因,媒体文件无法加载,那么MediaFailed事件就会被触发。Subservient Cat应用程序没有使用手动调用Play的方法,那是因为它使用了MediaElement的自动播放特性。但如果不使用其自动播放的特性,就必须在MediaElement_MediaOpened事件处理函数中调用Play方法。
注意:为什么在手机连接到PC机的Zune后,无法播放手机上的视频?
这个原因其实在前一章中已经解释过。Zune是一个桌面应用程序,它会锁定手机的媒体库,这就导致了MediaElement无法加载媒体文件。记住,如果我们需要调试应用程序中视频播放相关的功能,可以使用Windows Phone Developer Tools 中提供的Windows Phone Connect Tool工具来连接手机,而不是通过Zune来连接。
在Subservient Cat应用程序中,我们可以通过MediaFailed事件来检测这种情况。当然,我们假设这种情况的出现就是由于Zune的连接,因为对于应用程序来说,该视频文件就是本地的文件。
PlayClip方法可以使视频暂停,回到beginTime参数指定的起始时间点,重新初始化videoTimer,使得视频可以在endTime参数制定的终止时间停止播放。但是,由于设置MediaElement的Position会带来一些不友好的效果,如视频会快速前进或者快速回退到指定的时间点(而不是即刻的跳转),应用程序的简介页面已经对这种过渡进行了视频隐藏处理(我们不希望展示哪些有待用户发掘的视频片段)。Position的设置在BeginInvoke回调函数中完成,使得简介页面可以进行显示。如果不是这样做的话,我们就会看到不想要的情况出现。视频延时的长度设置为2秒,这段时间可以让用户在简介页面上浏览指令。我们没有办法获知用户真实消耗的时间,但2秒钟已经足够长的了。
注意:在我们设置MediaElement的Position参数后,效果无法即刻显现!
相反,我们可以看到目标时间点之前或之后的一小段视频,就像那种看快进或者是快退的效果。因为Subservient Cat应用程序使用的方法是:暂时采用其他的元素来遮盖视频。
在当前的Windows Phone版本中,MediaElement元素并不支持标记。使用标记来区分cat.wmv视频文件中单独的视频片段,这是一个理想的方案,而且还可以大幅度减少背后的处理代码。但是,使用DispatcherTimer来通知应用程序相关的视频已经播放完毕,这也是一个可替代的方案。下面是需要注意的两个事项:
1. 定时器的精度没有达到“帧”的级别。本应用程序使用的视频,在每个片段的最后使用了一些缓冲,以防videoTimer的Tick事件触发滞后。
2. 如果我们想要弹出一个消息框,视频文件会在后台继续播放,但是定时器的Tick事件处理不能被调用。无论视频播放多长时间,直到消息框解除才能恢复Tick事件处理(MessageBox.Show是一个阻塞的操作)。这正是为何在源代码中,首先使用DiscoveredButton_Click来暂停视频的播放。
当我开始写Subservient Cat应用程序的时候,我在OnNavigatedFrom事件中调用了MediaElement的Stop方法,因为在简介页面显示,而主页面处于堆栈中时,我担心不必要的视频播放会引来性能的下降。但是,事实证明这种担心是多余的,因为在页面离开时,MediaElement会暂停所播放的视频。如果我们不需要这种特性(例如,在其他页面时,我还想听到视频播放的声音),我们必须将MediaElement附加到某个帧,而不是一个特定的页面。
MediaElement和隔离存储空间中的文件
如果想要播放隔离存储空间中的文件(例如,我们的应用程序下载并将它保存在此处),我们可以调用MediaElement的SetSource方法,该方法以一个流为参数,而不是一个URI。有了它,我们就可以传入一个合适的IsolatedStorageFileStream。
本文转博客园博客,原文链接:
http://www.cnblogs.com/dearsj001/archive/2012/08/21/101App4WP7_SubservientCat.html,如需转载请自行联系原作者