【基于Ijkplayer】安卓机顶盒电视播放器开发

前言

Ijkplayer是B站开源的一款多媒体播放引擎,其基于ffmpeg开并支持很多的在线媒体播放格式。本文实现了在安卓TV上播放各大电视台的直播,其格式是.m3u8。当然了只要是编译的Ijkplayer支持的格式这款播放器自然可以播放。

首先上界面:


播放器界面非常简单,因为是用电视遥控器操作,所以布局和操作力求简单易用。布局分为播放区域和左侧菜单,按遥控器的确定按钮调出菜单,可以选择界面确认后进入播放界面。下面开始讲如何实现:

一,创建布局

播放器主界面布局文件activity_play_rel.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
复制代码
<?xml version="1.0" encoding="utf-8"?>  
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:orientation="vertical">  
  
    <LinearLayout  
        android:layout_width="fill_parent"  
        android:layout_height="fill_parent"  
        android:orientation="vertical">  
  
        <RelativeLayout  
            android:id="@+id/fl_videoview"  
            android:layout_width="match_parent"  
            android:layout_height="match_parent"  
            android:background="@color/colorBlack">  
  
            <com.gxf.liveplay.ijkplayer.media.IjkVideoView  
                android:id="@+id/videoview"  
                android:layout_width="match_parent"  
                android:layout_height="match_parent"  
                android:layout_centerInParent="true"  
                android:background="@color/colorBlack"></com.gxf.liveplay.ijkplayer.media.IjkVideoView>  
  
            <RelativeLayout  
                android:id="@+id/rl_loading"  
                android:layout_width="match_parent"  
                android:layout_height="match_parent"  
                android:background="#de262a3b">  
  
                <TextView  
                    android:id="@+id/tv_load_msg"  
                    android:layout_width="wrap_content"  
                    android:layout_height="wrap_content"  
                    android:layout_below="@+id/pb_loading"  
                    android:layout_centerInParent="true"  
                    android:layout_marginTop="6dp"  
                    android:textColor="#ffffff"  
                    android:textSize="16sp" />  
  
                <ProgressBar  
                    android:id="@+id/pb_loading"  
                    android:layout_width="60dp"  
                    android:layout_height="60dp"  
                    android:layout_centerInParent="true"  
                    android:layout_marginTop="60dp"  
                    android:indeterminate="false"  
                    android:indeterminateDrawable="@drawable/video_loading"  
                    android:padding="5dp" />  
            </RelativeLayout>  
  
            <!-- <LinearLayout  
                 android:layout_width="match_parent"  
                 android:layout_height="wrap_content"  
                 android:background="@color/player_panel_background_color">  
  
                 <TextView  
                     android:id="@+id/tv_title"  
                     android:layout_width="wrap_content"  
                     android:layout_height="60dp"  
                     android:textSize="24dp"  
                     android:text=""  
                     android:layout_centerVertical="true"  
                     android:layout_marginTop="18dp"  
                     android:textColor="@color/white"/>  
  
                 <TextView  
                     android:id="@+id/tv_time"  
                     android:layout_width="wrap_content"  
                     android:layout_height="60dp"  
                     android:textSize="20dp"  
                     android:layout_toRightOf="@id/tv_title"  
                     android:layout_alignParentRight="true"  
                     android:layout_centerVertical="true"  
                     android:layout_marginLeft="60dp"  
                     android:layout_marginTop="20dp"  
                     android:textColor="@color/white"/>  
             </LinearLayout>-->  
        </RelativeLayout>  
    </LinearLayout>  
  
    <include layout="@layout/layout_menu" />  
</RelativeLayout>
复制代码

整体分为两部分:上边为播放器实现的界面布局,下边为左侧菜单布局。这里没有采用滑动调出左侧菜单的效果,只是采用相对布局通过设定菜单的宽带直接覆盖在播放器布局之上。IjkVidoView下边的是播放器的控制器布局,可以扩展实现进度音量等控制,本文尚未实现。

接下来看左侧菜单布局文件layout_menu.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
复制代码
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="360dp"  
    android:layout_height="match_parent"  
    android:id="@+id/leftMenuCtrl"  
    android:layout_gravity="left"  
    android:visibility="gone"  
    android:background="@drawable/dark_no_shadow"  
    android:orientation="vertical">  
  
    <LinearLayout  
        android:layout_width="wrap_content"  
        android:layout_height="0dp"  
        android:layout_weight="1"  
        android:layout_gravity="center"  
        android:orientation="horizontal">  
  
        <ImageButton  
            android:id="@+id/turnLeft"  
            android:layout_width="30px"  
            android:layout_height="40px"  
            android:layout_gravity="center"  
            android:adjustViewBounds="false"  
            android:background="@drawable/tv_switch_bg_left"  
            android:onClick="turnLeft" />  
  
        <TextView  
            android:id="@+id/tvGroupTitle"  
            android:layout_width="300px"  
            android:layout_height="100px"  
            android:layout_gravity="center"  
            android:gravity="center"  
            android:text="我的频道"  
            android:textColor="@color/white"  
            android:textSize="24sp"  
            android:textStyle="bold"  />  
  
        <ImageButton  
            android:id="@+id/turnRight"  
            android:layout_width="30px"  
            android:layout_height="40px"  
            android:layout_gravity="center"  
            android:adjustViewBounds="false"  
            android:background="@drawable/tv_switch_bg_right"  
            android:onClick="turnRight" />  
  
    </LinearLayout>  
  
    <ListView  
        android:id="@+id/listView"  
        android:layout_width="match_parent"  
        android:layout_height="0dp"  
        android:layout_weight="9"  
        android:layout_gravity="center"  
        android:listSelector="@color/colorPrimaryDark"/>  
</LinearLayout>
复制代码

上边的LinearLayout用于切换节目组,下边是ListView显示每个分组下的节目列表。这次布局就实现了,这是相当简单的。

二,实现播放

LiveActivityRel实现了播放功能,启动该Activity的时候传入媒体资源的地址mVideoUrl,然后启动Ijkplayer的播放功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
复制代码
public void initVideo() {  
        // init player`  
        IjkMediaPlayer.loadLibrariesOnce(null);  
        IjkMediaPlayer.native_profileBegin("libijkplayer.so");  
        mVideoView.setVideoURI(Uri.parse(mVideoUrl));  
        mVideoView.setOnPreparedListener(new IMediaPlayer.OnPreparedListener() {  
            @Override  
            public void onPrepared(IMediaPlayer mp) {  
                mp.setVolume(100, 100);  
                mVideoView.start();  
            }  
        });  
  
        mVideoView.setOnInfoListener(new IMediaPlayer.OnInfoListener() {  
            @Override  
            public boolean onInfo(IMediaPlayer mp, int what, int extra) {  
                switch (what) {  
                    case IjkMediaPlayer.MEDIA_INFO_BUFFERING_START:  
                        mLoadingLayout.setVisibility(View.VISIBLE);  
                        break;  
                    case IjkMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START:  
                    case IjkMediaPlayer.MEDIA_INFO_BUFFERING_END:  
                        mLoadingLayout.setVisibility(View.GONE);  
                        break;  
                }  
                return false;  
            }  
        });  
  
        mVideoView.setOnCompletionListener(new IMediaPlayer.OnCompletionListener() {  
            @Override  
            public void onCompletion(IMediaPlayer mp) {  
                mLoadingLayout.setVisibility(View.VISIBLE);  
                mVideoView.stopPlayback();  
                mVideoView.release(true);  
                mVideoView.setVideoURI(Uri.parse(mVideoUrl));  
            }  
        });  
  
        mVideoView.setOnErrorListener(new IMediaPlayer.OnErrorListener() {  
            @Override  
            public boolean onError(IMediaPlayer mp, int what, int extra) {  
                if (++mRetryTimes > CONNECTION_TIMES) {  
                    new AlertDialog.Builder(LiveActivityRel.this)  
                            .setMessage("该频道媒体资源出现错误,节目暂时不能播放...")  
                            .setPositiveButton(R.string.VideoView_error_button,  
                                    new DialogInterface.OnClickListener() {  
                                        public void onClick(DialogInterface dialog, int whichButton) {  
                                            dialog.dismiss();  
                                            //LiveActivityRel.this.finish();  
                                        }  
                                    })  
                            .setCancelable(false)  
                            .show();  
                } else {  
                    mVideoView.stopPlayback();  
                    mVideoView.release(true);  
                    mVideoView.setVideoURI(Uri.parse(mVideoUrl));  
                }  
                return false;  
            }  
        });  
  
    }
复制代码

这里也是很简答的,首先进行库的加载和初始化,绑定各种事件,然后在setOnPreparedListener中启动播放。其余的功能主要是涉及到UI和数据解析。节目列表是一个JSON结构,在原来的项目是有API进行登录和节目列表获取的。为了方便本文直接把节目列表写死,同时启动的Activity设置为MainActivityOffline。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
复制代码
package com.gxf.liveplay;  
  
import android.app.Activity;  
import android.content.Context;  
import android.content.Intent;  
import android.content.SharedPreferences;  
import android.os.Bundle;  
import android.widget.Toast;  
  
/** 
 * splash 
 * 启动登录检测 
 */  
public class MainActivityOffline extends Activity {  
    private final int SPLASH_DISPLAY_LENGHT = 1000; // 两秒后进入系统  
    private boolean isUpdateChecked = false;  
    public void checkLogin(){  
        SharedPreferences userSettings = getSharedPreferences("auth", Context.MODE_PRIVATE);  
        final String lastVedioUrl = userSettings.getString("lastVedioUrl", "none");  
        final Toast toast = Toast.makeText(MainActivityOffline.this, "系统初始化失败...", Toast.LENGTH_SHORT);  
        // 开启一个子线程,进行网络操作,等待有返回结果,使用handler通知UI  
        new Thread(new Runnable() {  
            @Override  
            public void run() {  
                try {  
                    PlayListCache.initPlayInfo(null,null);  
                    String url = lastVedioUrl;  
                    if (url.equals("none"))  
                        url = PlayListCache.playListMap.get(PlayListCache.playListMap.keySet().iterator().next());  
                    LiveActivityRel.activityStart(MainActivityOffline.this, url);  
                    MainActivityOffline.this.finish();  
                } catch (Exception e) {  
                    toast.show();  
                    e.printStackTrace();  
                }  
            }  
        }).start();  
    }  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        checkLogin();  
    }  
}
复制代码

启动App在一个线程里去获取节目列表并缓存起来放在PlayListCache中,然后调用LiveActivityRel并传入一个默认的播放地址(首次启动)或者上次播放的地址。至于登录和HttpUtils中的代码(主要用于节目列表和登录控制)就不在赘述了,因为不属于本文的重点。

三,项目的代码

github.com/gongxufan/T…

THE END

PS:本项目中的播放源地址可能会失效,如需测试需要更改可用的播放源。

具体代码在com.gxf.liveplay.HttpUtils#getOfflinePlayList,数据格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
复制代码
[  
  {  
    "group": "省内频道",  
    "list": [  
      {  
        "湖南都市": "http://220.248.175.231:6610/001/2/ch00000090990000001049/index.m3u8?virtualDomain=001.live_hls.zte.com"  
      },  
      {  
        "湖南经视": "http://220.248.175.230:6610/001/2/ch00000090990000001052/index.m3u8?virtualDomain=001.live_hls.zte.com"  
      }  
    ]  
  },  
  {  
    "group": "其他频道",  
    "list": [  
      {  
        "安徽卫视": "http://220.248.175.230:6610/001/2/ch00000090990000001024/index.m3u8?virtualDomain=001.live_hls.zte.com"  
      },  
      {  
        "北京卫视": "http://220.248.175.230:6610/001/2/ch00000090990000001025/index.m3u8?virtualDomain=001.live_hls.zte.com"  
      }  
    ]  
  },  
  {  
    "group": "高清频道",  
    "list": [  
      {  
        "CCTV1综合HD": "http://220.248.175.230:6610/001/2/ch00000090990000001075/index.m3u8?virtualDomain=001.live_hls.zte.com"  
      },  
      {  
        "湖南经视HD": "http://220.248.175.230:6610/001/2/ch00000090990000001080/index.m3u8?virtualDomain=001.live_hls.zte.com"  
      }  
    ]  
  },  
  {  
    "group": "央视频道",  
    "list": [  
      {  
        "CCTV1综合": "http://220.248.175.230:6610/001/2/ch00000090990000001075/index.m3u8?virtualDomain=001.live_hls.zte.com"  
      },  
      {  
        "CCTV证券": "http://220.248.175.230:6610/001/2/ch00000090990000001133/index.m3u8?virtualDomain=001.live_hls.zte.com"  
      }  
    ]  
  }  
]
复制代码


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值