diff --git a/docs/Deploy.md b/docs/Deploy.md index 72d2b32..5f1b0d8 100644 --- a/docs/Deploy.md +++ b/docs/Deploy.md @@ -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 diff --git a/taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/MainActivity.java b/taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/MainActivity.java index 14b55e0..532c472 100644 --- a/taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/MainActivity.java +++ b/taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/MainActivity.java @@ -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); } } \ No newline at end of file diff --git a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/MediaManager.java b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/MediaManager.java index c4c1da4..a92532a 100644 --- a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/MediaManager.java +++ b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/MediaManager.java @@ -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; } diff --git a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/PhotographClient.java b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/PhotographClient.java index adc85f1..443fbe0 100644 --- a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/PhotographClient.java +++ b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/PhotographClient.java @@ -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); diff --git a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/RecordClient.java b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/RecordClient.java index 28d0367..4939f64 100644 --- a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/RecordClient.java +++ b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/RecordClient.java @@ -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) { diff --git a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/config/Config.java b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/config/Config.java index 6de815a..6cc4fc0 100644 --- a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/config/Config.java +++ b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/config/Config.java @@ -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; }