[+] 添加屏幕视频来源
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# 部署
|
||||
# 项目部署
|
||||
|
||||
## 整体环境
|
||||
|
||||
@@ -8,10 +8,11 @@ git >= 1.8.0
|
||||
pm2 >= 5.2.0
|
||||
Java >= 17.0.0
|
||||
Maven >= 3.8.0
|
||||
CMake >= 3.26.0
|
||||
nodejs >= v16.18.0
|
||||
python >= 3.8.0 with PIP
|
||||
Android >= 10
|
||||
gcc/g++ >= 10.2.0
|
||||
node version >= v16.18.0
|
||||
python version >= 3.8.0 with PIP
|
||||
```
|
||||
|
||||
## 设置Yum源
|
||||
@@ -119,8 +120,7 @@ cmake -v
|
||||
mkdir -p /data/dev/nodejs
|
||||
cd /data/dev/nodejs
|
||||
wget https://nodejs.org/dist/v16.19.0/node-v16.19.0-linux-x64.tar.xz
|
||||
xz -d node-v16.19.0-linux-x64.tar.xz
|
||||
tar -xf node-v16.19.0-linux-x64.tar
|
||||
tar -xvJf node-v16.19.0-linux-x64.tar.xz
|
||||
|
||||
# 连接
|
||||
ln -sf /data/dev/nodejs/node-v16.19.0-linux-x64/bin/npm /usr/local/bin/
|
||||
@@ -213,8 +213,7 @@ mkdir -p /data/dev/python
|
||||
cd /data/dev/python
|
||||
#wget https://www.python.org/ftp/python/3.8.16/Python-3.8.16.tar.xz
|
||||
wget https://mirrors.huaweicloud.com/python/3.8.16/Python-3.8.16.tar.xz
|
||||
xz -d Python-3.8.16.tar.xz
|
||||
tar -xf Python-3.8.16.tar
|
||||
tar -xvJf Python-3.8.16.tar.xz
|
||||
|
||||
# 安装
|
||||
cd Python-3.8.16
|
||||
@@ -226,7 +225,7 @@ ln -sf /usr/local/python3/bin/pip3.8 /usr/bin/pip
|
||||
ln -sf /usr/local/python3/bin/python3.8 /usr/bin/python
|
||||
ln -sf /usr/local/python3/bin/python3.8 /usr/bin/python3
|
||||
|
||||
# 配置YUM
|
||||
# 配置Yum
|
||||
|
||||
vim /usr/bin/yum
|
||||
vim /usr/libexec/urlgrabber-ext-down
|
||||
@@ -328,15 +327,13 @@ pm2 start | stop | restart taoyao-client-media
|
||||
|
||||
### Mediasoup编译失败
|
||||
|
||||
编译过程中的依赖下载容易失败,
|
||||
需要进入目录`mediasoup/worker/subprojects`,查看`*.wrap`文件依次下载所需依赖,修改名称放到`packagefiles`目录中,最后注释下载链接。
|
||||
将`package.json`中的`mediasoup`改为本地依赖`file:./mediasoup`,重新编译即可。
|
||||
编译过程中的依赖下载容易失败,需要进入目录`mediasoup/worker/subprojects`,查看`*.wrap`文件依次下载所需依赖,修改名称放到`packagefiles`目录中,最后注释下载链接。将`package.json`中的`mediasoup`改为本地依赖`file:./mediasoup`,重新编译即可。
|
||||
|
||||
> 下载依赖建议备份以备以后编译使用
|
||||
> 下载依赖建议备份方便再次编译使用
|
||||
|
||||
### Mediasoup单独编译
|
||||
|
||||
编译媒体服务时会自动编译`mediasoup`所以可以不用单独编译
|
||||
编译媒体服务时会自动编译`mediasoup`所以忽略单独编译
|
||||
|
||||
```
|
||||
# 编译代码
|
||||
@@ -350,6 +347,8 @@ make clean
|
||||
|
||||
## 安装Web终端
|
||||
|
||||
`Nginx`和`PM2`选择一种启动即可
|
||||
|
||||
```
|
||||
# 编译代码
|
||||
cd /data/taoyao/taoyao-client-web
|
||||
@@ -386,13 +385,13 @@ sh ./gradlew --no-daemon assembleRelease
|
||||
## 配置防火墙
|
||||
|
||||
```
|
||||
# Nginx端口
|
||||
# 终端服务(Web):Nginx
|
||||
firewall-cmd --zone=public --add-port=443/tcp --permanent
|
||||
# 终端服务:建议使用Nginx代理
|
||||
# 终端服务(Web):PM2
|
||||
firewall-cmd --zone=public --add-port=8443/tcp --permanent
|
||||
# 信令服务(WebSocket)
|
||||
firewall-cmd --zone=public --add-port=8888/tcp --permanent
|
||||
# 信令服务(Socket):没有启用不用添加规则
|
||||
# 信令服务(Socket)
|
||||
firewall-cmd --zone=public --add-port=9999/tcp --permanent
|
||||
# 媒体服务
|
||||
firewall-cmd --zone=public --add-port=40000-49999/udp --permanent
|
||||
|
||||
@@ -11,11 +11,10 @@ import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
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.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.GridLayout;
|
||||
|
||||
@@ -31,7 +30,8 @@ import com.acgist.taoyao.media.VideoSourceType;
|
||||
import com.acgist.taoyao.media.config.Config;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
import java.io.Serializable;
|
||||
import org.apache.commons.lang3.RandomUtils;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
@@ -39,9 +39,9 @@ import java.util.stream.Stream;
|
||||
*
|
||||
* @author acgist
|
||||
*/
|
||||
public class MainActivity extends AppCompatActivity implements Serializable {
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
private Handler threadHandler;
|
||||
private Handler actionHandler;
|
||||
private MainHandler mainHandler;
|
||||
private ActivityMainBinding binding;
|
||||
private MediaProjectionManager mediaProjectionManager;
|
||||
@@ -51,17 +51,20 @@ public class MainActivity extends AppCompatActivity implements Serializable {
|
||||
protected void onCreate(Bundle bundle) {
|
||||
Log.i(MainActivity.class.getSimpleName(), "onCreate");
|
||||
super.onCreate(bundle);
|
||||
final Window window = this.getWindow();
|
||||
// 强制横屏
|
||||
// this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
||||
this.buildAction();
|
||||
this.requestPermission();
|
||||
this.launchMediaService();
|
||||
this.registerMediaProjection();
|
||||
this.setTurnScreenOn(true);
|
||||
this.setShowWhenLocked(true);
|
||||
this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
this.binding = ActivityMainBinding.inflate(this.getLayoutInflater());
|
||||
this.setContentView(this.binding.getRoot());
|
||||
this.binding.getRoot().setZ(100F);
|
||||
this.registerMediaProjection();
|
||||
final View root = this.binding.getRoot();
|
||||
root.setZ(100F);
|
||||
this.setContentView(root);
|
||||
this.binding.action.setOnClickListener(this::action);
|
||||
this.binding.record.setOnClickListener(this::record);
|
||||
this.binding.settings.setOnClickListener(this::settings);
|
||||
@@ -86,13 +89,25 @@ public class MainActivity extends AppCompatActivity implements Serializable {
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置按钮线程
|
||||
* 防止按钮任务主线程等待导致主线程死锁
|
||||
*/
|
||||
private void buildAction() {
|
||||
if(this.actionHandler != null) {
|
||||
return;
|
||||
}
|
||||
final HandlerThread handlerThread = new HandlerThread("ActionThread");
|
||||
handlerThread.start();
|
||||
this.actionHandler = new Handler(handlerThread.getLooper());
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求权限
|
||||
*/
|
||||
private void requestPermission() {
|
||||
final String[] permissions = new String[]{
|
||||
final String[] permissions = new String[] {
|
||||
Manifest.permission.CAMERA,
|
||||
Manifest.permission.REBOOT,
|
||||
Manifest.permission.INTERNET,
|
||||
Manifest.permission.RECORD_AUDIO,
|
||||
Manifest.permission.ACCESS_WIFI_STATE,
|
||||
@@ -107,7 +122,7 @@ public class MainActivity extends AppCompatActivity implements Serializable {
|
||||
if (Stream.of(permissions).map(this.getApplicationContext()::checkSelfPermission).allMatch(v -> v == PackageManager.PERMISSION_GRANTED)) {
|
||||
Log.i(MediaService.class.getSimpleName(), "授权成功");
|
||||
} else {
|
||||
ActivityCompat.requestPermissions(this, permissions, 0);
|
||||
ActivityCompat.requestPermissions(this, permissions, RandomUtils.nextInt());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +133,6 @@ public class MainActivity extends AppCompatActivity implements Serializable {
|
||||
if (this.mainHandler != null) {
|
||||
return;
|
||||
}
|
||||
int waitCount = 0;
|
||||
this.mainHandler = new MainHandler();
|
||||
final Resources resources = this.getResources();
|
||||
MediaManager.getInstance().initContext(
|
||||
@@ -133,24 +147,19 @@ public class MainActivity extends AppCompatActivity implements Serializable {
|
||||
resources.getString(R.string.watermark),
|
||||
VideoSourceType.valueOf(resources.getString(R.string.videoSourceType))
|
||||
);
|
||||
final Display display = this.getWindow().getContext().getDisplay();
|
||||
while (Display.STATE_ON != display.getState() && waitCount++ < 10) {
|
||||
SystemClock.sleep(100);
|
||||
}
|
||||
if (display.STATE_ON == display.getState()) {
|
||||
Log.i(MainActivity.class.getSimpleName(), "拉起媒体服务");
|
||||
final Intent intent = new Intent(this, MediaService.class);
|
||||
intent.setAction(MediaService.Action.CONNECT.name());
|
||||
// 注意:不能使用intent传递
|
||||
MediaService.mainHandler = this.mainHandler;
|
||||
this.startService(intent);
|
||||
} else {
|
||||
Log.w(MainActivity.class.getSimpleName(), "拉起媒体服务失败");
|
||||
}
|
||||
// 注意:不能使用intent传递
|
||||
MediaService.mainHandler = this.mainHandler;
|
||||
Log.i(MainActivity.class.getSimpleName(), "拉起媒体服务");
|
||||
final Intent intent = new Intent(this, MediaService.class);
|
||||
intent.setAction(MediaService.Action.CONNECT.name());
|
||||
this.startService(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册捕获屏幕功能
|
||||
*/
|
||||
private void registerMediaProjection() {
|
||||
if (this.activityResultLauncher != null && this.mediaProjectionManager != null) {
|
||||
if (this.mediaProjectionManager != null && this.activityResultLauncher != null) {
|
||||
return;
|
||||
}
|
||||
this.mediaProjectionManager = this.getApplicationContext().getSystemService(MediaProjectionManager.class);
|
||||
@@ -172,58 +181,75 @@ public class MainActivity extends AppCompatActivity implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* 功能测试按钮根据实际情况设置功能
|
||||
* 功能按钮(测试使用)
|
||||
*
|
||||
* @param view View
|
||||
*/
|
||||
private void action(View view) {
|
||||
if (this.threadHandler == null) {
|
||||
final HandlerThread handlerThread = new HandlerThread("ActionThread");
|
||||
handlerThread.start();
|
||||
this.threadHandler = new Handler(handlerThread.getLooper());
|
||||
}
|
||||
this.threadHandler.post(() -> {
|
||||
this.actionHandler.post(() -> {
|
||||
// 进入房间
|
||||
// Taoyao.taoyao.roomEnter("4f19f6fc-1763-499b-a352-d8c955af5a6e", null);
|
||||
// 监控终端
|
||||
// Taoyao.taoyao.sessionCall("taoyao");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 录像按钮
|
||||
*
|
||||
* @param view View
|
||||
*/
|
||||
private void record(View view) {
|
||||
final MediaManager mediaManager = MediaManager.getInstance();
|
||||
if (mediaManager.isRecording()) {
|
||||
mediaManager.stopRecord();
|
||||
} else {
|
||||
mediaManager.startRecord();
|
||||
}
|
||||
}
|
||||
|
||||
private void settings(View view) {
|
||||
final Intent intent = new Intent(this, SettingsActivity.class);
|
||||
this.startActivity(intent);
|
||||
}
|
||||
|
||||
private void photograph(View view) {
|
||||
MediaManager.getInstance().photograph();
|
||||
this.actionHandler.post(() -> {
|
||||
final MediaManager mediaManager = MediaManager.getInstance();
|
||||
if (mediaManager.isRecording()) {
|
||||
mediaManager.stopRecord();
|
||||
} else {
|
||||
mediaManager.startRecord();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler
|
||||
* 设置按钮
|
||||
*
|
||||
* @param view View
|
||||
*/
|
||||
private void settings(View view) {
|
||||
this.actionHandler.post(() -> {
|
||||
final Intent intent = new Intent(this, SettingsActivity.class);
|
||||
this.startActivity(intent);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 拍照按钮
|
||||
*
|
||||
* @param view View
|
||||
*/
|
||||
private void photograph(View view) {
|
||||
this.actionHandler.post(() -> {
|
||||
MediaManager.getInstance().photograph();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* MainHandler
|
||||
*
|
||||
* @author acgist
|
||||
*/
|
||||
public class MainHandler extends Handler implements Serializable {
|
||||
public class MainHandler extends Handler {
|
||||
|
||||
@Override
|
||||
public void handleMessage(@NonNull Message message) {
|
||||
super.handleMessage(message);
|
||||
Log.d(MainHandler.class.getSimpleName(), "Handler消息:" + message.what);
|
||||
Log.d(MainHandler.class.getSimpleName(), "MainHandler消息:" + message.what);
|
||||
switch (message.what) {
|
||||
case Config.WHAT_SCREEN_CAPTURE -> MainActivity.this.screenCapture(message);
|
||||
case Config.WHAT_RECORD -> MainActivity.this.record(message);
|
||||
case Config.WHAT_NEW_LOCAL_VIDEO,
|
||||
Config.WHAT_NEW_REMOTE_VIDEO -> MainActivity.this.previewVideo(message);
|
||||
case Config.WHAT_REMOVE_VIDEO -> MainActivity.this.removeVideo(message);
|
||||
case Config.WHAT_REMOVE_VIDEO -> MainActivity.this.removePreviewVideo(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,27 +264,34 @@ public class MainActivity extends AppCompatActivity implements Serializable {
|
||||
this.activityResultLauncher.launch(this.mediaProjectionManager.createScreenCaptureIntent());
|
||||
}
|
||||
|
||||
/**
|
||||
* 录制按钮
|
||||
*
|
||||
* @param message 消息
|
||||
*/
|
||||
private void record(Message message) {
|
||||
final Resources resources = this.getResources();
|
||||
final Resources resources = this.getResources();
|
||||
final Resources.Theme theme = this.getTheme();
|
||||
final FloatingActionButton record = this.binding.record;
|
||||
if(Boolean.TRUE.equals(message.obj)) {
|
||||
record.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(R.color.purple_500, this.getTheme())));
|
||||
record.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(R.color.purple_500, theme)));
|
||||
} else {
|
||||
record.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(R.color.teal_200, this.getTheme())));
|
||||
record.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(R.color.teal_200, theme)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览用户视频
|
||||
* 视频预览
|
||||
*
|
||||
* @param message 消息
|
||||
*/
|
||||
private void previewVideo(Message message) {
|
||||
private synchronized void previewVideo(Message message) {
|
||||
final GridLayout video = this.binding.video;
|
||||
final int count = video.getChildCount();
|
||||
final GridLayout.Spec rowSpec = GridLayout.spec(count / 2, 1.0F);
|
||||
final GridLayout.Spec columnSpec = GridLayout.spec(count % 2, 1.0F);
|
||||
GridLayout.LayoutParams layoutParams = new GridLayout.LayoutParams(rowSpec, columnSpec);
|
||||
final int count = video.getChildCount();
|
||||
final GridLayout.LayoutParams layoutParams = new GridLayout.LayoutParams(
|
||||
GridLayout.spec(count / 2, 1.0F),
|
||||
GridLayout.spec(count % 2, 1.0F)
|
||||
);
|
||||
layoutParams.width = 0;
|
||||
layoutParams.height = 0;
|
||||
final SurfaceView surfaceView = (SurfaceView) message.obj;
|
||||
@@ -266,16 +299,19 @@ public class MainActivity extends AppCompatActivity implements Serializable {
|
||||
video.addView(surfaceView, layoutParams);
|
||||
}
|
||||
|
||||
private void removeVideo(Message message) {
|
||||
synchronized (this) {
|
||||
final GridLayout video = this.binding.video;
|
||||
final SurfaceView surfaceView = (SurfaceView) message.obj;
|
||||
final int index = video.indexOfChild(surfaceView);
|
||||
if(index < 0) {
|
||||
return;
|
||||
}
|
||||
video.removeViewAt(index);
|
||||
/**
|
||||
* 移除视频预览
|
||||
*
|
||||
* @param message 消息
|
||||
*/
|
||||
private synchronized void removePreviewVideo(Message message) {
|
||||
final GridLayout video = this.binding.video;
|
||||
final SurfaceView surfaceView = (SurfaceView) message.obj;
|
||||
final int index = video.indexOfChild(surfaceView);
|
||||
if(index < 0) {
|
||||
return;
|
||||
}
|
||||
video.removeViewAt(index);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -158,6 +158,10 @@ public final class MediaManager {
|
||||
* 视频处理
|
||||
*/
|
||||
private VideoProcesser videoProcesser;
|
||||
/**
|
||||
* 录屏等待锁
|
||||
*/
|
||||
private final Object screenLock = new Object();
|
||||
|
||||
static {
|
||||
// // 设置采样
|
||||
@@ -189,7 +193,7 @@ public final class MediaManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 是否可用:所有配置加载完成
|
||||
* @return 是否可用(所有配置加载完成)
|
||||
*/
|
||||
public boolean available() {
|
||||
return
|
||||
@@ -199,8 +203,38 @@ public final class MediaManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mainHandler Handler
|
||||
* @param context 上下文
|
||||
* @return 是否正在录制
|
||||
*/
|
||||
public boolean isRecording() {
|
||||
return this.recordClient != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MediaProperties
|
||||
*/
|
||||
public MediaProperties getMediaProperties() {
|
||||
return this.mediaProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WebrtcProperties
|
||||
*/
|
||||
public WebrtcProperties getWebrtcProperties() {
|
||||
return this.webrtcProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mainHandler MainHandler
|
||||
* @param context 上下文
|
||||
* @param imageQuantity 图片质量
|
||||
* @param audioQuantity 音频质量
|
||||
* @param videoQuantity 视频质量
|
||||
* @param channelCount 音频通道数量
|
||||
* @param iFrameInterval 关键帧频率
|
||||
* @param imagePath 图片保存路径
|
||||
* @param videoPath 视频保存路径
|
||||
* @param watermark 水印信息
|
||||
* @param videoSourceType 视频来源类型
|
||||
*/
|
||||
public void initContext(
|
||||
Handler mainHandler, Context context,
|
||||
@@ -223,7 +257,7 @@ public final class MediaManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param taoyao 信令
|
||||
* @param taoyao 信令
|
||||
*/
|
||||
public void initTaoyao(ITaoyao taoyao) {
|
||||
this.taoyao = taoyao;
|
||||
@@ -276,18 +310,6 @@ public final class MediaManager {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRecording() {
|
||||
return this.recordClient != null;
|
||||
}
|
||||
|
||||
public MediaProperties getMediaProperties() {
|
||||
return this.mediaProperties;
|
||||
}
|
||||
|
||||
public WebrtcProperties getWebrtcProperties() {
|
||||
return this.webrtcProperties;
|
||||
}
|
||||
|
||||
private void initPeerConnectionFactory() {
|
||||
PeerConnectionFactory.initialize(
|
||||
PeerConnectionFactory.InitializationOptions.builder(this.context)
|
||||
@@ -407,17 +429,22 @@ public final class MediaManager {
|
||||
} else if (this.videoSourceType.isCamera()) {
|
||||
this.initCamera();
|
||||
} else if (this.videoSourceType == VideoSourceType.SCREEN) {
|
||||
this.initSharePromise();
|
||||
this.initScreenPromise();
|
||||
} else {
|
||||
// 其他来源
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载文件采集
|
||||
*/
|
||||
private void initFile() {
|
||||
// 自己实现
|
||||
// new FileVideoCapturer();
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载摄像头
|
||||
* 加载摄像头采集
|
||||
*/
|
||||
private void initCamera() {
|
||||
final CameraEnumerator cameraEnumerator = new Camera2Enumerator(this.context);
|
||||
@@ -434,28 +461,39 @@ public final class MediaManager {
|
||||
this.initVideoSource();
|
||||
}
|
||||
|
||||
private void initSharePromise() {
|
||||
/**
|
||||
* 加载屏幕采集
|
||||
*/
|
||||
private void initScreenPromise() {
|
||||
this.mainHandler.obtainMessage(Config.WHAT_SCREEN_CAPTURE).sendToTarget();
|
||||
synchronized (this.screenLock) {
|
||||
try {
|
||||
this.screenLock.wait();
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(MediaManager.class.getSimpleName(), "等待录屏授权异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载屏幕
|
||||
* 加载屏幕采集
|
||||
*
|
||||
* @param intent Intent
|
||||
*/
|
||||
public void initScreen(Intent intent) {
|
||||
this.videoCapturer = new ScreenCapturerAndroid(intent, new ScreenCallback());
|
||||
this.initVideoSource();
|
||||
synchronized (this.screenLock) {
|
||||
this.screenLock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载视频
|
||||
* 加载视频来源
|
||||
*/
|
||||
private void initVideoSource() {
|
||||
// 加载视频
|
||||
this.surfaceTextureHelper = SurfaceTextureHelper.create("MediaVideoThread", this.eglContext);
|
||||
// this.surfaceTextureHelper.setTextureSize();
|
||||
// this.surfaceTextureHelper.setFrameRotation();
|
||||
// 主码流
|
||||
this.mainVideoSource = this.peerConnectionFactory.createVideoSource(this.videoCapturer.isScreencast());
|
||||
// 次码流
|
||||
@@ -464,8 +502,6 @@ public final class MediaManager {
|
||||
this.shareVideoSource.adaptOutputFormat(mediaVideoProperties.getWidth(), mediaVideoProperties.getHeight(), mediaVideoProperties.getFrameRate());
|
||||
// 视频捕获
|
||||
this.videoCapturer.initialize(this.surfaceTextureHelper, this.context, new VideoCapturerObserver());
|
||||
// 次码流视频处理
|
||||
// this.shareVideoSource.setVideoProcessor();
|
||||
}
|
||||
|
||||
private void initWatermark() {
|
||||
@@ -541,13 +577,16 @@ public final class MediaManager {
|
||||
cameraVideoCapturer.switchCamera(new CameraVideoCapturer.CameraSwitchHandler() {
|
||||
@Override
|
||||
public void onCameraSwitchDone(boolean success) {
|
||||
Log.d(MediaManager.class.getSimpleName(), "切换镜头成功");
|
||||
}
|
||||
@Override
|
||||
public void onCameraSwitchError(String message) {
|
||||
Log.e(MediaManager.class.getSimpleName(), "切换镜头失败:" + message);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.initVideo();
|
||||
// this.initVideo();
|
||||
// 切换所有VideoTrack视频来源
|
||||
}
|
||||
}
|
||||
|
||||
@@ -625,18 +664,14 @@ public final class MediaManager {
|
||||
|
||||
public String photograph() {
|
||||
synchronized (this) {
|
||||
String filepath;
|
||||
final PhotographClient photographClient = new PhotographClient(this.imageQuantity, this.imagePath);
|
||||
if(this.clientCount <= 0) {
|
||||
final MediaVideoProperties mediaVideoProperties = this.mediaProperties.getVideos().get(this.videoQuantity);
|
||||
photographClient.photograph(mediaVideoProperties.getWidth(), mediaVideoProperties.getHeight(), mediaVideoProperties.getFrameRate(), this.videoSourceType, this.context);
|
||||
filepath = photographClient.waitForPhotograph();
|
||||
} else {
|
||||
final VideoTrack videoTrack = this.peerConnectionFactory.createVideoTrack("TaoyaoVP", this.mainVideoSource);
|
||||
photographClient.photograph(videoTrack);
|
||||
filepath = photographClient.waitForPhotograph();
|
||||
photographClient.photograph(this.mainVideoSource, this.peerConnectionFactory);
|
||||
}
|
||||
return filepath;
|
||||
return photographClient.waitForPhotograph();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -654,8 +689,7 @@ public final class MediaManager {
|
||||
this.videoPath, this.taoyao, this.mainHandler
|
||||
);
|
||||
this.recordClient.start();
|
||||
final VideoTrack videoTrack = this.peerConnectionFactory.createVideoTrack("TaoyaoVR", this.mainVideoSource);
|
||||
this.recordClient.source(videoTrack);
|
||||
this.recordClient.record(this.mainVideoSource, this.peerConnectionFactory);
|
||||
this.mainHandler.obtainMessage(Config.WHAT_RECORD, Boolean.TRUE).sendToTarget();
|
||||
return this.recordClient;
|
||||
}
|
||||
|
||||
@@ -27,8 +27,10 @@ import android.view.Surface;
|
||||
import com.acgist.taoyao.boot.utils.DateUtils;
|
||||
import com.acgist.taoyao.media.VideoSourceType;
|
||||
|
||||
import org.webrtc.PeerConnectionFactory;
|
||||
import org.webrtc.VideoFrame;
|
||||
import org.webrtc.VideoSink;
|
||||
import org.webrtc.VideoSource;
|
||||
import org.webrtc.VideoTrack;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -94,10 +96,17 @@ public class PhotographClient implements VideoSink {
|
||||
return this.filepath;
|
||||
}
|
||||
|
||||
public void photograph(VideoTrack videoTrack) {
|
||||
videoTrack.setEnabled(true);
|
||||
videoTrack.addSink(this);
|
||||
this.videoTrack = videoTrack;
|
||||
public void photograph(VideoSource videoSource, PeerConnectionFactory peerConnectionFactory) {
|
||||
if(this.videoTrack != null) {
|
||||
return;
|
||||
}
|
||||
if(videoSource == null || peerConnectionFactory == null) {
|
||||
Log.e(PhotographClient.class.getSimpleName(), "数据采集无效");
|
||||
return;
|
||||
}
|
||||
this.videoTrack = peerConnectionFactory.createVideoTrack("TaoyaoVP", videoSource);
|
||||
this.videoTrack.setEnabled(true);
|
||||
this.videoTrack.addSink(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -178,6 +187,9 @@ public class PhotographClient implements VideoSink {
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
public void photograph(int width, int height, int fps, VideoSourceType videoSourceType, Context context) {
|
||||
if(this.handlerThread != null) {
|
||||
return;
|
||||
}
|
||||
this.handlerThread = new HandlerThread("PhotographThread");
|
||||
this.handlerThread.start();
|
||||
final Handler handler = new Handler(this.handlerThread.getLooper());
|
||||
@@ -200,6 +212,11 @@ public class PhotographClient implements VideoSink {
|
||||
// TODO:截屏
|
||||
}
|
||||
}
|
||||
if(cameraId == null) {
|
||||
Log.e(PhotographClient.class.getSimpleName(), "拍照失败没有适配:" + videoSourceType);
|
||||
PhotographClient.this.notifyWait();
|
||||
return;
|
||||
}
|
||||
cameraManager.openCamera(cameraId, this.cameraDeviceStateCallback, null);
|
||||
} catch (CameraAccessException e) {
|
||||
Log.e(PhotographClient.class.getSimpleName(), "拍照异常", e);
|
||||
|
||||
@@ -13,8 +13,10 @@ import com.acgist.taoyao.boot.utils.DateUtils;
|
||||
import com.acgist.taoyao.media.MediaManager;
|
||||
import com.acgist.taoyao.media.signal.ITaoyao;
|
||||
|
||||
import org.webrtc.PeerConnectionFactory;
|
||||
import org.webrtc.VideoFrame;
|
||||
import org.webrtc.VideoSink;
|
||||
import org.webrtc.VideoSource;
|
||||
import org.webrtc.VideoTrack;
|
||||
import org.webrtc.YuvHelper;
|
||||
import org.webrtc.audio.JavaAudioDeviceModule;
|
||||
@@ -127,7 +129,8 @@ public class RecordClient extends Client implements VideoSink, JavaAudioDeviceMo
|
||||
|
||||
public RecordClient(
|
||||
int audioBitRate, int sampleRate, int channelCount,
|
||||
int videoBitRate, int frameRate, int iFrameInterval, int width, int height,
|
||||
int videoBitRate, int frameRate, int iFrameInterval,
|
||||
int width, int height,
|
||||
String path, ITaoyao taoyao, Handler mainHandler
|
||||
) {
|
||||
super("本地录像", "LocalRecordClient", taoyao, mainHandler);
|
||||
@@ -140,8 +143,8 @@ public class RecordClient extends Client implements VideoSink, JavaAudioDeviceMo
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.yuvSize = width * height * 3 / 2;
|
||||
this.filename = DateUtils.format(LocalDateTime.now(), DateUtils.DateTimeStyle.YYYYMMDDHH24MMSS) + ".mp4";
|
||||
this.filepath = Paths.get(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath(), path, this.filename).toString();
|
||||
this.filename = DateUtils.format(LocalDateTime.now(), DateUtils.DateTimeStyle.YYYYMMDDHH24MMSS) + ".mp4";
|
||||
this.filepath = Paths.get(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath(), path, this.filename).toString();
|
||||
}
|
||||
|
||||
public void start() {
|
||||
@@ -366,8 +369,15 @@ public class RecordClient extends Client implements VideoSink, JavaAudioDeviceMo
|
||||
}
|
||||
}
|
||||
|
||||
public void source(VideoTrack videoTrack) {
|
||||
this.videoTrack = videoTrack;
|
||||
public void record(VideoSource videoSource, PeerConnectionFactory peerConnectionFactory) {
|
||||
if(this.videoTrack != null) {
|
||||
return;
|
||||
}
|
||||
if(videoSource == null || peerConnectionFactory == null) {
|
||||
Log.e(RecordClient.class.getSimpleName(), "数据采集无效");
|
||||
return;
|
||||
}
|
||||
this.videoTrack = peerConnectionFactory.createVideoTrack("TaoyaoVR", videoSource);
|
||||
this.videoTrack.setEnabled(true);
|
||||
this.videoTrack.addSink(this);
|
||||
}
|
||||
@@ -380,13 +390,18 @@ public class RecordClient extends Client implements VideoSink, JavaAudioDeviceMo
|
||||
}
|
||||
super.close();
|
||||
Log.i(RecordClient.class.getSimpleName(), "结束录制:" + this.filepath);
|
||||
this.videoTrack.removeSink(this);
|
||||
this.videoTrack.dispose();
|
||||
if(this.videoTrack != null) {
|
||||
this.videoTrack.removeSink(this);
|
||||
this.videoTrack.dispose();
|
||||
this.videoTrack = null;
|
||||
}
|
||||
if (this.audioThread != null) {
|
||||
this.audioThread.quitSafely();
|
||||
this.audioThread = null;
|
||||
}
|
||||
if (this.videoThread != null) {
|
||||
this.videoThread.quitSafely();
|
||||
this.videoThread = null;
|
||||
}
|
||||
final File file = new File(this.filepath);
|
||||
if(file.length() <= 0) {
|
||||
|
||||
@@ -10,19 +10,19 @@ public class Config {
|
||||
/**
|
||||
* 屏幕捕获
|
||||
*/
|
||||
public static final int WHAT_SCREEN_CAPTURE = 1000;
|
||||
public static final int WHAT_SCREEN_CAPTURE = 1000;
|
||||
/**
|
||||
* 视频录制
|
||||
*/
|
||||
public static final int WHAT_RECORD = 1001;
|
||||
public static final int WHAT_RECORD = 1001;
|
||||
/**
|
||||
* 新建本地音频
|
||||
*/
|
||||
public static final int WHAT_NEW_LOCAL_AUDIO = 2000;
|
||||
public static final int WHAT_NEW_LOCAL_AUDIO = 2000;
|
||||
/**
|
||||
* 新建本地视频
|
||||
*/
|
||||
public static final int WHAT_NEW_LOCAL_VIDEO = 2001;
|
||||
public static final int WHAT_NEW_LOCAL_VIDEO = 2001;
|
||||
/**
|
||||
* 新建远程音频
|
||||
*/
|
||||
@@ -31,13 +31,17 @@ public class Config {
|
||||
* 新建远程视频
|
||||
*/
|
||||
public static final int WHAT_NEW_REMOTE_VIDEO = 2003;
|
||||
/**
|
||||
* 移除远程音频
|
||||
*/
|
||||
public static final int WHAT_REMOVE_AUDIO = 2998;
|
||||
/**
|
||||
* 移除远程视频
|
||||
*/
|
||||
public static final int WHAT_REMOVE_VIDEO = 2999;
|
||||
public static final int WHAT_REMOVE_VIDEO = 2999;
|
||||
/**
|
||||
* 默认声音大小
|
||||
*/
|
||||
public static final double DEFAULT_VOLUME = 10.0D;
|
||||
public static final double DEFAULT_VOLUME = 10.0D;
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user