diff --git a/OnLive/src/main/AndroidManifest.xml b/OnLive/src/main/AndroidManifest.xml index c420dc5..4b6de5d 100644 --- a/OnLive/src/main/AndroidManifest.xml +++ b/OnLive/src/main/AndroidManifest.xml @@ -29,11 +29,12 @@ - + diff --git a/OnLive/src/main/java/com/frank/living/activity/MultiScreenActivity.java b/OnLive/src/main/java/com/frank/living/activity/MultiScreenActivity.java index b42d277..316b2e9 100644 --- a/OnLive/src/main/java/com/frank/living/activity/MultiScreenActivity.java +++ b/OnLive/src/main/java/com/frank/living/activity/MultiScreenActivity.java @@ -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 clientMap = new HashMap<>(); + //保存是否投屏与通道数对应关系 + private TreeMap 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 } } + diff --git a/OnLive/src/main/java/com/frank/living/constant/Constants.java b/OnLive/src/main/java/com/frank/living/constant/Constants.java new file mode 100644 index 0000000..4369c7f --- /dev/null +++ b/OnLive/src/main/java/com/frank/living/constant/Constants.java @@ -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"; + +} diff --git a/OnLive/src/main/java/com/frank/living/listener/OnDoubleClickListener.java b/OnLive/src/main/java/com/frank/living/listener/OnDoubleClickListener.java new file mode 100644 index 0000000..60b7850 --- /dev/null +++ b/OnLive/src/main/java/com/frank/living/listener/OnDoubleClickListener.java @@ -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; + } + +} diff --git a/OnLive/src/main/java/com/frank/living/widget/IjkVideoView.java b/OnLive/src/main/java/com/frank/living/widget/IjkVideoView.java index 8e81e7f..f451c7a 100644 --- a/OnLive/src/main/java/com/frank/living/widget/IjkVideoView.java +++ b/OnLive/src/main/java/com/frank/living/widget/IjkVideoView.java @@ -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(); } } diff --git a/README.md b/README.md index 0d38d19..4045db5 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,8 @@ android端基于FFmpeg库的使用。
- #### OpenGL+GPUImage滤镜 - #### FFmpeg的AVFilter滤镜 +- #### IjkPlayer的RTSP超低延时直播 +- #### IjkPlayer的RTSP多路投屏直播 左边是ffplay客户端拉流播放,中间是web网页播放: