J2ME视频播放
关于J2ME视频播放的问题,首先直接的切入话题,用J2ME播放3gp视频文件,即使再网络非常好的情况下,能否实现播放的连续和不中断,也就是边载入 边播放,为此我在网上查找了很多相关的资料和别人开发的软件,在捉鱼网上找到了个土豆视频,介绍上说可以实现完美播放,玩家好评70%,可是我真正的下了 个,发现,就给了两网址,然后调用手机自带的浏览器去下载视频资源,然后土豆视频的任务完工,我晕,从此彻底对捉鱼网失去信心了,言归正传,到底能不能 呢?
下面分析两种实现思路:
1.截取部分流进行播放
可是从实际的结果反馈方面观察我发现,这部分流根本就无法播放,问题出现在player.start();这个方法上 ,这里要说明下:我验证了很多步骤,都没有问题,但是只要调用player.start()方法后,那么程序就会跑出MediaException的异常 情况,查找了资料,同时请教了上海那边的一位牛人,探讨了下player.start()的底层实现:
J2ME中视频方面的API内部就已经嵌入了自己的解码程序,当然这些API里面的内容是不会向我们开放的,当player.start()方法启动 后,player会首先从载入的视频流中预先获取一些播放信息,比如播放的时间长度,播放信息等,而这些播放信息是分布在整个视频文件流的各个部分的,当 player无法找到这些信息的时候,就会跑出MeidaException ,既然无法播放也就是说如果我们从网络上截取的部分流里面不含有这些播放信息
针对这种情况,由此可以引发两种解决方案:
. ----------------进行视频解码
进行视频解码的目的就是让player能够正常的进行播放,那么现在最最紧迫的问题出现了,通过3GP视频解码在网络非常好的情况下,能够实现边播放边载入的功能呢?
那么我们从J2ME中的API进行分析
- Player player = Manager.creatPlayer(inputStream); //inputStream是网络下载的视频文件流
- plyaer.start();
从这里的代码分析,实现边载入边播放的关键是什么啊?
答案是player在start()后,能否允许我们为其补充视频流,player播放后,播放的就是视频流,我们只有继续的为其补充视频流才能实现真正 的边载入边播放,但是纵观J2ME的 api和文档说明,似乎player在播放后,就不在支持继续载入视频流了,因此J2ME是无法实现边载入边播放的功能的,因此UCWEB并没有打算开发 JAVA版本的播放器,同时也让我觉得J2ME在手机软件领域有点无能,因此才促发了我学习Symbian的决心,但是即使无法实现边载入边播放的功能, 我们还是可以实现播放过程中非常短的停顿的,也就是下面我将要介绍的轮播思想
.----------------------更换思路,采用轮播的方式进行解答 ,也就是我要介绍的第二种解决方案
首先说明一下,这里的思路我已经实现,而且在网络好的情况下,中间真的可以实现无缝对接,停顿的时间很短,第一段视频播放完后,视频二立马开始播放,但是 用户还是能够感觉的到中间有那么一小会儿停顿,实现这样的效果给了我无比的信息,同时也坚定了我从事手机软件开发的决心,我的思路是这样的:
ThreadMain 类说明:
- /**
- * 该线程主要用于下载资源并进行播放
- */
- public class ThreadMain{
- /*下载状态*/
- public byte m_loadState ;
- /*已经读取的字节*/
- private int read ;
- /*需要读取的总的字节数*/
- private int total ;
- /*存放读取的字节*/
- private byte [] data ;
- private Player player ;
- private PlayCanvas canvas ;
- private Display display ;
- /*资源正在下载中*/
- public final byte m_loading = 0 ;
- /*播放中*/
- public final byte m_playing = 1 ;
- /*播放完毕*/
- public final byte m_playover = 2 ;
- /*另一个线程已经开始播放了,那么就等待*/
- public final byte m_waiting = 3 ;
- /*资源已经载入完毕状态*/
- public final byte m_loadover = 4 ;
- public byte ioe = 0 ; //
- /*能否进行播放*/
- public byte canPlay = 0 ; //0 表示不能进行播放, 1 表示能进行播放
- ThreadMain(PlayCanvas canvas){
- this .canvas = canvas ;
- display = NeverGiveUp.getDisplay(); //获得视频显示权
- }
- /**
- * 开始下载资源
- */
- public void downLoadSrc( final String tv) {
- read = 0 ;
- total = 0 ;
- data = null ;
- m_loadState = m_loading ;
- new Thread (){
- public void run(){
- //采用的是CM_WAP的方式进行连接访问的
- //example: http://kukutv.kukuwap.com/tv/ceshi/nanhaichouyan_1.3gp
- int i = tv.indexOf( '/' , 7 );
- String server = tv.substring(i);
- String host = tv.substring( 7 , i);
- try {
- HttpConnection hp = (HttpConnection)Connector.open( "http://10.0.0.172:80" +server);
- hp.setRequestProperty( "X-Online-Host" , host);
- hp.setRequestProperty( "Cache-Control" , "no-cache, must-revalidate" );
- hp.setRequestProperty( "Pragma" , "no-cache" );
- hp.setRequestProperty( "Connection" , "Keep-Alive" );
- hp.setRequestProperty( "Accept" , "*/*" );
- hp.setRequestProperty( "Content-Language" , "en-US" );
- total = ( int )hp.getLength(); //需要读取的总的字节数
- if (total != - 1 )
- {
- System.out.println( "total:" +total);
- data = new byte [total] ;
- InputStream is = hp.openInputStream();
- i = 0 ;
- while (read < total)
- {
- read += is.read(data, read, total - read);
- i++;
- if (i % 20 == 0 )
- {
- Thread.Sleep( 100 );
- }
- }
- is.close();
- is = null ;
- hp.close();
- hp = null ;
- ByteArrayInputStream bas = new ByteArrayInputStream (data);
- data = null ; //因为data太大,必须清空,方便垃圾回收机制进行回收
- player = Manager.createPlayer(bas, "video/3gpp" ); //这里会耗费一点时间,因为要讲流资源转换成为视频资源,该方面里面已经内嵌了解码器
- player.addPlayerListener(canvas);
- player.realize(); //准备进行播放
- bas = null ; //bas的使用寿命结束
- VideoControl vc = (VideoControl) player.getControl( "VideoControl" );
- m_loadState = m_loadover ; //进入载入数据完毕的状态
- if (vc != null )
- {
- vc.initDisplayMode(VideoControl.USE_DIRECT_VIDEO, canvas);
- vc.setDisplayLocation( 50 , 50 );
- vc.setVisible( true ); //设置为可显
- if (canPlay == 0 ) //不能进行播放
- {
- //只有进入等待状态
- m_loadState = m_waiting ;
- }
- else //能够进行播放
- {
- startPlay();
- }
- }
- else
- {
- System.out.println( "不支持视频播放功能,或者使用模拟器" );
- }
- }
- else
- {
- System.out.println( "total:" +total);
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- catch (InterruptedException e) {
- e.printStackTrace();
- }
- catch (MediaException e) {
- e.printStackTrace();
- }
- }
- }.start();
- }
- public void startPlay(){
- try {
- player.start();
- } catch (MediaException e) {
- e.printStackTrace();
- }
- //进入到播放状态
- m_loadState = m_playing ;
- display.setCurrent(canvas);
- }
- public Player getPlayer(){
- return player ;
- }
- //获得已经读取的字节数
- public int getRead(){
- return read ;
- }
- //获得需要读取的总的字节数
- public int getTotal(){
- return total ;
- }
- //释放视频资源
- public void delcate(){
- if (player != null )
- {
- player.deallocate();
- player = null ;
- }
- canPlay = 0 ; //不能在进行播放了
- System.gc(); //建议垃圾回收机制进行垃圾回收
- }
- }
ThreadMain的主要任务是和PlayCanvas密切搭配的,PlayCanvas的主要任务是空着两者的播放顺序,同时监控播放情况,然后通知ThreadMain对象做出相应的调整,因此我让PlayCanvas实现了PlayerListener接口
PlayCanvas
- import javax.microedition.lcdui.Canvas;
- import javax.microedition.lcdui.Graphics;
- import javax.microedition.media.Player;
- import javax.microedition.media.PlayerListener;
- /**
- * 主要用于视频播放的类
- */
- public class PlayCanvas extends Canvas implements Runnable, PlayerListener {
- /*要进行访问的网页*/
- private String str ;
- /*开始的索引号*/
- private int s ;
- /*结束的索引号*/
- private int e ;
- /*当前载入视频的编号*/
- private int loadIndex ;
- private ThreadMain m_oneThread ;
- private ThreadMain m_twoThread ;
- /*显示状态*/
- private byte m_showState ;
- /*两个同时下载*/
- private final byte d_Loading = 0 ;
- /*线程一下载*/
- private final byte o_Loading = 1 ;
- /*线程二下载*/
- private final byte t_Loading = 2 ;
- /*是否需要刷新屏幕*/
- public boolean rflush = true ; //因为在播放视频的时候,如果刷新的话会引起播放的闪屏的
- PlayCanvas(String str, int s, int e)
- {
- this .str = str ;
- this .s = s ;
- this .e = e ;
- loadIndex = s ;
- m_oneThread = new ThreadMain ( this );
- m_twoThread = new ThreadMain ( this );
- this .setFullScreenMode( true ); // 设置为全屏幕模式
- new Thread ( this ).start(); //启动线程
- }
- protected void paint(Graphics g) {
- g.setColor( 0 );
- g.fillRect( 0 , 0 , Const.ScreenWidth, Const.ScreenHeight);
- switch (m_showState)
- {
- case d_Loading : //两个同时下载
- //显示出两个同时下载的进度显示
- g.setColor(Const.White);
- if (m_oneThread.getTotal() == 0 )
- {
- g.drawString( "线程一正在连接中" , Const.ScreenWidth/ 2 , Const.ScreenHeight- 40 , Const.HACBAS);
- }
- else
- {
- // 显示线程一的下载进度
- int one = m_oneThread.getRead()* 100 /m_oneThread.getTotal();
- System.out.println( "read:" +m_oneThread.getRead());
- System.out.println( "total:" +m_oneThread.getTotal());
- g.drawString( "线程一载入进度:" +one, Const.ScreenWidth/ 2 , Const.ScreenHeight- 40 , Const.HACBAS);
- }
- if (m_twoThread.getTotal() == 0 )
- {
- g.drawString( "线程二正在连接中" , Const.ScreenWidth/ 2 , Const.ScreenHeight- 20 , Const.HACBAS);
- }
- else
- {
- int two = m_twoThread.getRead()* 100 /m_twoThread.getTotal();
- // System.out.println("read:"+m_twoThread.getRead());
- // System.out.println("total:"+m_twoThread.getTotal());
- g.drawString( "线程二载入进度:" +two, Const.ScreenWidth/ 2 , Const.ScreenHeight- 20 , Const.HACBAS);
- }
- break ;
- case o_Loading: //线程一下载
- g.setColor(Const.White);
- g.drawString( "线程一正在载入中" , Const.ScreenWidth/ 2 , Const.ScreenHeight- 30 , Const.HACBAS);
- break ;
- case t_Loading: //线程二下载
- g.setColor(Const.White);
- g.drawString( "线程二正在载入中" , Const.ScreenWidth/ 2 , Const.ScreenHeight- 30 , Const.HACBAS);
- break ;
- }
- if (m_oneThread.ioe == 1 )
- {
- m_oneThread.notifyAll();
- m_oneThread.ioe = 0 ;
- }
- if (m_twoThread.ioe == 1 )
- {
- m_twoThread.notify();
- m_twoThread.ioe = 0 ;
- }
- }
- /**
- * 设定当前屏幕要显示的内容
- */
- public void changeState( byte iState){
- m_showState = iState ;
- }
- public void run() {
- Init(); //进行一些初始化的操作
- while (rflush){
- this .repaint();
- this .serviceRepaints();
- try {
- Thread.sleep( 100 );
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- private void Init() {
- //分别下载资源
- m_oneThread.downLoadSrc(str+loadIndex+ ".3gp" );
- m_oneThread.canPlay = 1 ; //视频一能够进行播放
- loadIndex += 1 ;
- m_twoThread.downLoadSrc(str+loadIndex+ ".3gp" );
- loadIndex += 1 ;
- m_twoThread.canPlay = 0 ; //视频二不能进行播放,必须等视频一播放完后才能进行播放
- }
- /**
- * 监听ThreadMain中Player的播放状态(我在播放界面对ThreadMain进行监控)
- */
- public void playerUpdate(Player player, String event, Object object) {
- if (player == m_oneThread.getPlayer()) //是线程一的播放器
- {
- if (event == PlayerListener.STARTED) //线程一的播放器已经在播放中了
- {
- rflush = false ; //不在需要刷新屏幕了,避免闪屏
- changeState(t_Loading);
- repaint(); //重绘一下
- }
- else if (event == PlayerListener.END_OF_MEDIA) //播放完毕
- {
- // ThreadMain.ioe = 0 ;//释放锁
- m_oneThread.delcate();
- //视频一播放完后的第一件事情就是载入下段视频
- m_oneThread.downLoadSrc(str+loadIndex+ ".3gp" ); //视频一播放完后,立即开始载入下一段视频
- loadIndex++;
- //判断线程二是否载入完毕
- if (m_twoThread.m_loadState == m_twoThread.m_waiting) //视频一播放完后,视频二已经在等待中了
- {
- m_twoThread.startPlay(); //视频二直接开始进行播放
- }
- else
- {
- m_twoThread.canPlay = 1 ; //视频二能够进行播放
- changeState(d_Loading); //让当前画面显示两者的载入进度
- rflush = true ;
- repaint();
- }
- }
- }
- else // 线程二的
- {
- if (event == PlayerListener.STARTED) //开始进行播放
- {
- rflush = false ;
- changeState(o_Loading);
- repaint();
- }
- else if (event == PlayerListener.END_OF_MEDIA) //播放结束
- {
- // ThreadMain.ioe = 0 ;
- //线程二播放完毕后的第一件事情,就是继续的载入剩余的资源
- m_twoThread.delcate();
- m_twoThread.downLoadSrc(str+loadIndex+ ".3gp" );
- loadIndex++;
- if (m_oneThread.m_loadState == m_oneThread.m_waiting) //线程一正在等待播放
- {
- m_oneThread.startPlay(); //线程一进行播放
- }
- else
- {
- m_oneThread.canPlay = 1 ; //视频一能够进行播放了
- changeState(d_Loading); //更改状态两个都在载入
- rflush = true ;
- repaint();
- }
- }
- }
- }
- }
前言:
在服务器端,是将视频进行了切割的,一个文件大约170K左右,按照顺序放在了服务器上
具体逻辑思路是这样的,代码细节大家请看代码:
首先在当启动playerCavas中的线程时,让m_oneThread和m_twoThread同时downLoadSrc();但是 m_oneThread中的canPlay = 1 ;m_twoThread中的canPlay = 0; (1代表能够进行播放,0代表不能够进行播放),这样处理的目的是按照顺序来播放,也就是1 2 3 4 的按顺序进行播放,然后资源载入的差不多,开始进行播放, 这个时候程序这边就要非常小心的对播放进行监控了,如果视频一播放完了,那么首先要做的第一件事情就是让视频一的线程立马的继续下载下一个视频的内容,同 时调整状态m_oneThread.canPlay = 0 ,m_twoThread.canPlay = 1,然后要判断视频二的状态,如果视频二是waiting状态,也就是播放器已经创建好了,就等着进行播放了,这样切换的时候,无法避免的中间会出现短暂 的暂停,如果视频二还是Loading状态,那么程序切换状态都显示两个的下载进度