本文实现一款最多支持4路分屏、每屏可独立选节目、独立播放控制的智能电视应用,基于Android TV平台,采用ExoPlayer播放器,适配遥控器、多路解码、音频管理等核心问题,附完整可复用代码。 一、核心需求 1. 支持1/2/4屏切换,默认2x2四分屏2. 每个分屏独立播放、独立选台3. 遥控器焦点精准控制4. 仅当前聚焦分屏出声5. 支持 m3u8、RTMP、FLV 等直播流6. 适配低端电视,防卡顿、防崩溃 二、技术选型 - 平台:Android 5.0± 播放器:ExoPlayer2- 架构:MVVM- 界面:自定义ViewGroup + Leanback 三、分屏布局核心代码
java
public enum ScreenMode { FULL_SCREEN(1), TWO_SCREEN_HORIZONTAL(2), FOUR_SCREEN_GRID(4); public final int screenCount; ScreenMode(int screenCount) { this.screenCount = screenCount; }} java
public class MultiScreenLayout extends ViewGroup { private ScreenMode screenMode = ScreenMode.FOUR_SCREEN_GRID; public MultiScreenLayout(Context context, AttributeSet attrs) { super(context, attrs); setChildrenDrawingOrderEnabled(true); } public void setScreenMode(ScreenMode screenMode) { this.screenMode = screenMode; requestLayout(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int w = MeasureSpec.getSize(widthMeasureSpec); int h = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(w, h); for (int i=0; i<getChildCount(); i++) { View child = getChildAt(i); Rect r = getChildRect(i, w, h); child.measure( MeasureSpec.makeMeasureSpec(r.width(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(r.height(), MeasureSpec.EXACTLY) ); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int w = r-l; int h = b-t; for (int i=0; i<getChildCount(); i++) { View child = getChildAt(i); Rect rect = getChildRect(i, w, h); child.layout(rect.left, rect.top, rect.right, rect.bottom); } } private Rect getChildRect(int index, int w, int h) { if (screenMode == ScreenMode.FOUR_SCREEN_GRID) { int gw = w/2; int gh = h/2; switch (index) { case 0: return new Rect(0,0,gw,gh); case 1: return new Rect(gw,0,w,gh); case 2: return new Rect(0,gh,gw,h); case 3: return new Rect(gw,gh,w,h); default: return new Rect(0,0,w,h); } } return new Rect(0,0,w,h); } @Override public View focusSearch(View focused, int dir) { int curr = indexOfChild(focused); if (curr <0) return super.focusSearch(focused, dir); int next = curr; if (screenMode == ScreenMode.FOUR_SCREEN_GRID) { switch (dir) { case FOCUS_LEFT: next = (curr1?0:curr3?2:curr); break; case FOCUS_RIGHT: next = (curr0?1:curr2?3:curr); break; case FOCUS_UP: next = (curr2?0:curr3?1:curr); break; case FOCUS_DOWN: next = (curr0?2:curr1?3:curr); break; } } if (next != curr) { MultiPlayerManager.getInstance().switchFocusScreen(next); return getChildAt(next); } return super.focusSearch(focused, dir); }}
四、多播放器管理
java
public class MultiPlayerManager { private static MultiPlayerManager instance; private SparseArray playerMap = new SparseArray<>(); private int focusIndex = 0; private MultiPlayerManager(){} public static MultiPlayerManager getInstance() { if (instance == null) instance = new MultiPlayerManager(); return instance; } public void addPlayer(int index, Context ctx, PlayerView pv) { PlayerWrapper pw = new PlayerWrapper(ctx, pv); playerMap.put(index, pw); if (index == 0) { pw.setMuted(false); focusIndex = 0; } } public void switchFocusScreen(int index) { PlayerWrapper old = playerMap.get(focusIndex); if (old != null) old.setMuted(true); PlayerWrapper now = playerMap.get(index); if (now != null) now.setMuted(false); focusIndex = index; } public PlayerWrapper getCurrentFocusPlayer() { return playerMap.get(focusIndex); } public void releaseAll() { for (int i=0; i<playerMap.size(); i++) { playerMap.valueAt(i).release(); } playerMap.clear(); }} java
public class PlayerWrapper { private ExoPlayer exoPlayer; private PlayerView playerView; private boolean isMuted; public PlayerWrapper(Context ctx, PlayerView pv) { this.playerView = pv; exoPlayer = new ExoPlayer.Builder(ctx).build(); pv.setPlayer(exoPlayer); pv.setUseController(false); setMuted(true); } public void playUrl(String url) { MediaItem item = MediaItem.fromUri(url); exoPlayer.setMediaItem(item); exoPlayer.prepare(); exoPlayer.play(); } public void setMuted(boolean mute) { isMuted = mute; exoPlayer.setVolume(mute ? 0:1); } public void release() { exoPlayer.release(); }}
五、主界面
java
public class MainActivity extends AppCompatActivity { private MultiScreenLayout layout; private MultiPlayerManager manager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); layout = findViewById(R.id.multi_screen); manager = MultiPlayerManager.getInstance(); initFourScreen(); } private void initFourScreen() { manager.releaseAll(); layout.removeAllViews(); layout.setScreenMode(ScreenMode.FOUR_SCREEN_GRID); for (int i=0; i<4; i++) { PlayerView pv = new PlayerView(this); layout.addView(pv); manager.addPlayer(i, this, pv); } } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { // 打开选台界面 return true; } return super.onKeyDown(keyCode, event); } @Override protected void onDestroy() { super.onDestroy(); manager.releaseAll(); }}
六、常见问题
- 电视花屏、卡顿:检测硬件解码能力,低端机自动降为2屏
- 声音混乱:只允许聚焦屏出声
- 遥控器焦点乱:重写focusSearch
- 内存崩溃:及时release播放器
七、权限
plaintext