Android TV開發(fā)總結(jié)(六)構(gòu)建一個TV app的直播節(jié)目實例

近年來,Android TV的迅速發(fā)展跟衅,傳統(tǒng)的有線電視受到較大的沖擊孵睬,在TV上用戶同樣也可以看到各個有線電視的直播頻道,相對于手機(jī)伶跷,這種直播節(jié)目掰读,體驗效果更佳,尤其是一樣賽事節(jié)目叭莫,大屏幕看得才夠痛快蹈集,還可以邀幾好友一起欣賞。今天將介紹構(gòu)建一個TV app的直播節(jié)目實例,此實例上傳到Github: https://github.com/hejunlin2013/LivePlayback 喜歡可以star雇初。Agenda如下:

  • 效果圖
  • 代碼實現(xiàn):
  • 主頁面:Recycleview對應(yīng)Adapater
  • 直播節(jié)目源
  • 播放器
  • 播放頁處理
  • 播放頁的播放panel:

先看下效果圖:主界面:
這里寫圖片描述
這里寫圖片描述

CCTV-1:
這里寫圖片描述
湖南衛(wèi)視:
這里寫圖片描述
CCTV-第一劇場:
這里寫圖片描述
CCTV-15(音樂):
這里寫圖片描述

CCTV-14(少兒):
這里寫圖片描述
CCTV-13(新聞):
這里寫圖片描述
CCTV-12(社會與法):
這里寫圖片描述
CCTV-11(戲曲):
這里寫圖片描述
CCTV-10(科教):
這里寫圖片描述
CCTV-9(紀(jì)錄):
這里寫圖片描述
CCTV-8(電視劼K痢):
這里寫圖片描述
CCTV-第一劇場:
這里寫圖片描述
CCTV-15:
這里寫圖片描述

代碼實現(xiàn):

  • 主頁面:Recycleview對應(yīng)adapater
  • 直播節(jié)目源
  • 播放器
  • 播放頁處理主頁面:
/*
 * Copyright (C) 2016 hejunlin <hejunlin2013@gmail.com>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
public class MainActivity extends Activity {

    private MetroViewBorderImpl mMetroViewBorderImpl;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mMetroViewBorderImpl = new MetroViewBorderImpl(this);
        mMetroViewBorderImpl.setBackgroundResource(R.drawable.border_color);
        loadRecyclerViewMenuItem();
    }

    private void loadRecyclerViewMenuItem() {
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.ry_menu_item);
        GridLayoutManager layoutManager = new GridLayoutManager(this, 1);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(layoutManager);
        recyclerView.setFocusable(false);
        mMetroViewBorderImpl.attachTo(recyclerView);
        createOptionItemData(recyclerView, R.layout.detail_menu_item);
    }

    private void createOptionItemData(RecyclerView recyclerView, int id) {
        OptionItemAdapter adapter = new OptionItemAdapter(this, id);
        recyclerView.setAdapter(adapter);
        recyclerView.scrollToPosition(0);
    }
}

播放頁:

/*
 * Copyright (C) 2016 hejunlin <hejunlin2013@gmail.com>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
public class LiveActivity extends Activity {

    private IjkVideoView mVideoView;
    private RelativeLayout mVideoViewLayout;
    private RelativeLayout mLoadingLayout;
    private TextView mLoadingText;
    private TextView mTextClock;
    private String mVideoUrl = "";
    private int mRetryTimes = 0;
    private static final int CONNECTION_TIMES = 5;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_live);
        mVideoUrl = getIntent().getStringExtra("url");
        mVideoView = (IjkVideoView) findViewById(R.id.videoview);
        mVideoViewLayout = (RelativeLayout) findViewById(R.id.fl_videoview);
        mLoadingLayout = (RelativeLayout) findViewById(R.id.rl_loading);
        mLoadingText = (TextView) findViewById(R.id.tv_load_msg);
        mTextClock = (TextView)findViewById(R.id.tv_time);
        mTextClock.setText(getDateFormate());
        mLoadingText.setText("節(jié)目加載中...");
        initVideo();
    }

    private String getDateFormate(){
        Calendar c = Calendar.getInstance();
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String formattedDate = df.format(c.getTime());
        return formattedDate;
    }

    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) {
                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(LiveActivity.this)
                            .setMessage("節(jié)目暫時不能播放")
                            .setPositiveButton(R.string.VideoView_error_button,
                                    new DialogInterface.OnClickListener() {
                                        public void onClick(DialogInterface dialog, int whichButton) {
                                            LiveActivity.this.finish();
                                        }
                                    })
                            .setCancelable(false)
                            .show();
                } else {
                    mVideoView.stopPlayback();
                    mVideoView.release(true);
                    mVideoView.setVideoURI(Uri.parse(mVideoUrl));
                }
                return false;
            }
        });

    }

    @Override
    protected void onResume() {
        super.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (!mVideoView.isBackgroundPlayEnabled()) {
            mVideoView.stopPlayback();
            mVideoView.release(true);
            mVideoView.stopBackgroundPlay();
        }
        IjkMediaPlayer.native_profileEnd();
    }

    public static void activityStart(Context context, String url) {
        Intent intent = new Intent(context, LiveActivity.class);
        intent.putExtra("url", url);
        context.startActivity(intent);
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
    }

}

播放器是用二次封裝的ijkplayer,從主頁面?zhèn)鱱rl到播放頁面抵皱,關(guān)才mediaplayer相關(guān)善榛,之前專門寫了專題分析,mediaplayer的狀態(tài)可參考《Android Multimedia框架總結(jié)(一)MediaPlayer介紹之狀態(tài)圖及生命周期》
第三方播放器典型特點就是另起一個mediaplayerservice呻畸,注意這是另外一個進(jìn)程米辐,為什么是另一個進(jìn)程,可參見我的文章:MediaPlayer的C/S模型侠坎。對于ijkplayer這個框架杀餐,因為做實例据途,才引入,不做評價叙甸,也不會去深究颖医,滿足基本播放需求就ok。市場上有很多第三方播放框架裆蒸,ijkplayer,vitamio,百度云播放等熔萧。

再看下播放頁的播放panel:

<?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:background="#22000000"
    android:orientation="vertical">

    <RelativeLayout
        android:id="@+id/fl_videoview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorBlack">

        <com.hejunlin.liveplayback.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.hejunlin.liveplayback.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 TV開發(fā)總結(jié)(六)構(gòu)建一個TV app的直播節(jié)目實例"
                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>
</RelativeLayout>

這里有幾個點要注意:

  • 為演示,并未對層級進(jìn)行使用FrameLayout,及viewstub,include等性能優(yōu)化相關(guān)的僚祷,在實際商用項目中佛致,建議寫xml文件,盡可能遵循過少的層級辙谜,高級標(biāo)簽及FrameLayout等技巧俺榆。
  • 所有的size切勿直接寫死,用 android:layout_marginTop="@dimen/dimen_20dp"表示装哆,string值統(tǒng)一寫到string.xml中罐脊,這些基本的規(guī)范,會讓你提高不少效率蜕琴。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末萍桌,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子奸绷,更是在濱河造成了極大的恐慌梗夸,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件号醉,死亡現(xiàn)場離奇詭異反症,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)畔派,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進(jìn)店門铅碍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人线椰,你說我怎么就攤上這事胞谈。” “怎么了憨愉?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵烦绳,是天一觀的道長。 經(jīng)常有香客問我配紫,道長径密,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任躺孝,我火速辦了婚禮享扔,結(jié)果婚禮上底桂,老公的妹妹穿的比我還像新娘。我一直安慰自己惧眠,他們只是感情好籽懦,可當(dāng)我...
    茶點故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著氛魁,像睡著了一般暮顺。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上呆盖,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天拖云,我揣著相機(jī)與錄音,去河邊找鬼应又。 笑死,一個胖子當(dāng)著我的面吹牛乏苦,可吹牛的內(nèi)容都是我干的株扛。 我是一名探鬼主播,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼汇荐,長吁一口氣:“原來是場噩夢啊……” “哼洞就!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起掀淘,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤旬蟋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后革娄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體倾贰,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年拦惋,在試婚紗的時候發(fā)現(xiàn)自己被綠了匆浙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡厕妖,死狀恐怖首尼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情言秸,我是刑警寧澤软能,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站举畸,受9級特大地震影響查排,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜俱恶,卻給世界環(huán)境...
    茶點故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一雹嗦、第九天 我趴在偏房一處隱蔽的房頂上張望范舀。 院中可真熱鬧,春花似錦了罪、人聲如沸锭环。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辅辩。三九已至,卻和暖如春娃圆,著一層夾襖步出監(jiān)牢的瞬間玫锋,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工讼呢, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留撩鹿,地道東北人。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓悦屏,卻偏偏與公主長得像节沦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子础爬,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,678評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,095評論 25 707
  • 前言 隨著音視頻領(lǐng)域的火熱甫贯,在很多領(lǐng)域(教育,游戲看蚜,娛樂叫搁,體育,跑步供炎,餐飲渴逻,音樂等)嘗試做音視頻直播/點播功能,那...
    passiontim閱讀 3,285評論 1 46
  • 公司要求做TV開發(fā)碱茁,就想著為那些一樣懵逼的人探探路裸卫,不喜勿噴!Eⅰ墓贿!TV開發(fā)最重要也是最麻煩的就是焦點問題,網(wǎng)上大多...
    迷失在你de流年閱讀 13,123評論 11 59
  • 要考試?yán)豺寻保€有不到六天聋袋,媽呀媽呀 遇見個孕婦,流過好幾次了穴吹,還沒領(lǐng)證幽勒,咋么不好好對自己的, 女生港令,真的要對自己好一...
    肥肥兔兔兔閱讀 167評論 0 0
  • 一眼望得到盡頭的人生击吱,找不到出口的生活里,沉重是真實的沉重遥昧,孤獨是真實的孤獨覆醇。看韓國電影《男與女》炭臭,品澀味人生永脓。 ...
    蘆瘋子閱讀 616評論 11 9