4.1 PlayerMIDlet 类
PlayerMIDlet 仅仅提供了 MIDlet 框架,在启动界面 创建视频播放面板:
public class PlayerMIDlet extends MIDlet {
//视频播放器面板 private VideoPanel panel;
protected void startApp() { //进入视频播放界面 panel = new VideoPanel (this);
4.2 VideoPanel 类
视频播放面板类主要负责播放列表和对象池管理。
表 1 是视频播放面板类的主要成员。
播放列表界面 音量控制
通过播放列表 获取对象池
音量控制界面
文件选择模块 播放池支持类
MIDlet 入口 启动
添加播放 列表
返回文件 URL 播放 视频播放布
表 1 视频播放面板类的主要成员
编号 成员 目的
1 播放列表 List 存储/管理播放列表
2 文件选取类实例 选择视频文件,生成播放列表
3 对象池帮助类实例 生成视频播放和音量控制对象池
4 播放和音量控制对象池 对象池管理
5 播放布 提供视频播放控制
(1)文件选取模块的调用
视频播放面板的“添加播放列表”菜单调用文件选 取类实例来生成播放列表,同时生成播放项目的播放和 音量控制对象,并提供播放入口菜单:
public void commandAction(Command c, Displayable d){
…
else if(c == cmdAddPlayList) {//添加播放列表 chooser = new FileChooser(this);
}
…
//结束添加列表
public void finishAddList() {
//显示当前界面(必须在 showSelected 之前)
display.setCurrent(playList);
//显示选择结果 showSelected();
}
//显示选择结果
private void showSelected() { //当前选取播放资源
String uri = chooser.getCurrentFile();
if(uri == null) {//取消选择 return;
}
if(URITable.indexOf(uri) == -1) {//该资源不存在 //添加资源列表
URITable.insertElementAt(uri, 0);
//以插入方式
playList.insert(0, getRelativeName(uri), iconHelper.
getIconByExt(extractExt(uri) ) );
}
else {//该资源已经存在,无需更新
Alert alert = new Alert("Error", "This item already exists!!", errorImage, AlertType.ERROR);
alert.setTimeout(Alert.FOREVER);
Display.getDisplay(let).setCurrent(alert);
return;
}
//对象池帮助实例
helper = new PoolHelper(this, uri);
helper.start();
//添加播放入口菜单
playList.addCommand(cmdPlay);
}
以上代码中,主模块通过提供 finishAddList 方法来 获取文件选取模块的返回。当选取完毕后,对选取项目
还要进行重复性判断,如果存在重复则提示,如果不存 在则调用对象池帮助类生成该项目的播放和音量控制 对象,同时提供播放入口菜单。图 3 是在 WTK 模拟器 和 Nokia5310 实机中开始播放列表的界面。
图 3 开始播放列表
(2)播放控制
视频播放面板只提供播放入口方法来启动播放布,
真正的播放控制在播放布类中:
public void commandAction(Command c, Displayable d){
…
else if(c == cmdPlay) {//播放 //记录当前选取媒体索引
selectedIndex = playList.getSelectedIndex();
if(initPlay() == true) {//播放成功 }
} } //开始播放
private boolean initPlay() { //播放测试
if(canvas.play(selectedIndex) ) { //设置显示
Display.getDisplay(let).setCurrent(canvas);
return (true);
}
…
(3)对象池的管理
对象池的初始化在对象池帮助类完成,在系统关闭
时进行对象池的释放:
public void commandAction(Command c, Displayable d){
if(c == cmdExit) {//退出系统 //释放播放池
freePlayerPool();
let.exit();
}
…
private void freePlayerPool() { int playerCount = playerPool.size();
//关闭所有的连接和文件流 for(int i = 0; i < playerCount; ++i) { //关闭播放器
Player player = (Player)playerPool.elementAt(i);
if(player != null) {
player.close();
} }
//播放器池和音量调节池,视频控制池 playerPool.removeAllElements();
volumePool.removeAllElements();
videoStatusPool.removeAllElements();
}
以上代码中关闭了所有视频播放器对象,释放视频 播放的所有相关资源。
4.3 PoolHelper 类
对象池帮助类,主要是根据文件选取模块提供的 URI 来生成播放项目的视频播放和音量控制对象池:
public void run() { Player player = null;
VolumeControl volume = null;
try { //创建播放
player = Manager.createPlayer(URI);
player.realize();
//设置播放侦听
player.addPlayerListener(panel.canvas);
//获取音量控制
volume = (VolumeControl)player.getControl (VOLUME_CTRL_NAME);
if(volume != null) {
//某些时候可能会为空,该错误无法捕获 volume.setLevel(62); //0.618
} }
catch (IOException e) { e.printStackTrace();
}
catch(MediaException e) { e.printStackTrace();
}
//添加到媒体文件流容器(以插入方式)
panel.playerPool.insertElementAt(player, 0);
panel.volumePool.insertElementAt(volume, 0);
panel.videoStatusPool.insertElementAt(new Boolean (false), 0);
}
以上代码,为了避免访问本地资源所造成的资源 死锁,对象池帮助类为线程类。在 run 函数中通过文件 URI 创建播放器和音量控制对象,并插入到对象池(后 进先出)。
对于视频播放器的创建是整个工程的核心,也是开 发人员比较迷惑的地方。
(1)视频播放对象的困惑
1)对视频媒体类型的支持。图 4 是常见的媒体类 型不支持的异常画面。
图 4 视频类型不支持警告
对于 WTK 模拟环境(浏览使用的是 WTK2.5.2),
似乎只支持 mpeg 类型的视频媒体,播放 mp4、3gp 类 型的视频时就会抛出不支持的内容类型的异常。对于手 机实机环境,厂商承诺支持的类型程序都可以支持。在 Nokia5310 环境中,可以支持 3gp、wmv、mp4 等主流 视频媒体类型以及 gif 动画,反而不支持 mpeg 类型。
2)视频播放对方的创建方式。上段代码中并没有 使用输入流和媒体内容类型(content type)来创建播放 器对象,而是通过视频文件 URI 自动创建。
使用输入流创建播放器对象存在制约。在模拟器环 境下调试发现,使用输入流创建播放器的方式不适用较 大的媒体文件,否则将抛出内存不足的异常。实机环境 下,往手机上安装文件大小大于一定限制的 jar 文件时,
会得到一个安装包过大而提示无法安装(大小限制额度 可能因各款手机不同而存在一定差异,但是这个值应该 小于一个常规视频文件的大小)。图 5 就是在将一个文 件大小为 2M 的 jar 文件安装到手机时提示的错误。
图 5 安装 jar 文件的错误提示
使用输入流创建视频播放器对象适用于文件大小 较小的视频文件,这也是 J2ME 教程中常用的通过 Class 类的 getResourceAsStream 的方法在资源中获取文件流 来创建播放对对象的方式。
3)创建视频控制对象的时机。上段代码中并没有在
生成播放对象的同时生成视频控制对象,其主要原因有 两 点 : 一 方 面 是 视 频 控 制 对 象 需 要 绑 定 到 播 放 布
(Canvas),当初始化视频控制对象显示模式时,会产生 视频控制对象视图与当前界面重叠的问题。如图 6 所示。
图 6 初始化视频控制对象模式造成界面重叠 另外一方面,视频播放布应该是共享的显示资源,
不可能为每个视频控制器创建播放布。基于这种对应关 系,视频控制器的管理应该纳入播放布的范围。
以上的这些“机关”也正是在前文说的仅仅在 WTK 模拟上写一个简单的播放器是很简单的,而要在实机上 写一款能用的视频播放器却是比较困难的原因。
4.4 PlayCanvas
播放布类是视频播放的核心,负责播放控制、播放 侦听以及视频控制对象的管理。
(1)播放控制
public void commandAction(Command c, Displayable d) { if(c == cmdPlay) { //恢复播放
resumePlay();
}
else if(c == cmdPause) {//暂停 if(pausePlay() ) {//暂停成功 removeCommand(cmdPause);
addCommand(cmdPlay);
} }
else if(c == cmdStop) {//停止
if(pausePlay() ) {//暂停成功并切换到主界面 //隐藏视频显示
hideVideo();
//切换显示界面
Display.getDisplay(let).setCurrent(playList.playList);
} }
else if(c == cmdSetting) {//音量控制设置 if(volume != null) {//某些时候可能会为空 VideoSettingPanel panel = new VideoSettingPane l(let, this, volume);
} }
else if(c == cmdFullScreen) {//全屏 try {
addCommand(cmdNormalScreen);
removeCommand(cmdFullScreen);
video.setDisplayFullScreen(true);
}
catch (MediaException screen) { screen.printStackTrace();
} }
else if(c == cmdNormalScreen) {//正常屏幕 Try {
addCommand(cmdFullScreen);
removeCommand(cmdNormalScreen);
video.setDisplayFullScreen(false);
}
catch (MediaException screen) { screen.printStackTrace();
} } }
以上代码中,播放布类提供了播放、暂停、全屏幕、
正常屏幕和音量控制(如图 1 所示的播放界面)。这里 并没有提供“完全结束播放”(close 方法)的接口,因 为对于播放“完全结束”会造成播放对象的释放,必须 重新创建播放器对象才能再执行播放行为,否则在播放 对象关闭的状态下调用播放方法会弹出异常(详细错误 内容可以参见 MMAPI 参考文档),所以只有当系统关 闭时才考虑“完全结束播放”。
(2)视频控制
当选择播放项目进行播放时,播放布就会获取播放 对象的视频控制对象并启动播放:
synchronized public boolean play(final int __selectedIndex) { //设置播放器
player = (Player)playList.playerPool.elementAt (__selectedIndex);
volume = (VolumeControl)playList.volumePool.
elementAt(__selectedIndex);
if(player == null) {
//播放器不能为空(音量控制器可能为空)
return (false);
}
//设置视频控制
video = (VideoControl)player.getControl ("VideoControl");
if(video == null) {//视频控制不能为空 return (false);
}
//初始化状态判断,重复初始化会抛出 mode already set 异常 Boolean isVideoInit = (Boolean)playList.videoStatusPool.
elementAt(__selectedIndex);
if(isVideoInit.booleanValue() == false) { video.initDisplayMode(VideoControl.USE_
DIRECT_VIDEO, this);
playList.videoStatusPool.setElementAt(new Boolean (true), __selectedIndex);
} try {
video.setDisplaySize(video.getDisplayWidth(), video.getDisplayHeight() );
}
catch (MediaException e) { }
return (initPlay() );
} //开始播放
synchronized private boolean initPlay() { try {
//设置视频控制可见 video.setVisible(true);
//设置居中显示
video.setDisplayLocation((getWidth()- video.getDisplayWidth() ) / 2, (getHeight() – video.getDisplayHeight() ) / 2);
//开始播放 player.start();
}
catch(MediaException e) {
Alert alert = new Alert("Exception", e.getMessage(), errorImage, AlertType.ERROR);
alert.setTimeout(Alert.FOREVER);
//显示警告
Display.getDisplay(let).setCurrent(alert);
e.printStackTrace();
return (false);
}
return (true);
}
以上代码中,播放布通过对象池中的播放对象来获 取视频控制对象(VideoControl),并初始化视频控制对 象的显示模式和区域大小,然后调整播放显示位置并开 始播放。其中有 3 个比较重要的地方:
(1)视频控制对象的模式初始化只能成功调用一 次,如果再次调用会抛出模式已经设置的异常。所以必 须对初始化状态进行管理,避免重复初始化(上段代码 中使用视频状态池 videoStatusPool 来进行状态管理)。
(2)对于部分视频,例如 gif 动画,存在没有音量 控制对象的情形,所以要对音量控制对象进行是否为空 的判断,否则会抛出指针为空的异常。
(3)为了完整监控播放状态,必须设置播放布为 播放侦听(PlayerListener)。
4.5 AudioSettingPanel
音量控制面板由播放布调用,主要用于提供音量控 制的界面,实现当前播放音量的控制:
public class AudioSettingPanel implements CommandListener, ItemStateListener
… //Gauge
private Gauge gauge;
…
//改变音量值
public void itemStateChanged(Item item) {
vc.setLevel(gauge.getValue() );
gauge.setLabel("Volume: " + gauge.getValue() );
}
以上代码中,该面板类实现了 ItemStateListener,
通过一个 Guage 来实现音量的控制。实际上,各款手 机所呈现的 Guage 组件与 WTK 模拟器显示的是不同 的。图 7 是 Guage 在 WTK 模拟器和 Nokia5310 实机的 运行界面。
图 7 Guage 组件运行界面
通过以上过程可以总结出,在实现手机视频播放器 的过程中要注意以下 7 个窍门:
(1)播放控制由播放布类完成,播放主界面只需 要提供开始播放的入口。
(2)播放布同时要设置为播放侦听,这样才能对 播放状态进行全面的监控。
(3)在播放控制中并不需要提供“完全结束播放”
(close 方法)的接口,可以通过停止接口来实现播放项 目的切换(被停止的项目可以接着播放),否则在播放 器关闭的状态下再执行播放操作会抛出播放异常。
(4)通过文件 URI 来创建播放器对象,可以避免 使用文件流来创建播放器造成的限制,同时也可以避免 对视频文件类型的管理。
(5)视频控制对象的获取在播放布初始化中进行,
而不能用对象池进行管理。
(6)视频控制对象的模式初始化只能有一次,再 次初始会抛出模式已经设置的异常。所以必须进行初始 化状态的管理。
(7)对于 gif 动画,其音量控制对象可能为空,需 要对音量控制对象进行是否为空的判断处理。