处理JMF基于时间的多媒体
为了处理JMF中像音频或视频的基于时间的多媒体,要使用 Player。程序可以控制回放,或者能够显示控制面板组件,让用户交互的控制回放. 如果您想播放多个媒体流,你需要为每一个媒体使用单独的 Player。为了同时播放,可以使用Player Objects中的一个来控制其他对象的操作。
Processor 是 Player中的一个特殊类型,它可以在媒体数据被处理前,提供更多的控制方法。无论你是使用基础的Player, 或是更高级的Processor处理媒体内容,都是用同样的方法去管理重新播放。
MediaPlayer bean 是一个压缩JMF播放器的Java Bean, 提供一种简单的方式去处理来自应用程序或applet中的媒体。当一个不同的媒体流被选中时,MediaPlay bean 自动构建一个新的Player类,使它可以更简便的播放一系列的媒体剪辑或允许用户选择播放媒体剪辑。
控制Player类
为了播放一个流媒体,你需要为stream构建一个Player类,configure这个类并且预备让它去run,然后start这个Player开始回放。
Creating a Player
直接通过media Manager 来创建Player类。为了个显示这个Player,你得到Player对象的组成,然后将它们添加到你的applet的表达空间或应用程序的窗口。
当需要去创建一个新的Player 时,要求Manager通过调用createPlayer 或者 createProcessor 来得到它。Manager使用媒体URL 或 MediaLocator,指定去创建相应的Player.如果安装了一个合适的通讯类 URLStreamHandler,则一个URL仅仅能成功的被创建。MediaLocator 没有这个约束。
模块化,直到一个Player被实现之前
当一个Player需要它处于Realized状态时,可以调用许多方法。一种方式是当你调用这些方法时使用Manager createRealizedPlayer方法来创建Player,来保证一个Player类是Realized状态。这个方法提供一种便利途径在一个步骤里去 create 和 realize 一个 Player.当这个方法被调用时,它是阻塞的直到 Player 变成 Realized 状态. Manager 提供一个等价的createRealizeProcessor方法创建一个Realized Processor.
Note: 知道直到一个Player或Processor变成Realized之前模块化可能导致糟糕的结果。例如,如果createRealizedPlayer在一个applet中被调用,Applet.start和Applet.stop将不能被打断创建过程。
使用一个ProcessorModel去创建一个Processor
Processor也可以使用ProcessorModel来创建。ProcessorModel 定义了 input 和 output 需求,为了让 Processor 和 Manager 更好的去创建实现这些需求的 Processor。使用ProcessorModel来创建一个Processor,调用Manager.createRealizedProcessor方法.
Example 3-1: Constructing a Processor with a ProcessorModel.
AudioFormat afs[] = new AudioFormat[1];
afs[0] = new AudioFormat("ima4", 44100, 16, 2);
Manager.createRealizedProcessor(new ProcessorModel(afs, null));
在这个例子中,因为ProcessorModel不能指定一个URL地址,Manager 隐式的找到一个capture驱动,它可以捕捉音频,然后创建一个可以将其解码到 IMA4 的 Processor.
注意,当你创建一个带有ProcessorModel 的 Realized Processor时,你将不能通过 Processor 对象的 TrackControls 指定处理选项.
显示 Media Interface 组件
Player类通常有两种用户接口组件的类型,一个可视的组件和一个控制面板的组件。一些Player实现可以显示附加的组件,比如音量控制和下载进度条。
显示可视组件
可视组件是Player处理它的媒体的可视请求的地方,如果它有一个的话。甚至音频Player也可以拥有可视组件,比如波形显示或动态字符。
要显示一个Player对象的可视组件,你可以:
1。通过调用getVisualComponent方法来得到组件.
2。将它添加applet的表达空间或应用程序窗口。
你可以访问Player对象的显示属性,比如它的 x 和 y 坐标,通过它的可视组件。Player的布局组件通过AWT布局管理器来控制。
显示控制面板组件
播放器常常拥有一个控制面板,允许用户控制媒体表象。例如,一个播放器可能有一组按钮来开始,停止和中断媒体流,并且有一个滑块控制来调节音量。
每一个播放器都提供一个默认的控制面板。显示这个默认控制面板:
1。调用getControlPanelComponent来得到组件。
2。把这个返回的组件添加到applet的表象空间或应用程序窗口。
如果你更喜欢定义一个定制的用户接口,你可以实现定制GUI组件,然后在响应用户时调用相应的Player方法。如果你注册定制组件如ControllerListeners,你也可以在Player状态改变时更新他们。
显示Gain-Control组件
Player支持实现GainControl接口的音频调节器。GainControl提供调节音频音量的方法,如setLevel和setMute。要显示GainControl Component,如果Player提供的话,你可以:
1。调用getGainControl从Player中得到GainContorl。如果Player返回空值,它便不能支持GainControl接口。
2。在返回的GainContorl里调用getControlComponent。
3。把这个返回的组件添加到applet的表象空间或应用程序窗口。
注意,getControls不能返回Player对象的GainControl.你只可以过调用getGainControl访问GainControl.
显示用户控制组件
许多播放器有其他的能够让用户去管理的属性。例如,一个视频播放器允许用户调节亮度和对比度,这些都不能通过Player接口来管理。你可以通过调用getControls方法找出定制一个Player所支持的内容。
例如,如果一个Player支持CachingControl接口的话,你可以调用getControls声明它。
Example 3-2: Using getControls to find out what Controls are supported.
Control[] controls = player.getControls();
for (int i = 0; i < controls.length; i++) {
if (controls[i] instanceof CachingControl) {
cachingControl = (CachingControl) controls[i];
}
}
显示下载进度条组件
CachingControl接口是一个通过Players实现的一个特殊的控制类型,它可以报告下载进度。CachingControl提供一个默认的进度条组件,它可以在下载时自动的更新进度。要在applet中使用这个默认的进度条:
1。实现ControllerListener接口,并在controllerUpdate中监听CachingControlEvents事件。
2。第一次接收CachingControlEvent:
a.在事件中调用getCachingControl得到缓存控制。
b.在CachingControl中调用getProgressBar得到默认的进度条组件。
c.将这个进度条组件添加到applet的表象空间或应用程序窗口。
3。每一次你接收一个CachingControlEvent时,检查一下下载是否完成。当getContentProgress返回相同的值如getContentLength时,移除控制条。
无论是否需要被更新,Player都提交一个CachingControlEvent事件。如果你实现你自己的进度条组件,则无论CachingControlEvent是否被提交,你都可以为个事件监听并且更新下载进度。
设置回放率
Player 对象的比率声明了媒体时间是如何用关联基于时间的计时来改变的;它定义了许多单元,一个Player对象的媒体时间为每个基于时间的计时单元提高. Player对象的比率可以被理解成一个临时衡量要素。例如,一个2.0的比率显示当Player开始后,媒体时间快速的通过了两次基于时间的计时。
在理论上,Player对象的比率能够被赋于任何真实的数据,如当播放媒体后退时用负数比率表示。然而,一些媒体格式依赖于框架,在后退时或不规范时,不能或不可用播放比率。
要设置比率,调用setRate,在临时范围要素里作为一个浮点值传递。当setRate被调用,方法返回实际设置的比率,即使这个值并未改变。Player仅仅保证能支持1.0的比率。
设置开始位置
设置Player对象的媒体时间与在媒体流内部设置一个读取位置是等价的。因为一个媒体数据源,比如文件,媒体时间是有限的;最长的媒体时间是通过媒体流的结束来定义的。
要设置媒体时间,调用setMediaTime,然后在Time对象里传递你想要设置的处理时间。
画面配置
一些Players允许查找一个视频的特殊的画面。这使得可以很容易的设置开始位置到特殊帧的起始点,而不用指定相应位置的精确的媒体时间。支持画面配置的Player实现了FramePositioningControl接口。
要设置画面位置,调用FramePositioningControl seek方法。当你查找画面时,Player对象的媒体时间是设置到相应画面的起始点的值,并且提交一个MediaTimeSetEvent事件。
一些Player能够在媒体时间和画面位置间进行转换。你可以使用FramePositioningControl mapFrameToTime和mapTimeToFrame方法访问这个消息,如果它可用的话。(支持FramePositioningControl的Player不是必需导出这个消息的。)注意在媒体时间与画面之间一对一的对应关系 ---- 一个画面有一断持续时间,所以一些不同的媒体时间可以映射到相同的画面。
准备开始
多数媒体播放器不能立即开始。在播放器开始前,要确认硬件和软件环境都适宜。例如,如是一个播放器从不开始播放,它可能是需要在内存中分配缓冲区以存储媒体数据。或者,如果媒体数据是网络上的,播放器可能不得不建立网络连接,在它下载数据之前。即使播放器已经开始播放之前,缓冲区也可能包含对当前媒体位置无效的数据。
Realizing 和 Prefetching 一个播放器
JMF中断准备让一个播放器开始进入两个步骤的进程,Realizing 和 Prefetching。Realizing 和 Prefetching一个播放器之前开始将时间设为最小,它在当start被调用时让播放器先处理媒体,然后帮助用户建立最快响应的交互式体验。当这些操作发生时,实现ControllerListener接口允许你去控制它们。
注意:Processor引进了第三个步骤,调用Configuring准备进程。在这一步骤中,选中Processor选项可以控制Processor如何操作媒体数据。
调用realize让播放器进入Realizing状态,然后开始实行处理。调用prefetch让播放器时进入Prefetching状态,然后启动计取处理。realize和prefetch方法都是异步且立即返回的。当Player完成了请求操作,它提交一个RealizeComleteEvent或PrefetchCompleteEvent事件。Player States描述了Player在这些状态中执行的每一步操作。
Player在Prefetched状态下是准备去开始,并且它的启动等待时间不会被进一步的减少。然而,通过setMediaTime设置媒体时间可能令Player返回到Realized状态,并增加它的启动时间。
时刻留意Prefetched Player占用的系统资源。因为一些资源,如声卡,可能一个时间里仅在一个程序中是可用的,在Prefetched状态下占有一个Player可能会阻止其他的播放器。
确定开始等待时间
要确定启动一个播放器需要多少等待时间,可以调用getStartLatency方法。因为有着固定启动等待时间的播放器,getStartLatency的返回值会处理最长的启动时间。由于一些媒体类型,getStartLatency 可能返回 LATENCY_UNKNOWN 值。
通过getStartLatency来记录的启动等待时间可以不依赖于Player对象的当前状态。例如,在进行prefetch操作之后,通过getStartLatency返回的值是典型的较小值。一旦Player被Prefetched,添加到Player中的Controller将返回一个有用的值。
开始和终止处理
Clock和Player接口定义了开始和终止处理的方法。
开始处理
通过调用start方法开始媒体数据的播放是很典型的方法。start方法告诉Player尽快的开始处理媒体数据。如果必需,start准备让Player去开始完成实现和预取操作。如果在一个Started Player上调用了start,唯一的结果是StartEvent在这个方法调用的告知里被提交。
Clock定义了syncStart方法,它可以被用在同步中。
在一个媒体流中的指定点上开始一个Player:
1.通过调用setMediaTime,指定在这个媒体流中你想开始的那一点。
2.在这个Player上调用start.
终止处理
四个条件下将终止处理:
1。当stop被调用时。
2。到了指定的终止时间时。
3。没有要处理的媒体数据。
4。因为回放使媒体数据接收太慢。
当Player被终止时,它的媒体时间是被冻结的,如果媒体是可控制的话。如果这个Player处理的是流媒体,它就不会冻结媒体时间。这样一来,只有媒体数据的接收是终止的――数据继续被流化,媒体时间继续增加。
当一个终止的 Player重新开始时,如果媒体时间是被冻结的,处理会从停止时间恢复。当Player终止时,如果媒体时间不能被冻结,流媒体重新开始接收并且重放刚刚接收到的数据。
要立即停止Player,可调用stop方法。如果在一个已终止的Player上调用stop,唯一的结果是,在方法调用的告知中,StopByRequestEvent事件被提交。
设定时间停止播放
调用setStopTime声明一个Player应该何时停止。过了设定的中止时间之后,Player停止播放。如果Player对象的比率是正数,当媒体时间超过或等于中止时间时,停止播放。如果Player对象的比率是负数,当媒体时间小于或等于中止时间时,停止播放。如果Player的当前媒体时间已经超过设定的中止时间时,Player立即停止。
例如,假定Player对象的媒体时间是5.0,调用setStopTime设定的中止时间是6.0。如果Player对象的比率是正数,媒体时间将增加,而Player会在媒体时间大于或等于6.0时停止。然而,如果Player对象的比率是负数,将播放后退,而Player 会立即停止,因为媒体时间已经超过了中止时间。
你可以在一个已停止的Player上一直调用setStopTime方法。然而,如果中止时间没有设成当前时间的话,在一个已开始的Player上,你只能设定中止时间。如果这个已开始的Player已经有一个中止时间,setStopTime会抛出异常。
你可以调用getStopTime来得到当前预定的停止时间。如果时钟没有预定中止时间,getStopTime返回Clock.RESET。为了让Player继续播放直到媒体结束,可以调用setStopTime(Clock.RESET) 移除中止时间。
释放Player资源
Deallocate方法告诉Player释放所有独占的资源,并将它的非独占的资源使用降到最低。尽管对播放器的缓冲和内存管理需要是指定的,但多数播放器分配的缓冲区都大于Java对象的标准。当deallocate被调用时,一个最佳实现的Player会尽可能多的释放内存。
Deallocate 方法仅在已停止的Player上被调用。为避免ClockStartedErrors异常,应该在调用deallocate之前调用stop。在Player的Prefectching或Prefetched状态下调用deallocate方法,返回的是Realized状态。当Player正在realizing时调用deallocate方法,Player会提交DealllocateEvent异常并且返回到Unrealized状态。(一旦Player被realized,它永远不可能返回到Unrealized状态。)
当Player没有被使用中时,通常调用deallocate 方法。例如,一个applet应该调用deallocate作为它的stop方法的一部分。在用户通过把系统作为整体释放其他资源时,调用deallocate,程序能够维护Player的引用。(JMF不会阻止一个被Prefetched或Started的 Realized Player,它会维护Player的信息以允许在将来更快的启动。)
当你完成了一个Player(或者任何其他的Controller),并且不需要在别的地方使用它时,应该调用close。close方法声明了这个Controller将不再被使用,然后可以自我停止。调用close释放所有Controller正在使用的资源,结束所有的活动。当一个Controller closed时,它会提交ControllerClosedEvent事件。一个closed Controller不能被重新开始,而且在一个closed Controller上调用方法可能会产生异常。
查询一个Player
一个Player能提供关于它当前参数的信息,包括它的比率,媒体时间,和持续时间。
得到重放比率
为得到Player对象的当前比率,可以调用getRate。调用getRate返回重放比率作为浮点值。
得到媒体时间
为了得到Player对象的当前媒体时间,可以调用getMediaTime。调用getMediaTime返回当前的媒体时间作为一个Time对象。如果Player不是正在处理媒体数据,这会是媒体播放将开始的地方。
注意,在媒体时间与框架之间不是一对一的对应关系。每一个框架都处理一个特定的期间,在这个期间里,媒体时间继续增加。
例如,想像你滑动的播放Player,它显示每一个经过5秒的滑动时间---这个Player实际上每秒有0.2帧的帧比率。
如果你在0.0的时间开始Player,在第一帧显示时,媒体时间从0.0增加到5.0。如果你从2.0的时间开始,第一帧的时间被显示3秒,直到走到5.0。
得到时基时间
你可以通过得到Player对象的TimeBase来得到一个Player对象的当前时基时间,然后调用getTime:
myCurrentTBTime = player1.getTimeBase().getTime();
当Player正在运行,你可以通过调用mapToTimeBase来得到对应到详细的时基媒体时间。
得到媒体流的持续时间
因为程序常常需要知道一个指定的媒体流将要播放多久,所以,所有的Controller都实现了Duration接口。这个接口只定义了一个方法,getDuration。这个持续时间再现媒体对象将播放的时间的长度,如果播放的比率是1.0,这个媒体流的持续时间就只够播放一个Player。
当getDuration被调用时,如果不能确定持续时间,将返回DURATION_UNKNOWN
值。如果Player仍旧不能变成一种状态,即媒体源的持续时间是可用的状态,这一切将发生。在随后的时间,持续时间可能是可用的,调用getDuration将返回它的值。如果媒体源没有定义持续时间,比如在播放一段生活广播的时候,getDuration返回
DURATION_UNBOUNDED
。
响应媒体事件
ControllerListener
是一个同步的接口,它通过Controller对象处理事件。使用ControllerListener接口可以管理Player中隐藏的操作耗时,比如prefetching。
实现ControllerListener接口
要实现ControllerListener接口,你需要:
1. 在一个类中实现ControllerListener接口。
2. 通过在Controller中调用addControllerListener注册那个类作为一个监听,Controller是你想接收事件的位置。
当一个Controller提交一个事件,它每一个已注册的监听者上调用controllerUpdate方法。
一般说来,controllerUpdate是当作一系列的if-else语句来实现的。
Example 3-3: Implementing controllerUpdate
if (event instanceof EventType){
...
} else if (event instanceof OtherEventType){
...
}
这个过滤器暴露了你不感兴趣的一些事件。如果你注册了一个监听器,比如它带有多种Controllers,你还需要去声明哪个Controller提交了事件。通过调用getSource,ControllerEvents用你能够访问的资源的引用开始“stamped”。
当接收来自Controller的事件时,你可能需要去做一些额外的处理,以确保Controller在调用control方法前处于正确的状态。例如,在调用任何受限方法去Stopped Players前,你应该调用getTargetState来检查Player对象的目标状态。如果start已经被调用,这个Player被认为处于Started状态,尽管它可能正在提交转换事件,就像它准备Player去播放媒体一样,
一些ControllerEvents事件的类型包含额外的状态信息。例如,StartEvent和StopEvent类各自都定义了一个方法,以允许你重新找到在事件发生时的媒体时间。
使用ControllerAdapter
ControllerAdapter是声明ControllerListener很方便的类,并且可能很容易的被扩展以响应特殊的事件。使用ControllerAdapter实现ControllerListener接口,你需要:
1.创建子类ControllerAdapter和覆盖你所感兴趣的那些事件的方法。
2.通过为一个特殊的Controller调用addControllerListener方法,来注册你的ControllerAdapter类作为一个监听者。
当Controller提交一个事件,它会在每一个已注册的监听者上调用controllerUpdate。ControllerAdapter自动的分派事件到合适的事件方法,过滤掉你不感兴趣的那些事件。
例如,接下来的代码继承了一个ControllerAdapter类,它用JDK1.1匿名的内部类创建一个单独的Player,这个Player自动的重设媒体的开始时间,并且当Player结束时自动的deallocated.
player.addControllerListener(new ControllerAdapter() { public void endOfMedia(EndOfMediaEvent e) { Controller controller = e.getSource(); |
如果你为一个多媒体注册了一个单独的ControllerAdapter作为监听者,在你的事件方法中执行Player生成的事件,这个事件需要由你来确定。你可以调用getSource来确定ControllerEvent事件发生的地点。
同步多媒体流
要同步多媒体流的回放,你可以用同样的TimeBase联合Players来同步。要做到这一点,使用getTimeBase和setTimeBase方法通过Clock接口来定义。例如,通过设置player1来使用player2的时间基,你可以用player2来同步player1。
player1.setTimeBase(player2.getTimeBase());
当你有相同的TimeBase通过联合Players来同步他们时,你必须还要管理每一个独立的Player的控制。因为用这种方式管理的同步Player可能变得复杂,JMF提供了一种机置允许Player不用任何其他的Controller来采取控制。这个Player自动管理着这些Controllers的状态,允许你只用一个控制点就能管理整个控制组。
使用一个Player同步控制组
使用syncStart直接同步Players需要你谨慎的管理所有已同步的Players的状态。你必须控制每一个单独的Player,监听事件,并且适当的时候在其上调用控制方法。甚至只用少量Players,就可以很快的完成不同的任务。通过Player接口,JMF提供了一个更简便的解决方案:一个Player可以被用来管理任何Controller的操作。
当你正联合的管理着一个Player,你的指令是自动地作为适当的已管理的控制组共同传递的。这个管理中的Player维护着状态管理并且同步其他控制组中的所有。
这个机置通过addController和removeController方法实现。当你在一个Player上调用addController时,你指定的Controller被加载到Controllers列表里,这个列表通过Player来管理。相反的,当你调用removeController时,指定的Controller被从管理的Controllers中移除。
典型的,当你需要同步Players或其他的Controllers,你应该使用addController机置。它更简单,更快速,而且比试着管理单独的同步的Players来说,它更少出发生错误。
当一个Player 采取一个Controller的控制:
。这个Controller采用Player对象的时间基。
。这个Player对象的持续时间变得比Controller对象的持续时间和它自己的更长。如果多个Controllers被置于一个Player对象的控制中,这个Player对象的持续时间被设置成最长。
。这个Player对象的潜伏期变得比Controller对象的潜伏期和它自己的更长。如果多个Controllers被置于一个Player对象的控制中,这个Player对象的潜伏期被设置成最长。
在Player所管理的每一个Controllers都提交完事件之后,一个管理中的Player只能为异步方法提交完成事件。
这个管理中的Player在适当的时候重新提交通过Controllers产生的其他事件。
添加一个Controller
使用addController方法添加一个Controller到Controllers列表中,这个列表由一个特殊的Player管理。补充一下,一个Controller必须处于Realized状态下;否则,将抛出一个NotRealizedError异常。两个Players不能位于相互的控制之下。例如,如果Player1位于Player2的控制下,Player2要想再处于Player1的控制下 ,就要首先将Player1从Player的控制中移除。
一旦一个Controller已经被加入到一个Player中,就不能在受管的Controller上直接调用方法。要控制一个受管的Controller,要结合管理中的Player.
要Player采用Player的控制,调用:
player2.addController(player1
);
控制受管Controllers
要控制一组在一个特殊的Player下管理的Controllers的操作,要直接结合管理中的Player.
例如,准备所有的受管的Controllers启动,在管理中的Player上调用prefetch。同样的,当你想启动他们,在管理中的Player上调用start。这个管理中的Player会确保所有的Controllers都被Prefetched,确定在这些Controllers中的最大启动等待时间,并且调用syncStart去启动他们,指定一个时间,这个时间是计算出的最大的启动时间。
当你在一个管理中的Player上调用Controller方法时,Player在适当的时候调用受管的Controllers传播method。在一个受管的Controller上调用Controller方法以前,Player确保Controller处于合适的状态。接下来的表格描述了当你在管理中的Player上调用控制方法时,受管的Controllers将会发生的事情。
Table 3-1: 在一个管理中的player上调用控制方法.
移除一个Controller
使用removeController方法从被特殊的Player管理的控制列表中移除一个Controller.
要player2释放play1的控制,调用:
player2.removeController(player1
);
直接同步Players
在少数情况下,你可能想自己管理多个Player对象的同步,以便能够独立的控制比率和媒体时间。要做到这一点,你必须:
1.为每一个同步的Player注册一个监听者。
2.确定将要被使用的Player对象的时间基,以驱动其他Player对象,并且为已同步的Player对象设置时间基。不是所有的Player对象都能采用一个新的时间基。例如,如果你想同步的Player对象中的一个已装入了数据源,那么,这个Player对象的时间基就必须被用来驱动其他的Player 对象。
3.设置所有Players的比率。如果一个Player不支持你指定的比率,它返回的是被使用的比率。(Player不支持查询比率)。
4.同步所有Player对象的状态。(例如,停止所有Players).
5.同步Player对象的操作:
。为每一个Player设定媒体时间。
。Prefetch每个Player.
。在已同步的Player对象上定义最大的启动时间。
。通过调用syncStart方法启动一个已计算好最大启动时间的Player对象。
你必须为所有的Player对象监听转换事件,并且维护是哪一个player提交事件的记录。例如,当你预取一个Player 对象,你需要维护提交了PrefetchComplete事件的那些players,以便你可以保证所有对象在调用syncStart前被Prefetched。同理,当你请求同步的Player对象在一个特殊的时间停止时,你需要通过每一个Player提交stop事件,以确定他们实际停止的时间。
在一些情况下,你需要谨慎的通过已同步的Player对象提交响应事件。保证所有Player对象的状态,你可能需要在继续之前,在一些特定的条件下等待所有对象达到一致的状态。
例如,假定你正在使用一个Player驱动一组同步的Plyer对象。一个用户用互动的Player将媒体时间设为10,启动这个Player,然后将媒体时间改为20。然后:
1.用第一个setMediaTime调用所有同步的Player对象。
2.在每个Player上调用prefetch准备他们启动。
3.当接收到第二个设置媒体时间的请求时,在每个Player上调用stop。
4.用新的时间在每个 Player上调用setMediaTime。
5.重启预取操作。
6.当所有的Player对象都已被预取,通过调用syncStart启动Player对象,计算启动等待时间。
在这种情况下,只在调用syncStart之前从所有的Player对象中监听PrefetchComplete事件是不够的。你无法断定那些被提交的事件是在第一次预取时响应的,还是在第二次预取时响应的。要避免这个问题,你可以当调用stop时阻塞,然后在继续之前等待所有的Player对象提交stop事件。这可以保证接下来的PrefetchComplete 事件中有你真正感兴趣的。
例子:在一个Applet中播放一个MPEG Movie
这个简单的例子演示了如何去创建一个Player,并且从一个带有Java applet的程序中处理一个MPEG movie。这是一个普通的例子,可以很容易的用于处理其他类型的媒体流。
Player对角的视觉表象和它的控制是显示在浏览窗口的applet的发表空间里的。如果你在Java应用程序里创建了一个Player,你有责任创建一个窗口去显示Player对象的组件。
注意:虽然PlayerApplet演示了一个Player的基本用法,但它不能在一个真实的applet或application中执行必要的错误处理。
PlayerApplet总结
APPLET 标签被用于在一个HTML文件中调用PlayerApplet。HTML APPLET 标签HTMHHH dsfsdhf dsfdsfdsf dshfdfd g df dfdsdfasdfsdf
的WIDTH和HEIGHT域定义了浏览器窗口中applet的发表空间的大小。PARAM标签确定将要播放的媒体文件。
<APPLET CODE=ExampleMedia.PlayerApplet WIDTH=320 HEIGHT=300> |
当用户打开了一个包含PlayerApplet的网页,applet在指定的表达空间里自动加载并运行,这个表达空间包含了Player对象的可视组件和默认控制。Player启动MPEG电影,并将其播放一遍。用户可以使用默认Player控制关闭,重启,或者重放电影。如果包含applet的网页被关闭,而当时Player正在播放电影,Player会自动的停止并释放它所占用的资源。
要实现这些,PlayerApplet 继承了Applet类并实现了ControllerListener接口。PlayerApplet定义了五个方法:
·init ---- 为通过PARAM标签设置的文件创建一个Player,注册PlayerApplet作为一个控制监听者以便它能监测通过Player提交的媒体事件。(当Player提交一个事件时,会引发PlayerApplet调用ControllerUpdate方法。)
·start --- 当PlayerApplet启动时开始Player。
·stop ――― 当PlayerApplet停止时停止并释放Player。
·destroy ――― 当PlayerApplet被移除时,关闭Player。
·controllerUpdate ――― 响应Player事件并显示Player对象的组件.
初始化Applet
当一个Java applet启动时,它的init方法被自动的调用。你覆盖init方法预备让你的applet去启动。PlayerApplet在init中完成了四个任务:
1.找到applet的FILE参数。
2.使用FIFE参数定位媒体文件,生成一个URL对象描述这个媒体文件。
3.通过调用Manager.createPlayer为这个媒体文件创建一个Player。
4.通过调用addControllerListener,用新的Player注册applet作为一个控制监听者。注册监听器引起PlayerApplet 的 controllerUpdate方法被自动调用,而无论这个Player何时提交媒体事件。这个机置允许你控制Player对象在状态间的转换,并确保Player处于能够处理请求的状态下。
控制Player
当包含applet的网页被打开和关闭时,Applet类定义的start和stop方法被自动调用。你覆盖这些方法,用来定义每次applet启动和停止时所发生的事情。
PlayerApplet实现了start方法启动Player,而无论applet开始。
同样的,PlayerApplet覆盖了stop方法停止并销毁Player。
销毁Player释放任何将阻碍其他Player的启动的资源。例如,如果Player使用硬件驱动处理它的媒体,deallocate释放驱动以使其他Players能够使用。
当一个applet退出时,destroy被调用,释放了创建这个applet任何资源。PlayerApplet覆盖destroy方法关闭Player。关闭Player释放它正在使用的一切的资源,并且将它永远关闭。
响应媒体事件
PlayerApplet在它的init方法里将自己注册为一个ControllerListener监听,以便从Player中接收媒体事件。为响应这些媒体,PlayerApplet实现了controllerUpdate方法,这个方法是当Player提交一个事件时被自动调用的。
PlayerApplet响应事件的一种类型,RealizedCompleteEvent。当Player提交一个RealizeCompleteEvent时,PlayerApplet显示这个Player对象的组件.
public synchronized void controllerUpdate(ControllerEvent event) { if (event instanceof RealizeCompleteEvent) { if ((comp = player.getVisualComponent()) != null) |
Player对象的用户接口组件直到Player是Realized状态时才能被显示;一个Unrealized Player不能充分的了解关于它的媒体流访问它的用户接口组件的信息。PlayerApplet 等待Player提交一个RealizeCompleteEvent事件,然后通过将他们添加到applet容器中来显示这个Player对象的视觉表象和默认控制面板。调用validate引发布局管理器更新显示到包括新的组件。
用MediaPlayer Bean处理媒体
使用MediaPlayer Java Bean (javax.media.bean.playerbean.MediaPlayer)是最简便的方式,用来在你的applets和applications中处理媒体流。MediaPlayer在Java Bean中压缩了一个完整的JMF Player。你可以使用MediaPlayer bean’s的默认控制,也可以定制它的控制组件。
使用MediaPlayer的一个关健优势是,当为回放选择一个不同的媒体流时,它能自动创建一个新的Player。这使得可以很容易的播放一系列的媒体剪辑或使用户能够选择他们想要播放的媒体剪辑。
一个MediaPlayer bean 有几个你能设置的属性,包括媒体源:
Table 3-2: Media bean properties.
要使用MediaPlayer bean来播放媒体帧:
1.构造一个MediaPlayer的实例:
MediaPlayer mp1 = new javax.media.bean.playerbean.MediaPlayer();
2.设置你想播放的剪辑的位置:
mp1.setMediaLocation(new java.lang.String("file:///E:/jvideo/media/Sample1.mov"));
3.启动MediaPlayer:
mp1.start();
你可以在这个MediaPlayer上通过调用stop停止回放:
mp1.stop();
通过在你的Applet的init方法中设定MediaPlayer参数,并且在start方法中启动MediaPlayer,可以当Applet被加载时自动启动媒体播放。应该在Applet的stop方法中调用stop以便当Applet停止时中止回放。
你可以显示MediaPlayer bean的默认控制面板,也可提供自定义控制允许用户控制媒体播放,两者方法可以选择一种。如果你提供自定义控制,在用户交互控制时可以调用适当的MediaPlayer控制和属性方法。例如,如果你在Applet中提供了一个自定义的启动按钮,当用户点击按钮时它可监听鼠标事件及调用start。
处理RTP媒体流
通过Manager使用一个有RTP session参数的MediaLocator创建的JMF Player,你可以播放流媒体。当你使用MediaLocator为RTP session创建一个Player时,只有在session中被删除掉的第一个RTP流可以被处理----Manager在RTP session中为第一个被删除的流创建一个Player。有关从同一个session中播放多个RTP流的信息,请参阅Receiving and Presenting RTP Media Streams。
注意:适应JMF的实现并不要求支持在javax.media.rt, javax.media.rtp.event, 和javax.media.rtp.rtcp中的RTP APIs.通过Sun Microsystems,Inc.和IBM 公司提供的JMF的引用实现全部支持这些APIs.
当session中的数据被删除时,Player提交一个RealizeCompleteEvent事件。通过监听这个事件,你能够确定任何数据是否已经到达,如果Player能处理任何数据的话。一旦Player提交了这个事件,就能够找回它的可视面板和控制面板。
监听RTP 格式变化
当一个Player提交了一个FormatChangeEvent事件,它能够指示一个有效负荷的改变已经发生。Player对象用一个MediaLocator自动处理有效负荷的改变来创建。在多数情况下,这个处理包括创建一个新的Player处理新格式。编写处理RTP媒体流的程序需要监听FormatChangeEvents,以使他们在一个新的Player 被创建时可以响应。
当一个FormatChangeEvent被提交,检查Player对象的控制和可视组件是否已改变。如果他们改变了,将创建一个新的Player,而你需要将旧的Player对象的组件引用移除,并得到新的Player对象的组件。