• 沒有找到結果。

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 动画,其音量控制对象可能为空,需 要对音量控制对象进行是否为空的判断处理。

相關文件