[*] 依赖
This commit is contained in:
@@ -9,14 +9,19 @@ import android.os.Message;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.acgist.taoyao.client.databinding.ActivityMainBinding;
|
||||
import com.acgist.taoyao.config.Config;
|
||||
import com.acgist.taoyao.media.MediaManager;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@@ -25,7 +30,7 @@ import java.io.Serializable;
|
||||
*
|
||||
* @author acgist
|
||||
*/
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
public class MainActivity extends AppCompatActivity implements Serializable {
|
||||
|
||||
private MainHandler mainHandler;
|
||||
private ActivityMainBinding binding;
|
||||
@@ -96,6 +101,9 @@ public class MainActivity extends AppCompatActivity {
|
||||
if(!allGranted) {
|
||||
Toast.makeText(this, "授权失败", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
MediaManager.getInstance().init(this.getApplicationContext());
|
||||
MediaManager.getInstance().initAudio();
|
||||
MediaManager.getInstance().initVideo();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,7 +119,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
Log.i(MainActivity.class.getSimpleName(), "拉起媒体服务");
|
||||
final Intent intent = new Intent(this, MediaService.class);
|
||||
if(this.mainHandler == null) {
|
||||
this.mainHandler = new MainHandler();
|
||||
this.mainHandler = new MainHandler(this);
|
||||
}
|
||||
intent.setAction(MediaService.Action.CONNECT.name());
|
||||
intent.putExtra("mainHandler", this.mainHandler);
|
||||
@@ -136,14 +144,52 @@ public class MainActivity extends AppCompatActivity {
|
||||
*
|
||||
* @author acgist
|
||||
*/
|
||||
private static class MainHandler extends Handler implements Serializable {
|
||||
public static class MainHandler extends Handler implements Serializable {
|
||||
|
||||
private final MainActivity mainActivity;
|
||||
|
||||
public MainHandler(MainActivity mainActivity) {
|
||||
this.mainActivity = mainActivity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(@NonNull Message message) {
|
||||
super.handleMessage(message);
|
||||
Log.d(MainActivity.class.getSimpleName(), "Handler消息:" + message.what + " - " + message.obj);
|
||||
Log.d(MainHandler.class.getSimpleName(), "Handler消息:" + message.what + " - " + message.obj);
|
||||
switch(message.what) {
|
||||
case Config.WHAT_SCREEN_CAPTURE -> this.mainActivity.screenCapture(message);
|
||||
case Config.WHAT_NEW_CLIENT_VIDEO -> this.mainActivity.newClientVideo(message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 屏幕捕获
|
||||
*
|
||||
* @param message 消息
|
||||
*/
|
||||
private void screenCapture(Message message) {
|
||||
final ActivityResultLauncher<Intent> activityResultLauncher = this.registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {
|
||||
if(result.getResultCode() == RESULT_OK) {
|
||||
Log.i(MediaManager.class.getSimpleName(), "开始屏幕捕获");
|
||||
message.getCallback().run();
|
||||
} else {
|
||||
Log.w(MainActivity.class.getSimpleName(), "屏幕捕获失败:" + result.getResultCode());
|
||||
}
|
||||
}
|
||||
);
|
||||
activityResultLauncher.launch((Intent) message.obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新建用户视频
|
||||
*
|
||||
* @param message 消息
|
||||
*/
|
||||
private void newClientVideo(Message message) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,6 +27,7 @@ public class MediaService extends Service {
|
||||
|
||||
static {
|
||||
System.loadLibrary("taoyao");
|
||||
System.loadLibrary("jingle_peerconnection_so");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,7 +24,8 @@ android {
|
||||
"-DLIBWEBRTC_BINARY_PATH=" + WEBRTC_LIB_PATH,
|
||||
"-DMEDIASOUPCLIENT_BUILD_TESTS=OFF",
|
||||
"-DMEDIASOUPCLIENT_LOG_TRACE=OFF",
|
||||
"-DMEDIASOUPCLIENT_LOG_DEV=OFF"
|
||||
"-DMEDIASOUPCLIENT_LOG_DEV=OFF",
|
||||
"-DANDROID_STL=c++_shared"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,10 +45,15 @@ android {
|
||||
version '3.22.1'
|
||||
}
|
||||
}
|
||||
sourceSets {
|
||||
main {
|
||||
jniLibs.srcDirs = ["libs"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.a', '*.so', '*.jar'])
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'org.apache.commons:commons-lang3:3.12.0'
|
||||
implementation 'org.apache.commons:commons-collections4:4.4'
|
||||
implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.2'
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,19 @@
|
||||
package com.acgist.taoyao.config;
|
||||
|
||||
/**
|
||||
* 配置
|
||||
*
|
||||
* @author acgist
|
||||
*/
|
||||
public class Config {
|
||||
|
||||
/**
|
||||
* 屏幕捕获
|
||||
*/
|
||||
public static final int WHAT_SCREEN_CAPTURE = 1000;
|
||||
/**
|
||||
* 新建用户视频
|
||||
*/
|
||||
public static final int WHAT_NEW_CLIENT_VIDEO = 1001;
|
||||
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
package com.acgist.taoyao.media;
|
||||
|
||||
import java.util.logging.Handler;
|
||||
|
||||
/**
|
||||
* 房间终端
|
||||
* 使用NDK + Mediasoup实现多人会话
|
||||
* 房间本地终端
|
||||
*
|
||||
* @author acgist
|
||||
*/
|
||||
public class LocalClient {
|
||||
public class LocalClient extends RoomClient {
|
||||
|
||||
public LocalClient(String name, String clientId, Handler handler) {
|
||||
super(name, clientId, handler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
package com.acgist.taoyao.media;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.media.projection.MediaProjection;
|
||||
import android.media.projection.MediaProjectionManager;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
|
||||
import com.acgist.taoyao.config.Config;
|
||||
|
||||
import org.webrtc.AudioSource;
|
||||
import org.webrtc.AudioTrack;
|
||||
import org.webrtc.Camera2Enumerator;
|
||||
@@ -12,6 +19,7 @@ import org.webrtc.EglBase;
|
||||
import org.webrtc.MediaConstraints;
|
||||
import org.webrtc.MediaStream;
|
||||
import org.webrtc.PeerConnectionFactory;
|
||||
import org.webrtc.ScreenCapturerAndroid;
|
||||
import org.webrtc.SurfaceTextureHelper;
|
||||
import org.webrtc.SurfaceViewRenderer;
|
||||
import org.webrtc.VideoCapturer;
|
||||
@@ -36,6 +44,8 @@ public class MediaManager {
|
||||
*/
|
||||
public enum Type {
|
||||
|
||||
// 文件共享
|
||||
FILE,
|
||||
// 后置摄像头
|
||||
BACK,
|
||||
// 前置摄像头
|
||||
@@ -43,12 +53,33 @@ public class MediaManager {
|
||||
// 屏幕共享
|
||||
SCREEN;
|
||||
|
||||
/**
|
||||
* @return 是否摄像头
|
||||
*/
|
||||
public boolean isCamera() {
|
||||
return this == BACK || this == FRONT;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private static final MediaManager INSTANCE = new MediaManager();
|
||||
|
||||
private MediaManager() {
|
||||
}
|
||||
|
||||
public static final MediaManager getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频类型
|
||||
*/
|
||||
private Type type;
|
||||
/**
|
||||
* Handler
|
||||
*/
|
||||
private Handler handler;
|
||||
/**
|
||||
* 上下文
|
||||
*/
|
||||
@@ -58,21 +89,16 @@ public class MediaManager {
|
||||
*/
|
||||
private MediaStream mediaStream;
|
||||
/**
|
||||
* 屏幕捕获
|
||||
* 视频捕获
|
||||
* FileVideoCapturer
|
||||
* CameraVideoCapturer
|
||||
* ScreenCapturerAndroid
|
||||
*/
|
||||
private VideoCapturer videoCapturer;
|
||||
/**
|
||||
* 摄像头捕获
|
||||
*/
|
||||
private CameraVideoCapturer cameraVideoCapturer;
|
||||
/**
|
||||
* Peer连接工厂
|
||||
*/
|
||||
private PeerConnectionFactory peerConnectionFactory;
|
||||
/**
|
||||
* 预览
|
||||
*/
|
||||
private SurfaceViewRenderer previewView;
|
||||
|
||||
static {
|
||||
// 设置采样
|
||||
@@ -85,8 +111,17 @@ public class MediaManager {
|
||||
|
||||
/**
|
||||
* 加载媒体流
|
||||
*
|
||||
* @param context 上下文
|
||||
*/
|
||||
public void init() {
|
||||
public void init(Context context) {
|
||||
this.type = Type.BACK;
|
||||
this.context = context;
|
||||
PeerConnectionFactory.initialize(
|
||||
PeerConnectionFactory.InitializationOptions.builder(this.context)
|
||||
.setEnableInternalTracer(true)
|
||||
.createInitializationOptions()
|
||||
);
|
||||
this.peerConnectionFactory = PeerConnectionFactory.builder().createPeerConnectionFactory();
|
||||
this.mediaStream = this.peerConnectionFactory.createLocalMediaStream("ARDAMS");
|
||||
}
|
||||
@@ -104,11 +139,10 @@ public class MediaManager {
|
||||
}
|
||||
this.type = type;
|
||||
Log.i(MediaManager.class.getSimpleName(), "设置视频来源:" + type);
|
||||
if(this.type == Type.SCREEN || type == Type.SCREEN) {
|
||||
this.initVideo();
|
||||
} else {
|
||||
if(this.type.isCamera() && type.isCamera()) {
|
||||
// TODO:测试是否需要完全重置
|
||||
this.cameraVideoCapturer.switchCamera(new CameraVideoCapturer.CameraSwitchHandler() {
|
||||
final CameraVideoCapturer cameraVideoCapturer = (CameraVideoCapturer) this.videoCapturer;
|
||||
cameraVideoCapturer.switchCamera(new CameraVideoCapturer.CameraSwitchHandler() {
|
||||
@Override
|
||||
public void onCameraSwitchDone(boolean success) {
|
||||
}
|
||||
@@ -116,13 +150,15 @@ public class MediaManager {
|
||||
public void onCameraSwitchError(String message) {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.initVideo();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载音频
|
||||
*/
|
||||
private void initAudio() {
|
||||
public void initAudio() {
|
||||
// 关闭音频
|
||||
this.closeAudioTrack();
|
||||
// 加载音频
|
||||
@@ -137,57 +173,68 @@ public class MediaManager {
|
||||
/**
|
||||
* 加载视频
|
||||
*/
|
||||
private void initVideo() {
|
||||
public void initVideo() {
|
||||
this.closeVideoTrack();
|
||||
if(this.cameraVideoCapturer != null) {
|
||||
this.cameraVideoCapturer.dispose();
|
||||
if(this.videoCapturer != null) {
|
||||
this.videoCapturer.dispose();
|
||||
}
|
||||
final CameraEnumerator cameraEnumerator = new Camera2Enumerator(this.context);
|
||||
final String[] names = cameraEnumerator.getDeviceNames();
|
||||
for(String name : names) {
|
||||
if(this.type == Type.FRONT && cameraEnumerator.isFrontFacing(name)) {
|
||||
Log.i(MediaManager.class.getSimpleName(), "加载前置摄像头:" + name);
|
||||
this.cameraVideoCapturer = cameraEnumerator.createCapturer(name, new MediaCameraEventsHandler());
|
||||
} else if(this.type == Type.BACK && cameraEnumerator.isBackFacing(name)) {
|
||||
Log.i(MediaManager.class.getSimpleName(), "加载后置摄像头:" + name);
|
||||
this.cameraVideoCapturer = cameraEnumerator.createCapturer(name, new MediaCameraEventsHandler());
|
||||
} else {
|
||||
Log.i(MediaManager.class.getSimpleName(), "忽略摄像头:" + name);
|
||||
if(this.type.isCamera()) {
|
||||
final CameraEnumerator cameraEnumerator = new Camera2Enumerator(this.context);
|
||||
final String[] names = cameraEnumerator.getDeviceNames();
|
||||
for(String name : names) {
|
||||
if(this.type == Type.FRONT && cameraEnumerator.isFrontFacing(name)) {
|
||||
Log.i(MediaManager.class.getSimpleName(), "加载视频(前置摄像头):" + name);
|
||||
this.videoCapturer = cameraEnumerator.createCapturer(name, new MediaCameraEventsHandler());
|
||||
} else if(this.type == Type.BACK && cameraEnumerator.isBackFacing(name)) {
|
||||
Log.i(MediaManager.class.getSimpleName(), "加载视频(后置摄像头):" + name);
|
||||
this.videoCapturer = cameraEnumerator.createCapturer(name, new MediaCameraEventsHandler());
|
||||
} else {
|
||||
Log.d(MediaManager.class.getSimpleName(), "忽略摄像头:" + name);
|
||||
}
|
||||
}
|
||||
} else if(this.type == Type.FILE) {
|
||||
Log.i(MediaManager.class.getSimpleName(), "加载视频(文件)");
|
||||
} else if(this.type == Type.SCREEN) {
|
||||
Log.i(MediaManager.class.getSimpleName(), "加载视频(录屏)");
|
||||
final MediaProjectionManager mediaProjectionManager = this.context.getSystemService(MediaProjectionManager.class);
|
||||
final Intent intent = mediaProjectionManager.createScreenCaptureIntent();
|
||||
final Message message = Message.obtain(this.handler, () -> {
|
||||
this.videoCapturer = new ScreenCapturerAndroid(intent, new MediaProjection.Callback() {
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
Log.i(MediaManager.class.getSimpleName(), "停止屏幕捕获");
|
||||
}
|
||||
});
|
||||
});
|
||||
message.obj = intent;
|
||||
message.what = Config.WHAT_SCREEN_CAPTURE;
|
||||
this.handler.sendMessage(message);
|
||||
// this.handler.dispatchMessage(message);
|
||||
}
|
||||
this.initVideoTrack();
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载视频
|
||||
*/
|
||||
private void initVideoTrack() {
|
||||
SurfaceTextureHelper surfaceTextureHelper = null;
|
||||
// 设置预览
|
||||
if(this.previewView != null) {
|
||||
final EglBase eglBase = EglBase.create();
|
||||
surfaceTextureHelper = SurfaceTextureHelper.create("MediaVideoThread", eglBase.getEglBaseContext());
|
||||
this.previewView.setMirror(true);
|
||||
this.previewView.setEnableHardwareScaler(true);
|
||||
}
|
||||
// 是否捕获屏幕
|
||||
final boolean screen = this.type == Type.SCREEN;
|
||||
final VideoSource videoSource = this.peerConnectionFactory.createVideoSource(screen);
|
||||
if(screen) {
|
||||
Log.i(MediaManager.class.getSimpleName(), "捕获屏幕");
|
||||
} else {
|
||||
Log.i(MediaManager.class.getSimpleName(), "捕获摄像头");
|
||||
this.cameraVideoCapturer.initialize(surfaceTextureHelper, this.context, videoSource.getCapturerObserver());
|
||||
this.cameraVideoCapturer.startCapture(640, 480, 30);
|
||||
}
|
||||
final SurfaceViewRenderer surfaceViewRenderer = new SurfaceViewRenderer(this.context);
|
||||
surfaceViewRenderer.setMirror(true);
|
||||
surfaceViewRenderer.setEnableHardwareScaler(true);
|
||||
// 加载视频
|
||||
final EglBase eglBase = EglBase.create();
|
||||
final SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create("MediaVideoThread", eglBase.getEglBaseContext());
|
||||
final VideoSource videoSource = this.peerConnectionFactory.createVideoSource(this.videoCapturer.isScreencast());
|
||||
this.videoCapturer.initialize(surfaceTextureHelper, this.context, videoSource.getCapturerObserver());
|
||||
this.videoCapturer.startCapture(640, 480, 30);
|
||||
final VideoTrack videoTrack = this.peerConnectionFactory.createVideoTrack("ARDAMSv0", videoSource);
|
||||
videoTrack.setEnabled(true);
|
||||
this.mediaStream.addTrack(videoTrack);
|
||||
Log.i(MediaManager.class.getSimpleName(), "加载视频:" + videoTrack.id());
|
||||
}
|
||||
|
||||
private void initPreview() {
|
||||
}
|
||||
|
||||
public void pauseAudio() {
|
||||
synchronized (this.mediaStream.audioTracks) {
|
||||
this.mediaStream.audioTracks.forEach(a -> a.setEnabled(false));
|
||||
@@ -264,14 +311,17 @@ public class MediaManager {
|
||||
* 释放资源
|
||||
*/
|
||||
public void close() {
|
||||
if(this.cameraVideoCapturer != null) {
|
||||
this.cameraVideoCapturer.dispose();
|
||||
if(this.videoCapturer != null) {
|
||||
this.videoCapturer.dispose();
|
||||
this.videoCapturer = null;
|
||||
}
|
||||
if(this.mediaStream != null) {
|
||||
this.mediaStream.dispose();
|
||||
this.mediaStream = null;
|
||||
}
|
||||
if(this.peerConnectionFactory != null) {
|
||||
this.peerConnectionFactory.dispose();
|
||||
this.peerConnectionFactory = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
package com.acgist.taoyao.media;
|
||||
|
||||
/**
|
||||
* 房间远程终端
|
||||
*
|
||||
* @author acgist
|
||||
*/
|
||||
public class RemoteClient {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.acgist.taoyao.media;
|
||||
|
||||
import org.webrtc.AudioTrack;
|
||||
import org.webrtc.MediaStream;
|
||||
import org.webrtc.VideoTrack;
|
||||
|
||||
import java.util.logging.Handler;
|
||||
|
||||
/**
|
||||
* 房间终端
|
||||
* 使用NDK + Mediasoup实现多人会话
|
||||
*
|
||||
* @author acgist
|
||||
*/
|
||||
public class RoomClient {
|
||||
|
||||
protected final String name;
|
||||
protected final String clientId;
|
||||
protected final Handler handler;
|
||||
protected AudioTrack audioTrack;
|
||||
protected VideoTrack videoTrack;
|
||||
protected MediaStream mediaStream;
|
||||
|
||||
public RoomClient(String name, String clientId, Handler handler) {
|
||||
this.name = name;
|
||||
this.clientId = clientId;
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开预览
|
||||
*/
|
||||
private void preview() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user