[*] 依赖

This commit is contained in:
acgist
2023-03-31 08:13:58 +08:00
parent d8a1121308
commit b7c9a0e4c1
9 changed files with 228 additions and 60 deletions

View File

@@ -9,14 +9,19 @@ import android.os.Message;
import android.os.SystemClock; import android.os.SystemClock;
import android.util.Log; import android.util.Log;
import android.view.Display; import android.view.Display;
import android.view.SurfaceView;
import android.view.View; import android.view.View;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.Toast; import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import com.acgist.taoyao.client.databinding.ActivityMainBinding; import com.acgist.taoyao.client.databinding.ActivityMainBinding;
import com.acgist.taoyao.config.Config;
import com.acgist.taoyao.media.MediaManager;
import java.io.Serializable; import java.io.Serializable;
@@ -25,7 +30,7 @@ import java.io.Serializable;
* *
* @author acgist * @author acgist
*/ */
public class MainActivity extends AppCompatActivity { public class MainActivity extends AppCompatActivity implements Serializable {
private MainHandler mainHandler; private MainHandler mainHandler;
private ActivityMainBinding binding; private ActivityMainBinding binding;
@@ -96,6 +101,9 @@ public class MainActivity extends AppCompatActivity {
if(!allGranted) { if(!allGranted) {
Toast.makeText(this, "授权失败", Toast.LENGTH_SHORT).show(); 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(), "拉起媒体服务"); Log.i(MainActivity.class.getSimpleName(), "拉起媒体服务");
final Intent intent = new Intent(this, MediaService.class); final Intent intent = new Intent(this, MediaService.class);
if(this.mainHandler == null) { if(this.mainHandler == null) {
this.mainHandler = new MainHandler(); this.mainHandler = new MainHandler(this);
} }
intent.setAction(MediaService.Action.CONNECT.name()); intent.setAction(MediaService.Action.CONNECT.name());
intent.putExtra("mainHandler", this.mainHandler); intent.putExtra("mainHandler", this.mainHandler);
@@ -136,14 +144,52 @@ public class MainActivity extends AppCompatActivity {
* *
* @author acgist * @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 @Override
public void handleMessage(@NonNull Message message) { public void handleMessage(@NonNull Message message) {
super.handleMessage(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) {
}
} }

View File

@@ -27,6 +27,7 @@ public class MediaService extends Service {
static { static {
System.loadLibrary("taoyao"); System.loadLibrary("taoyao");
System.loadLibrary("jingle_peerconnection_so");
} }
/** /**

View File

@@ -24,7 +24,8 @@ android {
"-DLIBWEBRTC_BINARY_PATH=" + WEBRTC_LIB_PATH, "-DLIBWEBRTC_BINARY_PATH=" + WEBRTC_LIB_PATH,
"-DMEDIASOUPCLIENT_BUILD_TESTS=OFF", "-DMEDIASOUPCLIENT_BUILD_TESTS=OFF",
"-DMEDIASOUPCLIENT_LOG_TRACE=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' version '3.22.1'
} }
} }
sourceSets {
main {
jniLibs.srcDirs = ["libs"]
}
}
} }
dependencies { 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-lang3:3.12.0'
implementation 'org.apache.commons:commons-collections4:4.4' implementation 'org.apache.commons:commons-collections4:4.4'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.2' implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.2'

View File

@@ -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;
}

View File

@@ -1,12 +1,16 @@
package com.acgist.taoyao.media; package com.acgist.taoyao.media;
import java.util.logging.Handler;
/** /**
* 房间终端 * 房间本地终端
* 使用NDK + Mediasoup实现多人会话
* *
* @author acgist * @author acgist
*/ */
public class LocalClient { public class LocalClient extends RoomClient {
public LocalClient(String name, String clientId, Handler handler) {
super(name, clientId, handler);
}
} }

View File

@@ -1,8 +1,15 @@
package com.acgist.taoyao.media; package com.acgist.taoyao.media;
import android.content.Context; 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 android.util.Log;
import com.acgist.taoyao.config.Config;
import org.webrtc.AudioSource; import org.webrtc.AudioSource;
import org.webrtc.AudioTrack; import org.webrtc.AudioTrack;
import org.webrtc.Camera2Enumerator; import org.webrtc.Camera2Enumerator;
@@ -12,6 +19,7 @@ import org.webrtc.EglBase;
import org.webrtc.MediaConstraints; import org.webrtc.MediaConstraints;
import org.webrtc.MediaStream; import org.webrtc.MediaStream;
import org.webrtc.PeerConnectionFactory; import org.webrtc.PeerConnectionFactory;
import org.webrtc.ScreenCapturerAndroid;
import org.webrtc.SurfaceTextureHelper; import org.webrtc.SurfaceTextureHelper;
import org.webrtc.SurfaceViewRenderer; import org.webrtc.SurfaceViewRenderer;
import org.webrtc.VideoCapturer; import org.webrtc.VideoCapturer;
@@ -36,6 +44,8 @@ public class MediaManager {
*/ */
public enum Type { public enum Type {
// 文件共享
FILE,
// 后置摄像头 // 后置摄像头
BACK, BACK,
// 前置摄像头 // 前置摄像头
@@ -43,12 +53,33 @@ public class MediaManager {
// 屏幕共享 // 屏幕共享
SCREEN; 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; private Type type;
/**
* Handler
*/
private Handler handler;
/** /**
* 上下文 * 上下文
*/ */
@@ -58,21 +89,16 @@ public class MediaManager {
*/ */
private MediaStream mediaStream; private MediaStream mediaStream;
/** /**
* 屏幕捕获 * 视频捕获
* FileVideoCapturer
* CameraVideoCapturer
* ScreenCapturerAndroid
*/ */
private VideoCapturer videoCapturer; private VideoCapturer videoCapturer;
/**
* 摄像头捕获
*/
private CameraVideoCapturer cameraVideoCapturer;
/** /**
* Peer连接工厂 * Peer连接工厂
*/ */
private PeerConnectionFactory peerConnectionFactory; private PeerConnectionFactory peerConnectionFactory;
/**
* 预览
*/
private SurfaceViewRenderer previewView;
static { 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.peerConnectionFactory = PeerConnectionFactory.builder().createPeerConnectionFactory();
this.mediaStream = this.peerConnectionFactory.createLocalMediaStream("ARDAMS"); this.mediaStream = this.peerConnectionFactory.createLocalMediaStream("ARDAMS");
} }
@@ -104,11 +139,10 @@ public class MediaManager {
} }
this.type = type; this.type = type;
Log.i(MediaManager.class.getSimpleName(), "设置视频来源:" + type); Log.i(MediaManager.class.getSimpleName(), "设置视频来源:" + type);
if(this.type == Type.SCREEN || type == Type.SCREEN) { if(this.type.isCamera() && type.isCamera()) {
this.initVideo();
} else {
// TODO测试是否需要完全重置 // TODO测试是否需要完全重置
this.cameraVideoCapturer.switchCamera(new CameraVideoCapturer.CameraSwitchHandler() { final CameraVideoCapturer cameraVideoCapturer = (CameraVideoCapturer) this.videoCapturer;
cameraVideoCapturer.switchCamera(new CameraVideoCapturer.CameraSwitchHandler() {
@Override @Override
public void onCameraSwitchDone(boolean success) { public void onCameraSwitchDone(boolean success) {
} }
@@ -116,13 +150,15 @@ public class MediaManager {
public void onCameraSwitchError(String message) { public void onCameraSwitchError(String message) {
} }
}); });
} else {
this.initVideo();
} }
} }
/** /**
* 加载音频 * 加载音频
*/ */
private void initAudio() { public void initAudio() {
// 关闭音频 // 关闭音频
this.closeAudioTrack(); this.closeAudioTrack();
// 加载音频 // 加载音频
@@ -137,57 +173,68 @@ public class MediaManager {
/** /**
* 加载视频 * 加载视频
*/ */
private void initVideo() { public void initVideo() {
this.closeVideoTrack(); this.closeVideoTrack();
if(this.cameraVideoCapturer != null) { if(this.videoCapturer != null) {
this.cameraVideoCapturer.dispose(); this.videoCapturer.dispose();
} }
final CameraEnumerator cameraEnumerator = new Camera2Enumerator(this.context); if(this.type.isCamera()) {
final String[] names = cameraEnumerator.getDeviceNames(); final CameraEnumerator cameraEnumerator = new Camera2Enumerator(this.context);
for(String name : names) { final String[] names = cameraEnumerator.getDeviceNames();
if(this.type == Type.FRONT && cameraEnumerator.isFrontFacing(name)) { for(String name : names) {
Log.i(MediaManager.class.getSimpleName(), "加载前置摄像头:" + name); if(this.type == Type.FRONT && cameraEnumerator.isFrontFacing(name)) {
this.cameraVideoCapturer = cameraEnumerator.createCapturer(name, new MediaCameraEventsHandler()); Log.i(MediaManager.class.getSimpleName(), "加载视频(前置摄像头):" + name);
} else if(this.type == Type.BACK && cameraEnumerator.isBackFacing(name)) { this.videoCapturer = cameraEnumerator.createCapturer(name, new MediaCameraEventsHandler());
Log.i(MediaManager.class.getSimpleName(), "加载后置摄像头:" + name); } else if(this.type == Type.BACK && cameraEnumerator.isBackFacing(name)) {
this.cameraVideoCapturer = cameraEnumerator.createCapturer(name, new MediaCameraEventsHandler()); Log.i(MediaManager.class.getSimpleName(), "加载视频(后置摄像头):" + name);
} else { this.videoCapturer = cameraEnumerator.createCapturer(name, new MediaCameraEventsHandler());
Log.i(MediaManager.class.getSimpleName(), "忽略摄像头:" + name); } 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() { private void initVideoTrack() {
SurfaceTextureHelper surfaceTextureHelper = null;
// 设置预览 // 设置预览
if(this.previewView != null) { final SurfaceViewRenderer surfaceViewRenderer = new SurfaceViewRenderer(this.context);
final EglBase eglBase = EglBase.create(); surfaceViewRenderer.setMirror(true);
surfaceTextureHelper = SurfaceTextureHelper.create("MediaVideoThread", eglBase.getEglBaseContext()); surfaceViewRenderer.setEnableHardwareScaler(true);
this.previewView.setMirror(true); // 加载视频
this.previewView.setEnableHardwareScaler(true); final EglBase eglBase = EglBase.create();
} final SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create("MediaVideoThread", eglBase.getEglBaseContext());
// 是否捕获屏幕 final VideoSource videoSource = this.peerConnectionFactory.createVideoSource(this.videoCapturer.isScreencast());
final boolean screen = this.type == Type.SCREEN; this.videoCapturer.initialize(surfaceTextureHelper, this.context, videoSource.getCapturerObserver());
final VideoSource videoSource = this.peerConnectionFactory.createVideoSource(screen); this.videoCapturer.startCapture(640, 480, 30);
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 VideoTrack videoTrack = this.peerConnectionFactory.createVideoTrack("ARDAMSv0", videoSource); final VideoTrack videoTrack = this.peerConnectionFactory.createVideoTrack("ARDAMSv0", videoSource);
videoTrack.setEnabled(true); videoTrack.setEnabled(true);
this.mediaStream.addTrack(videoTrack); this.mediaStream.addTrack(videoTrack);
Log.i(MediaManager.class.getSimpleName(), "加载视频:" + videoTrack.id()); Log.i(MediaManager.class.getSimpleName(), "加载视频:" + videoTrack.id());
} }
private void initPreview() {
}
public void pauseAudio() { public void pauseAudio() {
synchronized (this.mediaStream.audioTracks) { synchronized (this.mediaStream.audioTracks) {
this.mediaStream.audioTracks.forEach(a -> a.setEnabled(false)); this.mediaStream.audioTracks.forEach(a -> a.setEnabled(false));
@@ -264,14 +311,17 @@ public class MediaManager {
* 释放资源 * 释放资源
*/ */
public void close() { public void close() {
if(this.cameraVideoCapturer != null) { if(this.videoCapturer != null) {
this.cameraVideoCapturer.dispose(); this.videoCapturer.dispose();
this.videoCapturer = null;
} }
if(this.mediaStream != null) { if(this.mediaStream != null) {
this.mediaStream.dispose(); this.mediaStream.dispose();
this.mediaStream = null;
} }
if(this.peerConnectionFactory != null) { if(this.peerConnectionFactory != null) {
this.peerConnectionFactory.dispose(); this.peerConnectionFactory.dispose();
this.peerConnectionFactory = null;
} }
} }

View File

@@ -1,4 +1,9 @@
package com.acgist.taoyao.media; package com.acgist.taoyao.media;
/**
* 房间远程终端
*
* @author acgist
*/
public class RemoteClient { public class RemoteClient {
} }

View File

@@ -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() {
}
}