多路投屏与全屏模式切换

1、单路投屏默认为全屏
2、第二路投屏时,全屏切为多分屏
3、双击切换多分屏/全屏模式
pull/107/head
xufulong 6 years ago
parent df8570f873
commit 3aa00909bd
  1. 3
      OnLive/src/main/AndroidManifest.xml
  2. 434
      OnLive/src/main/java/com/frank/living/activity/MultiScreenActivity.java
  3. 10
      OnLive/src/main/java/com/frank/living/constant/Constants.java
  4. 59
      OnLive/src/main/java/com/frank/living/listener/OnDoubleClickListener.java
  5. 20
      OnLive/src/main/java/com/frank/living/widget/IjkVideoView.java
  6. 2
      README.md

@ -29,11 +29,12 @@
<activity
android:name="com.frank.living.activity.MultiScreenActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="@string/app_name"
android:screenOrientation="landscape"
android:theme="@style/FullscreenTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

@ -1,92 +1,425 @@
package com.frank.living.activity;
import android.os.Bundle;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.PowerManager;
import android.os.Process;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import com.frank.living.R;
import com.frank.living.constant.Constants;
import com.frank.living.listener.OnDoubleClickListener;
import com.frank.living.listener.IjkPlayerListener;
import com.frank.living.widget.IjkVideoView;
import java.util.HashMap;
import java.util.TreeMap;
import tv.danmaku.ijk.media.player.IMediaPlayer;
import tv.danmaku.ijk.media.player.IjkMediaPlayer;
public class MultiScreenActivity extends AppCompatActivity implements IjkPlayerListener{
/**
* 多路投屏直播
* Created by xufulong on 2019/01/04
*/
public class MultiScreenActivity extends AppCompatActivity{
private final static String TAG = MultiScreenActivity.class.getSimpleName();
private static final String TAG = MultiScreenActivity.class.getSimpleName();
private IjkMediaPlayer ijkMediaPlayer;
private IjkVideoView mVideoView1;
private IjkVideoView mVideoView2;
private IjkVideoView mVideoView3;
private IjkVideoView mVideoView4;
private final static String url1 = "rtsp://184.72.239.149/vod/mp4://BigBuckBunny_175k.mov";
private final static String url2 = "http://ivi.bupt.edu.cn/hls/cctv5phd.m3u8";
private final static String url3 = "rtmp://58.200.131.2:1935/livetv/hunantv";
private final static String url4 = "http://ivi.bupt.edu.cn/hls/cctv1hd.m3u8";
private View divider1;
private View divider2;
private IMediaPlayer ijkPlayer;
private CustomReceiver customReceiver;
private String ipAddress;
//四分屏模式还是全屏模式
private boolean isMultiScreen;
//保存客户端ip与通道数对应关系
private HashMap<String, Integer> clientMap = new HashMap<>();
//保存是否投屏与通道数对应关系
private TreeMap<Integer, Boolean> channelMap = new TreeMap<>();
private String url = "";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_multi_screen);
init();
parseIntent();
registerBroadcast();
}
initView();
initListener();
setupView();
wakeUp();
private void init(){
IjkMediaPlayer.loadLibrariesOnce(null);
IjkMediaPlayer.native_profileBegin("libijkplayer.so");
}
private void initView(){
mVideoView1 = (IjkVideoView) findViewById(R.id.video_view1);
mVideoView1.setIjkPlayerListener(this);
mVideoView1.setVideoPath(url1);
mVideoView2 = (IjkVideoView) findViewById(R.id.video_view2);
mVideoView3 = (IjkVideoView) findViewById(R.id.video_view3);
mVideoView4 = (IjkVideoView) findViewById(R.id.video_view4);
divider1 = findViewById(R.id.divider1);
divider2 = findViewById(R.id.divider2);
}
private void initListener(){
mVideoView1.setOnTouchListener(new OnDoubleClickListener(new OnDoubleClickListener.OnDoubleClick() {
@Override
public void onDouble() {
changeScreenMode(1);
}
}));
mVideoView2.setOnTouchListener(new OnDoubleClickListener(new OnDoubleClickListener.OnDoubleClick() {
@Override
public void onDouble() {
changeScreenMode(2);
}
}));
mVideoView3.setOnTouchListener(new OnDoubleClickListener(new OnDoubleClickListener.OnDoubleClick() {
@Override
public void onDouble() {
changeScreenMode(3);
}
}));
mVideoView4.setOnTouchListener(new OnDoubleClickListener(new OnDoubleClickListener.OnDoubleClick() {
@Override
public void onDouble() {
changeScreenMode(4);
}
}));
}
private void setupView(){
//第一路投屏默认全屏
enterFullScreen(1);
mVideoView1.setVideoPath(url);
mVideoView1.setIjkPlayerListener(new IjkPlayerListener() {
@Override
public void onIjkPlayer(IjkMediaPlayer ijkMediaPlayer) {
//设置播放器option
setOptions(ijkMediaPlayer);
}
});
mVideoView1.setOnPreparedListener(new IMediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(IMediaPlayer iMediaPlayer) {
ijkPlayer = iMediaPlayer;
}
});
mVideoView1.start();
}
mVideoView2 = (IjkVideoView) findViewById(R.id.video_view2);
mVideoView2.setVideoPath(url2);
mVideoView2.start();
/**
* 外部传进来的数据:URL
*/
private void parseIntent(){
for (int i=1; i<=4; i++){
channelMap.put(i, false);
}
clientMap.put(ipAddress, 1);
channelMap.put(1, true);
mVideoView3 = (IjkVideoView) findViewById(R.id.video_view3);
mVideoView3.setVideoPath(url3);
mVideoView3.start();
Intent intent = getIntent();
String url = intent.getStringExtra("url");
if(!TextUtils.isEmpty(url)){
this.url = url;
}
String ip = intent.getStringExtra("ip");
if (!TextUtils.isEmpty(ip)){
ipAddress = ip;
}
}
mVideoView4 = (IjkVideoView) findViewById(R.id.video_view4);
mVideoView4.setVideoPath(url4);
mVideoView4.start();
//唤醒屏幕
private void wakeUp(){
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (powerManager == null)
return;
PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK
|PowerManager.ACQUIRE_CAUSES_WAKEUP, TAG);
wakeLock.acquire(1000);
wakeLock.release();
}
private void initOptions(){
if (ijkMediaPlayer == null)
/**
* 配置播放器参数项
*
*/
private void setOptions(IjkMediaPlayer ijkPlayer){
if (ijkPlayer == null)
return;
Log.e(TAG, "initOptions");
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "fast", 1);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "probesize", 200);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "flush_packets", 1);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", 0);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", 1);
//0:代表关闭 1:代表开启
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 0);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-auto-rotate", 0);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-handle-resolution-change", 0);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max-buffer-size", 0);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "min-frames", 2);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max_cached_duration", 30);
//input buffer:don't limit the input buffer size (useful with realtime streams)
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", 1);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "fflags", "nobuffer");
//ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "rtsp_transport", "tcp");
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzedmaxduration", 100);
ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "fast", 1);//不额外优化
ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "probesize", 200);
ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "flush_packets", 1);
ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", 0);//是否开启缓冲
ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", 1);
//0:代表关闭,1:代表开启
ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 0);//开启硬解
ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-auto-rotate", 0);//自动旋屏
ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-handle-resolution-change", 0);//处理分辨率变化
ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max-buffer-size", 0);//最大缓存数
ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "min-frames", 2);//默认最小帧数2
ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max_cached_duration", 30);//最大缓存时长
ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", 1);//是否限制输入缓存数
ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "fflags", "nobuffer");
//设置播放前的最大探测时间,分析码流时长:默认1024*1000
ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzedmaxduration", 100);
//ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "rtsp_transport", "tcp");//tcp传输数据
// 音视频同步基线,0-音频为基准,1-视频为基准,3-系统时间戳为基准
ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "av_sync_type", 0);
}
@Override
public void onIjkPlayer(IjkMediaPlayer ijkMediaPlayer) {
this.ijkMediaPlayer = ijkMediaPlayer;
initOptions();
/**
* 注册广播
*/
private void registerBroadcast(){
customReceiver = new CustomReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Constants.ACTION_CLIENT_REMOVE);
intentFilter.addAction(Constants.ACTION_CLIENT_ADD);
registerReceiver(customReceiver, intentFilter);
}
/**
* 注销广播
*/
private void unregisterBroadcast(){
if(customReceiver != null){
unregisterReceiver(customReceiver);
customReceiver = null;
}
}
/**
* 自定义广播接收器
*/
private class CustomReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(action == null)
return;
Log.e(TAG, "onReceive=" + action);
switch (action){
case Constants.ACTION_CLIENT_REMOVE: //移除投屏
int num = intent.getIntExtra("clientNum", 0);
if (num == 0){
Process.killProcess(Process.myPid());
}else if (num > 0){
String ipAddress = intent.getStringExtra("ipAddress");
int target = clientMap.get(ipAddress);
removeClient(target);
clientMap.remove(ipAddress);
channelMap.put(target, false);
//TODO:多屏变为单屏时,自动切换为全屏
// if (num == 1){
// int castingChannel = getCastingChannel();
// enterFullScreen(castingChannel);
// }
}
break;
case Constants.ACTION_CLIENT_ADD://新增投屏
int clientNum = intent.getIntExtra("clientNum", 0);
String otherUrl = intent.getStringExtra("url");
String ipAddress = intent.getStringExtra("ip");
//选择空闲通道
int channel = selectIdleChannel(clientNum);
clientMap.put(ipAddress, channel);
channelMap.put(channel, true);
addClient(channel, otherUrl);
//TODO:单屏变为两路投屏时,自动切换为多屏模式
if (clientNum == 2){
exitFullScreen();
}
break;
default:
break;
}
}
}
/**
* 选择空闲通道
* @param clientNum clientNum
* @return idleChannel
*/
private int selectIdleChannel(int clientNum){
for (int channel = 1; channel < clientNum; channel++){
if (!channelMap.get(channel)){
return channel;
}
}
return clientNum;
}
/**
* 获取当前投屏通道
* @return idleChannel
*/
private int getCastingChannel(){
for (int channel = 1; channel <= 4; channel++){
if (channelMap.get(channel)){
return channel;
}
}
return 0;
}
/**
* 添加客户端
* @param target target
* @param clientUrl clientUrl
*/
private void addClient(int target, String clientUrl){
switch (target){
case 1:
mVideoView1.setVisibility(View.VISIBLE);
mVideoView1.setVideoPath(clientUrl);
mVideoView1.start();
break;
case 2:
mVideoView2.setVisibility(View.VISIBLE);
mVideoView2.setVideoPath(clientUrl);
mVideoView2.start();
break;
case 3:
mVideoView3.setVisibility(View.VISIBLE);
mVideoView3.setVideoPath(clientUrl);
mVideoView3.start();
break;
case 4:
mVideoView4.setVisibility(View.VISIBLE);
mVideoView4.setVideoPath(clientUrl);
mVideoView4.start();
break;
default:
break;
}
}
/**
* 移除客户端
* @param target target
*/
private void removeClient(int target){
switch (target){
case 1:
mVideoView1.stopPlayback();
mVideoView1.setVisibility(View.GONE);
break;
case 2:
mVideoView2.stopPlayback();
mVideoView2.setVisibility(View.GONE);
break;
case 3:
mVideoView3.stopPlayback();
mVideoView3.setVisibility(View.GONE);
break;
case 4:
mVideoView4.stopPlayback();
mVideoView4.setVisibility(View.GONE);
break;
default:
break;
}
}
/**
* 隐藏分割线
*/
private void hideDivider(){
divider1.setVisibility(View.GONE);
divider2.setVisibility(View.GONE);
}
/**
* 显示分割线
*/
private void showDivider(){
divider1.setVisibility(View.VISIBLE);
divider2.setVisibility(View.VISIBLE);
}
/**
* 进入全屏模式
* @param channel channel
*/
private void enterFullScreen(int channel){
hideDivider();
switch (channel){
case 1:
mVideoView1.setVisibility(View.VISIBLE);
mVideoView2.setVisibility(View.GONE);
mVideoView3.setVisibility(View.GONE);
mVideoView4.setVisibility(View.GONE);
break;
case 2:
mVideoView1.setVisibility(View.GONE);
mVideoView2.setVisibility(View.VISIBLE);
mVideoView3.setVisibility(View.GONE);
mVideoView4.setVisibility(View.GONE);
break;
case 3:
mVideoView1.setVisibility(View.GONE);
mVideoView2.setVisibility(View.GONE);
mVideoView3.setVisibility(View.VISIBLE);
mVideoView4.setVisibility(View.GONE);
break;
case 4:
mVideoView1.setVisibility(View.GONE);
mVideoView2.setVisibility(View.GONE);
mVideoView3.setVisibility(View.GONE);
mVideoView4.setVisibility(View.VISIBLE);
break;
default:
break;
}
}
/**
* 退出全屏模式
*/
private void exitFullScreen(){
showDivider();
mVideoView1.setVisibility(View.VISIBLE);
mVideoView2.setVisibility(View.VISIBLE);
mVideoView3.setVisibility(View.VISIBLE);
mVideoView4.setVisibility(View.VISIBLE);
}
/**
* 切换分屏模式
* @param channel channel
*/
private void changeScreenMode(int channel){
isMultiScreen = !isMultiScreen;
if (isMultiScreen){
enterFullScreen(channel);
}else {
exitFullScreen();
}
}
@Override
protected void onStop() {
super.onStop();
protected void onDestroy() {
super.onDestroy();
unregisterBroadcast();
mVideoView1.stopPlayback();
mVideoView1.release(true);
@ -101,3 +434,4 @@ public class MultiScreenActivity extends AppCompatActivity implements IjkPlayerL
}
}

@ -0,0 +1,10 @@
package com.frank.living.constant;
public class Constants {
//新增投屏广播
public final static String ACTION_CLIENT_ADD = "action.client.ADD";
//移除投屏广播
public final static String ACTION_CLIENT_REMOVE = "action.client.REMOVE";
}

@ -0,0 +1,59 @@
package com.frank.living.listener;
import android.view.MotionEvent;
import android.view.View;
/**
* 双击事件监听
* Created by xufulong on 2019/2/27.
*/
public class OnDoubleClickListener implements View.OnTouchListener {
private final static long DOUBLE_TIME = 500;
private long firstClick;
private long secondClick;
private int clickCount;
private OnDoubleClick onDoubleClick;
public OnDoubleClickListener(OnDoubleClick onDoubleClick){
this.onDoubleClick = onDoubleClick;
}
public interface OnDoubleClick{
void onDouble();
}
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP){
return v.performClick();
}else if (event.getAction() == MotionEvent.ACTION_DOWN){
clickCount ++;
if (clickCount == 1){
firstClick = System.currentTimeMillis();
}else if (clickCount == 2){
secondClick = System.currentTimeMillis();
if (secondClick - firstClick <= DOUBLE_TIME){
firstClick = 0;
clickCount = 1;
if (onDoubleClick != null){
onDoubleClick.onDouble();
}
}else {
firstClick = secondClick;
clickCount = 1;
}
secondClick = 0;
}else {
clickCount = 0;
firstClick = 0;
secondClick = 0;
}
}
return false;
}
}

@ -1182,23 +1182,9 @@ public class IjkVideoView extends FrameLayout implements MediaController.MediaPl
return language;
}
public ITrackInfo[] getTrackInfo() {
if (mMediaPlayer == null)
return null;
return mMediaPlayer.getTrackInfo();
}
public void selectTrack(int stream) {
MediaPlayerCompat.selectTrack(mMediaPlayer, stream);
}
public void deselectTrack(int stream) {
MediaPlayerCompat.deselectTrack(mMediaPlayer, stream);
}
public int getSelectedTrack(int trackType) {
return MediaPlayerCompat.getSelectedTrack(mMediaPlayer, trackType);
@Override
public boolean performClick() {
return super.performClick();
}
}

@ -30,6 +30,8 @@ android端基于FFmpeg库的使用。<br>
- #### OpenGL+GPUImage滤镜
- #### FFmpeg的AVFilter滤镜
- #### IjkPlayer的RTSP超低延时直播
- #### IjkPlayer的RTSP多路投屏直播
左边是ffplay客户端拉流播放,中间是web网页播放:

Loading…
Cancel
Save