diff --git a/docs/Deploy.md b/docs/Deploy.md index 54ff12f..26b62a8 100644 --- a/docs/Deploy.md +++ b/docs/Deploy.md @@ -380,6 +380,7 @@ nginx -s reload ``` # 终端服务:建议使用Nginx代理 +firewall-cmd --zone=public --add-port=443/tcp --permanent firewall-cmd --zone=public --add-port=8443/tcp --permanent # 信令服务(WebSocket) firewall-cmd --zone=public --add-port=8888/tcp --permanent @@ -392,6 +393,7 @@ firewall-cmd --reload firewall-cmd --list-ports # 删除端口 +#firewall-cmd --zone=public --remove-port=443/tcp --permanent #firewall-cmd --zone=public --remove-port=8443/tcp --permanent #firewall-cmd --zone=public --remove-port=8888/tcp --permanent #firewall-cmd --zone=public --remove-port=9999/tcp --permanent diff --git a/taoyao-client-android/taoyao/boot/src/main/java/com/acgist/taoyao/boot/model/Message.java b/taoyao-client-android/taoyao/boot/src/main/java/com/acgist/taoyao/boot/model/Message.java index aa5f3c4..ea9f152 100644 --- a/taoyao-client-android/taoyao/boot/src/main/java/com/acgist/taoyao/boot/model/Message.java +++ b/taoyao-client-android/taoyao/boot/src/main/java/com/acgist/taoyao/boot/model/Message.java @@ -1,5 +1,7 @@ package com.acgist.taoyao.boot.model; +import android.util.Log; + import com.acgist.taoyao.boot.utils.JSONUtils; import com.fasterxml.jackson.annotation.JsonIncludeProperties; @@ -161,7 +163,8 @@ public class Message implements Cloneable, Serializable { } else if (this.body == null) { return new HashMap<>(); } else { - throw MessageCodeException.of("信令主体类型错误:" + this.body); + Log.w(Message.class.getSimpleName(), "信令主体类型错误:" + this.body); + return new HashMap<>(); } } diff --git a/taoyao-client-android/taoyao/boot/src/main/java/com/acgist/taoyao/boot/model/MessageCodeException.java b/taoyao-client-android/taoyao/boot/src/main/java/com/acgist/taoyao/boot/model/MessageCodeException.java deleted file mode 100644 index 327a4f6..0000000 --- a/taoyao-client-android/taoyao/boot/src/main/java/com/acgist/taoyao/boot/model/MessageCodeException.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.acgist.taoyao.boot.model; - -import java.util.Objects; - -/** - * 状态编码异常 - * - * @author acgist - */ -public class MessageCodeException extends RuntimeException { - - private static final long serialVersionUID = 1L; - - /** - * 状态编码 - */ - private final MessageCode code; - - /** - * @param message 异常消息 - * @return 状态编码异常 - */ - public static final MessageCodeException of(String message) { - return of(null, null, message); - } - - /** - * @param t 异常 - * @param message 异常消息 - * @return 状态编码异常 - */ - public static final MessageCodeException of(Throwable t, String message) { - return of(t, null, message); - } - - /** - * @param code 状态编码 - * @param message 异常消息 - * @return 状态编码异常 - */ - public static final MessageCodeException of(MessageCode code, String message) { - return of(null, code, message); - } - - /** - * @param t 异常 - * @param code 状态编码 - * @param message 异常消息 - * @return 状态编码异常 - */ - public static final MessageCodeException of(Throwable t, MessageCode code, String message) { - if (code == null) { - code = MessageCode.CODE_9999; - } - if (message == null || message.isEmpty()) { - message = Objects.isNull(t) ? code.getMessage() : t.getMessage(); - } - return new MessageCodeException(t, code, message); - } - - /** - * @param t 异常 - * @param code 状态编码 - * @param message 异常消息 - */ - public MessageCodeException(Throwable t, MessageCode code, String message) { - super(message, t); - this.code = code; - } - - public MessageCode getCode() { - return this.code; - } - -} diff --git a/taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/utils/IdUtils.java b/taoyao-client-android/taoyao/boot/src/main/java/com/acgist/taoyao/boot/utils/IdUtils.java similarity index 96% rename from taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/utils/IdUtils.java rename to taoyao-client-android/taoyao/boot/src/main/java/com/acgist/taoyao/boot/utils/IdUtils.java index 25cd403..4fd2cb1 100644 --- a/taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/utils/IdUtils.java +++ b/taoyao-client-android/taoyao/boot/src/main/java/com/acgist/taoyao/boot/utils/IdUtils.java @@ -1,4 +1,4 @@ -package com.acgist.taoyao.client.utils; +package com.acgist.taoyao.boot.utils; import java.time.LocalDateTime; diff --git a/taoyao-client-android/taoyao/boot/src/main/java/com/acgist/taoyao/boot/utils/PointerUtils.java b/taoyao-client-android/taoyao/boot/src/main/java/com/acgist/taoyao/boot/utils/PointerUtils.java new file mode 100644 index 0000000..eb93309 --- /dev/null +++ b/taoyao-client-android/taoyao/boot/src/main/java/com/acgist/taoyao/boot/utils/PointerUtils.java @@ -0,0 +1,41 @@ +package com.acgist.taoyao.boot.utils; + +import android.util.Log; + +import org.apache.commons.lang3.reflect.FieldUtils; + +import java.lang.reflect.Field; + +/** + * 指针工具 + * + * @author acgist + */ +public final class PointerUtils { + + private PointerUtils() { + } + + /** + * @param object 对象 + * @param name 指针属性名称 + * + * @return 指针 + */ + public static final long getNativePointer(Object object, String name) { + if(object == null) { + return 0L; + } + try { + final Field reader = FieldUtils.getField(object.getClass(), name, true); + if(reader == null) { + return 0L; + } + return reader.getLong(object); + } catch (Exception e) { + Log.e(PointerUtils.class.getSimpleName(), "获取指针异常", e); + } + return 0L; + } + +} diff --git a/taoyao-client-android/taoyao/client/src/main/AndroidManifest.xml b/taoyao-client-android/taoyao/client/src/main/AndroidManifest.xml index f2613a3..74790f8 100644 --- a/taoyao-client-android/taoyao/client/src/main/AndroidManifest.xml +++ b/taoyao-client-android/taoyao/client/src/main/AndroidManifest.xml @@ -3,7 +3,9 @@ + + + @@ -35,11 +40,13 @@ android:foregroundServiceType="mediaProjection" /> + + 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 7589617..3d7bb2b 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 @@ -2,19 +2,14 @@ package com.acgist.taoyao.client; import android.Manifest; import android.app.Activity; -import android.app.AlarmManager; -import android.app.Application; -import android.app.PendingIntent; -import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.content.res.Resources; import android.media.projection.MediaProjectionManager; import android.os.Bundle; import android.os.Handler; +import android.os.HandlerThread; import android.os.Looper; import android.os.Message; -import android.os.Process; import android.os.SystemClock; import android.util.Log; import android.view.Display; @@ -31,6 +26,7 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import com.acgist.taoyao.client.databinding.ActivityMainBinding; +import com.acgist.taoyao.client.signal.Taoyao; import com.acgist.taoyao.config.Config; import com.acgist.taoyao.media.MediaManager; import com.acgist.taoyao.media.MediaRecorder; @@ -45,7 +41,9 @@ import java.util.stream.Stream; */ public class MainActivity extends AppCompatActivity implements Serializable { + private Handler threadHandler; private MainHandler mainHandler; + private HandlerThread handlerThread; private ActivityMainBinding binding; private MediaProjectionManager mediaProjectionManager; private ActivityResultLauncher activityResultLauncher; @@ -54,27 +52,21 @@ public class MainActivity extends AppCompatActivity implements Serializable { protected void onCreate(Bundle bundle) { Log.i(MainActivity.class.getSimpleName(), "onCreate"); super.onCreate(bundle); - // 全局异常 - this.catchAll(); - // 请求权限 + this.catchAllException(); this.requestPermission(); - // 启动点亮屏幕 - this.setTurnScreenOn(true); - // 锁屏显示屏幕 - this.setShowWhenLocked(true); - // 设置屏幕常亮 - this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - // 拉起媒体服务 this.launchMediaService(); - // 布局 + this.setTurnScreenOn(true); + this.setShowWhenLocked(true); + this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); this.binding = ActivityMainBinding.inflate(this.getLayoutInflater()); - this.binding.getRoot().setZ(100F); this.setContentView(this.binding.getRoot()); + this.binding.getRoot().setZ(100F); + this.buildHandlerThread(); this.registerMediaProjection(); + this.binding.action.setOnClickListener(this::action); this.binding.record.setOnClickListener(this::switchRecord); this.binding.settings.setOnClickListener(this::launchSettings); - // 加载媒体管理 - MediaManager.getInstance().initMedia(this.mainHandler, this.getApplicationContext()); + this.binding.photograph.setOnClickListener(this::photograph); } @Override @@ -95,12 +87,32 @@ public class MainActivity extends AppCompatActivity implements Serializable { super.onDestroy(); } + private void catchAllException() { + Log.i(MainActivity.class.getSimpleName(), "全局异常捕获"); + final Thread.UncaughtExceptionHandler old = Thread.getDefaultUncaughtExceptionHandler(); + Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + Log.e(MediaService.class.getSimpleName(), "系统异常:" + t.getName(), e); + final Looper looper = Looper.myLooper(); + if (looper == Looper.getMainLooper()) { +// if(t.getId() == Looper.getMainLooper().getThread().getId()) { + // TODO:重启应用 + old.uncaughtException(t, e); + } else { + // 子线程 + } + } + }); + } + /** * 请求权限 */ private void requestPermission() { final String[] permissions = new String[]{ Manifest.permission.CAMERA, + Manifest.permission.REBOOT, Manifest.permission.INTERNET, Manifest.permission.RECORD_AUDIO, Manifest.permission.ACCESS_WIFI_STATE, @@ -112,7 +124,7 @@ public class MainActivity extends AppCompatActivity implements Serializable { Manifest.permission.RECEIVE_BOOT_COMPLETED, Manifest.permission.WRITE_EXTERNAL_STORAGE }; - if(Stream.of(permissions).map(this.getApplicationContext()::checkSelfPermission).allMatch(v -> v == PackageManager.PERMISSION_GRANTED)) { + 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); @@ -123,16 +135,18 @@ public class MainActivity extends AppCompatActivity implements Serializable { * 拉起媒体服务 */ private void launchMediaService() { - if(this.mainHandler != null) { + if (this.mainHandler != null) { return; } int waitCount = 0; this.mainHandler = new MainHandler(this); + // 不能写在service里面: Attempt to invoke virtual method 'android.view.View android.view.Window.getDecorView()' on a null object reference + MediaManager.getInstance().initContext(this.mainHandler, this.getApplicationContext()); final Display display = this.getWindow().getContext().getDisplay(); - while(Display.STATE_ON != display.getState() && waitCount++ < 10) { + while (Display.STATE_ON != display.getState() && waitCount++ < 10) { SystemClock.sleep(100); } - if(display.STATE_ON == display.getState()) { + 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()); @@ -143,15 +157,24 @@ public class MainActivity extends AppCompatActivity implements Serializable { } } + private void buildHandlerThread() { + if (this.handlerThread != null) { + return; + } + this.handlerThread = new HandlerThread("MainHandleThread"); + this.handlerThread.start(); + this.threadHandler = new Handler(this.handlerThread.getLooper()); + } + private void registerMediaProjection() { - if(this.activityResultLauncher != null && this.mediaProjectionManager != null) { + if (this.activityResultLauncher != null && this.mediaProjectionManager != null) { return; } this.mediaProjectionManager = this.getApplicationContext().getSystemService(MediaProjectionManager.class); this.activityResultLauncher = this.registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { - if(result.getResultCode() == Activity.RESULT_OK) { + if (result.getResultCode() == Activity.RESULT_OK) { Log.i(MediaManager.class.getSimpleName(), "屏幕捕获成功"); final Intent intent = new Intent(this, MediaService.class); intent.setAction(MediaService.Action.SCREEN_RECORD.name()); @@ -165,12 +188,19 @@ public class MainActivity extends AppCompatActivity implements Serializable { ); } + private void action(View view) { + this.threadHandler.post(() -> { + // 进入房间 + Taoyao.taoyao.enterRoom("d8f1e91c-58d0-4e58-ad67-decc0fd61df2", null); + }); + } + private synchronized void switchRecord(View view) { final MediaRecorder mediaRecorder = MediaRecorder.getInstance(); - if(mediaRecorder.isActive()) { + if (mediaRecorder.isActive()) { mediaRecorder.stop(); } else { - mediaRecorder.record(Resources.getSystem().getString(R.string.storagePathVideo)); + mediaRecorder.record(this.getResources().getString(R.string.storagePathVideo)); } } @@ -184,6 +214,9 @@ public class MainActivity extends AppCompatActivity implements Serializable { this.startActivity(intent); } + private void photograph(View view) { + } + /** * Handler * @@ -201,9 +234,9 @@ public class MainActivity extends AppCompatActivity implements Serializable { public void handleMessage(@NonNull Message message) { super.handleMessage(message); 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_LOCAL_VIDEO -> this.mainActivity.newLocalVideo(message); + switch (message.what) { + case Config.WHAT_SCREEN_CAPTURE -> this.mainActivity.screenCapture(message); + case Config.WHAT_NEW_LOCAL_VIDEO -> this.mainActivity.newLocalVideo(message); case Config.WHAT_NEW_REMOTE_VIDEO -> this.mainActivity.newRemoteVideo(message); } } @@ -240,23 +273,4 @@ public class MainActivity extends AppCompatActivity implements Serializable { this.addContentView(surfaceView, layoutParams); } - private void catchAll() { - Log.i(MainActivity.class.getSimpleName(), "全局异常捕获"); - final Thread.UncaughtExceptionHandler old = Thread.getDefaultUncaughtExceptionHandler(); - Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { - @Override - public void uncaughtException(Thread t, Throwable e) { - Log.e(MediaService.class.getSimpleName(), "系统异常:" + t.getName(), e); - final Looper looper = Looper.myLooper(); - if(looper == Looper.getMainLooper()) { -// if(t.getId() == Looper.getMainLooper().getThread().getId()) { - // TODO:重启应用 - old.uncaughtException(t, e); - } else { - // 子线程 - } - } - }); - } - } \ No newline at end of file diff --git a/taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/MediaService.java b/taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/MediaService.java index 87ac9ba..a710184 100644 --- a/taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/MediaService.java +++ b/taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/MediaService.java @@ -10,9 +10,6 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Resources; import android.graphics.BitmapFactory; -import android.location.LocationManager; -import android.net.wifi.WifiManager; -import android.os.BatteryManager; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -43,6 +40,8 @@ public class MediaService extends Service { */ public enum Action { + // 启动 + LAUNCH, // 连接 CONNECT, // 重连 @@ -69,8 +68,11 @@ public class MediaService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { - Log.i(MediaService.class.getSimpleName(), "onStartCommand"); - if (Action.CONNECT.name().equals(intent.getAction())) { + Log.i(MediaService.class.getSimpleName(), "onStartCommand:" + intent.getAction()); + if (Action.LAUNCH.name().equals(intent.getAction())) { + this.launch(intent); + this.openConnect(intent); + } else if (Action.CONNECT.name().equals(intent.getAction())) { this.openConnect(intent); } else if (Action.RECONNECT.name().equals(intent.getAction())) { this.reconnect(); @@ -89,6 +91,24 @@ public class MediaService extends Service { this.close(); } + private void launch(Intent intent) { + final Intent notificationIntent = new Intent(this, MediaService.class); + final PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); + final NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, "NOTIFICATION_CHANNEL_ID") + .setSmallIcon(R.mipmap.ic_launcher_foreground) + .setLargeIcon(BitmapFactory.decodeResource(this.getResources(), R.mipmap.ic_launcher_foreground)) + .setTicker("NOTIFICATION_TICKER") + .setContentTitle("屏幕录制") + .setContentText("屏幕录制共享") + .setContentIntent(pendingIntent); + final Notification notification = notificationBuilder.build(); + final NotificationChannel channel = new NotificationChannel("NOTIFICATION_CHANNEL_ID", "NOTIFICATION_CHANNEL_NAME", NotificationManager.IMPORTANCE_DEFAULT); + channel.setDescription("NOTIFICATION_CHANNEL_DESC"); + final NotificationManager notificationManager = this.getSystemService(NotificationManager.class); + notificationManager.createNotificationChannel(channel); + this.startForeground((int) System.currentTimeMillis(), notification); + } + private void openConnect(Intent intent) { if (this.taoyao == null) { Log.d(MediaService.class.getSimpleName(), "打开信令连接"); @@ -119,17 +139,13 @@ public class MediaService extends Service { final Resources resources = this.getResources(); // 系统服务 final Context context = this.getApplicationContext(); - final WifiManager wifiManager = context.getSystemService(WifiManager.class); - final BatteryManager batteryManager = context.getSystemService(BatteryManager.class); - final LocationManager locationManager = context.getSystemService(LocationManager.class); this.close(); // 连接信令 this.taoyao = new Taoyao( port, host, resources.getString(R.string.version), name, clientId, resources.getString(R.string.clientType), username, password, resources.getInteger(R.integer.timeout), resources.getString(R.string.encrypt), resources.getString(R.string.encryptSecret), - this.mainHandler, context, - wifiManager, batteryManager, locationManager + this.mainHandler, context ); Toast.makeText(this.getApplicationContext(), "连接信令", Toast.LENGTH_SHORT).show(); } diff --git a/taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/BootReceiver.java b/taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/TaoyaoReceiver.java similarity index 61% rename from taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/BootReceiver.java rename to taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/TaoyaoReceiver.java index a6c3745..c5557ac 100644 --- a/taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/BootReceiver.java +++ b/taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/TaoyaoReceiver.java @@ -3,7 +3,6 @@ package com.acgist.taoyao.client; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.content.res.Resources; import android.util.Log; /** @@ -11,16 +10,15 @@ import android.util.Log; * * @author acgist */ -public class BootReceiver extends BroadcastReceiver { +public class TaoyaoReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - Log.i(BootReceiver.class.getSimpleName(), "onReceive"); - final Resources resources = context.getResources(); + Log.i(TaoyaoReceiver.class.getSimpleName(), "onReceive:" + intent.getAction()); if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { - if(resources.getBoolean(R.bool.preview)) { - this.launchPreview(context); - } + this.launchPreview(context); + } else { + // TODO:重启关机释放资源(录像) } } @@ -30,8 +28,9 @@ public class BootReceiver extends BroadcastReceiver { * @param context 上下文 */ private void launchPreview(Context context) { - final Intent intent = new Intent(context, MainActivity.class); + final Intent intent = new Intent(context, MediaService.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setAction(MediaService.Action.LAUNCH.name()); context.startForegroundService(intent); } diff --git a/taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/signal/Taoyao.java b/taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/signal/Taoyao.java index 9944cb8..b20f1a8 100644 --- a/taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/signal/Taoyao.java +++ b/taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/signal/Taoyao.java @@ -3,6 +3,7 @@ package com.acgist.taoyao.client.signal; import android.Manifest; import android.content.Context; import android.content.pm.PackageManager; +import android.content.res.Resources; import android.location.Criteria; import android.location.Location; import android.location.LocationManager; @@ -11,6 +12,7 @@ import android.net.wifi.WifiManager; import android.os.BatteryManager; import android.os.Handler; import android.os.HandlerThread; +import android.os.PowerManager; import android.os.Process; import android.util.Log; @@ -18,13 +20,13 @@ import androidx.core.app.ActivityCompat; import com.acgist.taoyao.boot.model.Header; import com.acgist.taoyao.boot.model.Message; -import com.acgist.taoyao.boot.model.MessageCode; -import com.acgist.taoyao.boot.model.MessageCodeException; import com.acgist.taoyao.boot.utils.CloseableUtils; +import com.acgist.taoyao.boot.utils.IdUtils; import com.acgist.taoyao.boot.utils.JSONUtils; import com.acgist.taoyao.boot.utils.MapUtils; -import com.acgist.taoyao.client.utils.IdUtils; +import com.acgist.taoyao.client.R; import com.acgist.taoyao.config.MediaProperties; +import com.acgist.taoyao.media.MediaManager; import com.acgist.taoyao.media.MediaRecorder; import com.acgist.taoyao.media.Room; import com.acgist.taoyao.media.SessionClient; @@ -133,6 +135,10 @@ public final class Taoyao implements ITaoyao { * Wifi管理器 */ private final WifiManager wifiManager; + /** + * 电源管理 + */ + private final PowerManager powerManager; /** * 电池管理器 */ @@ -151,6 +157,8 @@ public final class Taoyao implements ITaoyao { private final HandlerThread heartbeatThread; private final Handler executeMessageHandler; private final HandlerThread executeMessageThread; + private final MediaManager mediaManager; + private final MediaRecorder mediaRecorder; /** * 媒体配置 */ @@ -163,13 +171,13 @@ public final class Taoyao implements ITaoyao { * 会话终端列表 */ private final Map sessionClients; + public static Taoyao taoyao; public Taoyao( int port, String host, String version, String name, String clientId, String clientType, String username, String password, int timeout, String algo, String secret, - Handler mainHandler, Context context, - WifiManager wifiManager, BatteryManager batteryManager, LocationManager locationManager + Handler mainHandler, Context context ) { this.close = false; this.connect = false; @@ -187,9 +195,10 @@ public final class Taoyao implements ITaoyao { this.decrypt = plaintext ? null : this.buildCipher(Cipher.DECRYPT_MODE, algo, secret); this.mainHandler = mainHandler; this.context = context; - this.wifiManager = wifiManager; - this.batteryManager = batteryManager; - this.locationManager = locationManager; + this.wifiManager = context.getSystemService(WifiManager.class); + this.powerManager = context.getSystemService(PowerManager.class); + this.batteryManager = context.getSystemService(BatteryManager.class); + this.locationManager = context.getSystemService(LocationManager.class); this.requestMessage = new ConcurrentHashMap<>(); this.loopMessageThread = new HandlerThread("TaoyaoLoopMessageThread"); this.loopMessageThread.start(); @@ -202,8 +211,11 @@ public final class Taoyao implements ITaoyao { this.executeMessageThread = new HandlerThread("TaoyaoExecuteMessageThread"); this.executeMessageThread.start(); this.executeMessageHandler = new Handler(this.executeMessageThread.getLooper()); + this.mediaManager = MediaManager.getInstance(); + this.mediaRecorder = MediaRecorder.getInstance(); this.rooms = new ConcurrentHashMap<>(); this.sessionClients = new ConcurrentHashMap<>(); + Taoyao.taoyao = this; } /** @@ -306,14 +318,12 @@ public final class Taoyao implements ITaoyao { buffer.get(message); buffer.compact(); final String content = new String(this.decrypt.doFinal(message)); - this.executeMessageHandler.post(() -> { - try { - Log.d(Taoyao.class.getSimpleName(), "处理信令:" + content); - Taoyao.this.on(content); - } catch (Exception e) { - Log.e(Taoyao.class.getSimpleName(), "处理信令异常:" + content, e); - } - }); + try { + Log.d(Taoyao.class.getSimpleName(), "处理信令:" + content); + Taoyao.this.on(content); + } catch (Exception e) { + Log.e(Taoyao.class.getSimpleName(), "处理信令异常:" + content, e); + } } } } @@ -388,7 +398,7 @@ public final class Taoyao implements ITaoyao { final Message response = this.requestMessage.remove(id); if (response == null || request.equals(response)) { Log.w(Taoyao.class.getSimpleName(), "请求信令没有响应:" + request); - throw MessageCodeException.of(MessageCode.CODE_2001, "请求信令没有响应"); + return null; } return response; } @@ -482,15 +492,26 @@ public final class Taoyao implements ITaoyao { request.notifyAll(); } } else { - final Map body = message.body(); - switch (header.getSignal()) { - case "client::config" -> this.clientConfig(message, body); - case "client::register" -> this.clientRegister(message, body); - case "session::call" -> this.sessionCall(message, body); - case "session::close" -> this.sessionClose(message, body); - case "session::exchange" -> this.sessionExchange(message, body); - default -> Log.d(Taoyao.class.getSimpleName(), "没有适配信令:" + content); - } + this.executeMessageHandler.post(() -> this.dispatch(content, header, message)); + } + } + + private void dispatch(final String content, final Header header, final Message message) { + final Map body = message.body(); + switch (header.getSignal()) { + case "client::config" -> this.clientConfig(message, body); + case "client::register" -> this.clientRegister(message, body); + case "client::reboot" -> this.clientReboot(message, body); + case "client::shutdown" -> this.clientShutdown(message, body); +// case "room::close" -> this.roomClose(message, body); +// case "room::enter" -> this.roomEnter(message, body); +// case "room::expel" -> this.roomExpel(message, body); + case "room::invite" -> this.roomInivte(message, body); +// case "room::leave" -> this.roomLeave(message, body); + case "session::call" -> this.sessionCall(message, body); + case "session::close" -> this.sessionClose(message, body); + case "session::exchange" -> this.sessionExchange(message, body); + default -> Log.d(Taoyao.class.getSimpleName(), "没有适配信令:" + content); } } @@ -535,11 +556,40 @@ public final class Taoyao implements ITaoyao { IdUtils.setClientIndex(index); } - private void clientClose() { -// PowerManager manager = (PowerManager)this.getSystemService(Context.POWER_SERVICE); -// manager.reboot("重新启动系统") -// Process.killProcess(Process.myPid()); -// System.exit(0); + private void clientReboot(Message message, Map body) { + Log.i(Taoyao.class.getSimpleName(), "系统重启"); +// this.powerManager.reboot("系统重启"); + Process.killProcess(Process.myPid()); + } + + private void clientShutdown(Message message, Map body) { + Log.i(Taoyao.class.getSimpleName(), "系统关机"); + // 自行实现 +// this.powerManager.reboot("系统关机"); + Process.killProcess(Process.myPid()); + } + + private void roomInivte(Message message, Map body) { + final String roomId = MapUtils.get(body, "roomId"); + final String password = MapUtils.get(body, "password"); + final Room room = this.enterRoom(roomId, password); + room.produceMedia(); + } + + public Room enterRoom(String roomId, String password) { + final Resources resources = this.context.getResources(); + final Room room = this.rooms.computeIfAbsent( + roomId, + key -> new Room( + key, password, + resources.getBoolean(R.bool.audioConsume), + resources.getBoolean(R.bool.videoConsume), + resources.getBoolean(R.bool.audioProduce), + resources.getBoolean(R.bool.videoProduce), + this.mediaManager.nativeNewRoom(key), this) + ); + room.enter(); + return room; } private void sessionCall(Message message, Map body) { diff --git a/taoyao-client-android/taoyao/client/src/main/res/drawable/action.xml b/taoyao-client-android/taoyao/client/src/main/res/drawable/action.xml new file mode 100644 index 0000000..30cc6be --- /dev/null +++ b/taoyao-client-android/taoyao/client/src/main/res/drawable/action.xml @@ -0,0 +1,5 @@ + + + diff --git a/taoyao-client-android/taoyao/client/src/main/res/drawable/photograph.xml b/taoyao-client-android/taoyao/client/src/main/res/drawable/photograph.xml new file mode 100644 index 0000000..43a4fd5 --- /dev/null +++ b/taoyao-client-android/taoyao/client/src/main/res/drawable/photograph.xml @@ -0,0 +1,6 @@ + + + + diff --git a/taoyao-client-android/taoyao/client/src/main/res/layout/activity_main.xml b/taoyao-client-android/taoyao/client/src/main/res/layout/activity_main.xml index 460b74f..4be9000 100644 --- a/taoyao-client-android/taoyao/client/src/main/res/layout/activity_main.xml +++ b/taoyao-client-android/taoyao/client/src/main/res/layout/activity_main.xml @@ -7,6 +7,34 @@ android:layout_height="match_parent" tools:context="com.acgist.taoyao.client.MainActivity"> + + + + /taoyao/video WEBRTC + + true + true + true + true \ No newline at end of file diff --git a/taoyao-client-android/taoyao/client/src/main/res/values/strings.xml b/taoyao-client-android/taoyao/client/src/main/res/values/strings.xml index e86f118..1283510 100644 --- a/taoyao-client-android/taoyao/client/src/main/res/values/strings.xml +++ b/taoyao-client-android/taoyao/client/src/main/res/values/strings.xml @@ -3,7 +3,9 @@ 桃夭 桃夭终端预览 桃夭终端设置 - 录制 + 拍照 + 动作 + 录像 设置 连接 信令端口 diff --git a/taoyao-client-android/taoyao/media/CMakeLists.txt b/taoyao-client-android/taoyao/media/CMakeLists.txt index eea198a..c532cc2 100644 --- a/taoyao-client-android/taoyao/media/CMakeLists.txt +++ b/taoyao-client-android/taoyao/media/CMakeLists.txt @@ -26,6 +26,8 @@ set( set( SOURCE_FILES ${SOURCE_DIR}/include/LocalClient.hpp + ${SOURCE_DIR}/include/Log.hpp + ${SOURCE_DIR}/include/MediaManager.hpp ${SOURCE_DIR}/include/MediaRecorder.hpp ${SOURCE_DIR}/include/RemoteClient.hpp ${SOURCE_DIR}/include/Room.hpp @@ -37,6 +39,7 @@ set( ${SOURCE_DIR}/rtp/RtpClient.cpp ${SOURCE_DIR}/rtp/RtpVideoPublisher.cpp ${SOURCE_DIR}/webrtc/LocalClient.cpp + ${SOURCE_DIR}/webrtc/MediaManager.cpp ${SOURCE_DIR}/webrtc/MediaRecorder.cpp ${SOURCE_DIR}/webrtc/RemoteClient.cpp ${SOURCE_DIR}/webrtc/Room.cpp diff --git a/taoyao-client-android/taoyao/media/src/main/cpp/include/LocalClient.hpp b/taoyao-client-android/taoyao/media/src/main/cpp/include/LocalClient.hpp index f7883f8..3ef43c3 100644 --- a/taoyao-client-android/taoyao/media/src/main/cpp/include/LocalClient.hpp +++ b/taoyao-client-android/taoyao/media/src/main/cpp/include/LocalClient.hpp @@ -1,5 +1,26 @@ #pragma once +#include + +#include "jni.h" +#include "mediasoupclient.hpp" + namespace acgist { + class LocalClient { + public: + std::string name; + std::string clientId; + mediasoupclient::Producer* producer; + public: + jmethodID newCallback; + jmethodID pauseCallback; + jmethodID resumeCallback; + jmethodID closeCallback; + public: + void pause(); + void resume(); + void close(); + }; + } \ No newline at end of file diff --git a/taoyao-client-android/taoyao/media/src/main/cpp/include/Log.hpp b/taoyao-client-android/taoyao/media/src/main/cpp/include/Log.hpp new file mode 100644 index 0000000..d9ce581 --- /dev/null +++ b/taoyao-client-android/taoyao/media/src/main/cpp/include/Log.hpp @@ -0,0 +1,13 @@ +#pragma once + +namespace acgist { + +#include "android/log.h" + +#define LOG_TAG "libtaoyao" + +#define LOG_D(message, ...) acgist::__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, message, __VA_ARGS__) +#define LOG_I(message, ...) acgist::__android_log_print(ANDROID_LOG_INFO, LOG_TAG, message, __VA_ARGS__) +#define LOG_W(message, ...) acgist::__android_log_print(ANDROID_LOG_WARN, LOG_TAG, message, __VA_ARGS__) +#define LOG_E(message, ...) acgist::__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, message, __VA_ARGS__) +} diff --git a/taoyao-client-android/taoyao/media/src/main/cpp/include/MediaManager.hpp b/taoyao-client-android/taoyao/media/src/main/cpp/include/MediaManager.hpp new file mode 100644 index 0000000..1178352 --- /dev/null +++ b/taoyao-client-android/taoyao/media/src/main/cpp/include/MediaManager.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +#include "jni.h" +#include "Log.hpp" +#include "mediasoupclient.hpp" + +namespace acgist { + + jmethodID closeCallback; + +} \ No newline at end of file diff --git a/taoyao-client-android/taoyao/media/src/main/cpp/include/RemoteClient.hpp b/taoyao-client-android/taoyao/media/src/main/cpp/include/RemoteClient.hpp index f7883f8..dd314ea 100644 --- a/taoyao-client-android/taoyao/media/src/main/cpp/include/RemoteClient.hpp +++ b/taoyao-client-android/taoyao/media/src/main/cpp/include/RemoteClient.hpp @@ -1,5 +1,26 @@ #pragma once +#include + +#include "jni.h" +#include "mediasoupclient.hpp" + namespace acgist { + class RemoteClient { + public: + std::string name; + std::string clientId; + mediasoupclient::Consumer* consumer; + public: + jmethodID newCallback; + jmethodID pauseCallback; + jmethodID resumeCallback; + jmethodID closeCallback; + public: + void pause(); + void resume(); + void close(); + }; + } \ No newline at end of file diff --git a/taoyao-client-android/taoyao/media/src/main/cpp/include/Room.hpp b/taoyao-client-android/taoyao/media/src/main/cpp/include/Room.hpp index cf1e20d..496fc90 100644 --- a/taoyao-client-android/taoyao/media/src/main/cpp/include/Room.hpp +++ b/taoyao-client-android/taoyao/media/src/main/cpp/include/Room.hpp @@ -1,15 +1,44 @@ #pragma once +#include +#include +#include + +#include "jni.h" +#include "Log.hpp" +#include "LocalClient.hpp" +#include "RemoteClient.hpp" #include "mediasoupclient.hpp" +#include "sdk/android/src/jni/pc/peer_connection.h" +#include "sdk/android/native_api/jni/scoped_java_ref.h" + namespace acgist { -class Room { -public: -// static mediasoupclient::Device * pDevice; -public: - Room(); - virtual ~Room(); -}; + class Room { + public: + mediasoupclient::Device* device; + mediasoupclient::PeerConnection* peerConnection; + acgist::LocalClient* localClient; + std::map remoteClients; + jstring roomId; + public: + /** + * 新建Transport回调 + */ + jmethodID newCallback; + /** + * 房间关闭回调 + */ + jmethodID closeCallback; + public: + Room(jstring roomId); + virtual ~Room(); + public: + void load(std::string rtpCapabilities, webrtc::PeerConnectionFactoryInterface* factory, webrtc::PeerConnectionInterface::RTCConfiguration& configuration); + void closeLocalClient(); + void closeRemoteClient(); + void close(); + }; } \ No newline at end of file diff --git a/taoyao-client-android/taoyao/media/src/main/cpp/webrtc/LocalClient.cpp b/taoyao-client-android/taoyao/media/src/main/cpp/webrtc/LocalClient.cpp index 82b4b50..e63762a 100644 --- a/taoyao-client-android/taoyao/media/src/main/cpp/webrtc/LocalClient.cpp +++ b/taoyao-client-android/taoyao/media/src/main/cpp/webrtc/LocalClient.cpp @@ -1,5 +1,3 @@ -#include - #include "LocalClient.hpp" namespace acgist { diff --git a/taoyao-client-android/taoyao/media/src/main/cpp/webrtc/MediaManager.cpp b/taoyao-client-android/taoyao/media/src/main/cpp/webrtc/MediaManager.cpp new file mode 100644 index 0000000..fd64c51 --- /dev/null +++ b/taoyao-client-android/taoyao/media/src/main/cpp/webrtc/MediaManager.cpp @@ -0,0 +1,25 @@ +#include "Room.hpp" +#include "MediaManager.hpp" + +namespace acgist { + +extern "C" JNIEXPORT void JNICALL Java_com_acgist_taoyao_media_MediaManager_nativeInit(JNIEnv * env, jobject me) { + LOG_I("加载MediasoupClient:", mediasoupclient::Version().data()); + mediasoupclient::Initialize(); + // => { spatialLayers: 2, temporalLayers: 3 } +// mediasoupclient::parseScalabilityMode("L2T3"); + // => { spatialLayers: 4, temporalLayers: 7 } +// mediasoupclient::parseScalabilityMode("L4T7_KEY_SHIFT"); +} + +extern "C" JNIEXPORT void JNICALL Java_com_acgist_taoyao_media_MediaManager_nativeStop(JNIEnv * env, jobject me) { + std::cout << "释放mediasoupclient" << std::endl; + mediasoupclient::Cleanup(); +} + +extern "C" JNIEXPORT jlong JNICALL Java_com_acgist_taoyao_media_MediaManager_nativeNewRoom(JNIEnv * env, jobject me, jstring roomId) { + const Room* room = new Room(roomId); + return (jlong) room; +} + +} diff --git a/taoyao-client-android/taoyao/media/src/main/cpp/webrtc/Room.cpp b/taoyao-client-android/taoyao/media/src/main/cpp/webrtc/Room.cpp index 3786617..bbcd2de 100644 --- a/taoyao-client-android/taoyao/media/src/main/cpp/webrtc/Room.cpp +++ b/taoyao-client-android/taoyao/media/src/main/cpp/webrtc/Room.cpp @@ -1,36 +1,83 @@ -#include - #include "Room.hpp" namespace acgist { -Room::Room() { -} + Room::Room(jstring roomId) { + this->roomId = roomId; + this->device = new mediasoupclient::Device(); + } -Room::~Room() { -} + Room::~Room() { + } + + void Room::load(std::string rtpCapabilities, webrtc::PeerConnectionFactoryInterface* factory, webrtc::PeerConnectionInterface::RTCConfiguration& configuration) { + // TODO:PeerConnectionFactory复用 + nlohmann::json json; + mediasoupclient::PeerConnection::Options options; + options.config = configuration; + options.factory = factory; + json["routerRtpCapabilities"] = nlohmann::json::parse(rtpCapabilities); + this->device->Load(json, &options); + } + + void Room::close() { + delete this->device; + this->device = nullptr; + } + + extern "C" JNIEXPORT void JNICALL Java_com_acgist_taoyao_media_Room_nativeLoad( + JNIEnv* env, jobject me, + jlong nativePointer, jstring rtpCapabilities, + jlong factoryPointer, jobject configuration + ) { + Room *room = (Room *) nativePointer; + webrtc::PeerConnectionInterface::RTCConfiguration rtcConfiguration(webrtc::PeerConnectionInterface::RTCConfigurationType::kAggressive); + // TODO:为什么不能转换 + jobject configurationGlobal = webrtc::jni::NewGlobalRef(env, configuration); + webrtc::JavaParamRef configurationRef(configurationGlobal); +// webrtc::jni::JavaToNativeMediaConstraints() + webrtc::jni::JavaToNativeRTCConfiguration(env, webrtc::JavaParamRef(env, configurationGlobal), &rtcConfiguration); + webrtc::jni::DeleteGlobalRef(env, configurationGlobal); + const char* routerRtpCapabilities = env->GetStringUTFChars(rtpCapabilities, 0); + room->load( + routerRtpCapabilities, + reinterpret_cast(factoryPointer), +// (webrtc::PeerConnectionFactoryInterface*) factoryPointer, + rtcConfiguration + ); +// env->ReleaseStringUTFChars(rtpCapabilities, routerRtpCapabilities); +// delete routerRtpCapabilities; + } + + extern "C" JNIEXPORT void JNICALL Java_com_acgist_taoyao_media_Room_nativeNewClient(JNIEnv * env, jobject me) { + + } + + extern "C" JNIEXPORT void JNICALL Java_com_acgist_taoyao_media_Room_nativeCloseClient(JNIEnv * env, jobject me) { + } + + extern "C" JNIEXPORT void JNICALL Java_com_acgist_taoyao_media_Room_nativeCloseRoom(JNIEnv * env, jobject me, jlong nativePointer) { +// JNIEXPORT void JNICALL +// Java_nativeMethod +// (JNIEnv *env, jobject thiz) { +// MyCPlusObj *obj = new MyCPlusObj(); +// jclass clazz = (jclass)(*env).GetObjectClass(thiz); +// jfieldID fid = (jfieldID)(*env).GetFieldID(clazz, "mObj", "I"); +// (*env).SetIntField(thiz, fid, (jint)obj); +// } + +// jclass objClazz = (jclass)env->GetObjectClass(obj);//obj为对应的JAVA对象 +// jfieldID fid = env->GetFieldID(objClazz, "mObj", "I"); +// jlong p = (jlong)env->GetObjectField(obj, fid); +// MyCPlusObj *cPlusObj = (MyCPlusObj *)p; +////cPlusObj 为JAVA对象对应的C++对象 + +// jobject gThiz = (jobject)env->NewGlobalRef(thiz);//thiz为JAVA对象 +// (*obj).javaObj = (jint)gThiz; + + Room* room = (Room*) nativePointer; + room->close(); + delete room; + } } - -void init() { - std::cout << "加载MediasoupClient:" << mediasoupclient::Version() << std::endl; - std::cout << "加载libwebrtc" << std::endl; - mediasoupclient::Initialize(); -// mediasoupclient::parseScalabilityMode("L2T3"); - // => { spatialLayers: 2, temporalLayers: 3 } -// mediasoupclient::parseScalabilityMode("L4T7_KEY_SHIFT"); - // => { spatialLayers: 4, temporalLayers: 7 } -} - -void load() { - // TODO:JNI信令交互 -// if(acgist::Room::pDevice == nullptr) { -// acgist::Room::pDevice = new mediasoupclient::Device(); -// acgist::Room::pDevice->Load(); -// } -} - -void stop() { - std::cout << "释放libwebrtc" << std::endl; - mediasoupclient::Cleanup(); -} \ No newline at end of file diff --git a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/RouterCallback.java b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/RouterCallback.java new file mode 100644 index 0000000..bb94691 --- /dev/null +++ b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/RouterCallback.java @@ -0,0 +1,16 @@ +package com.acgist.taoyao; + +/** + * 路由回调 + */ +public interface RouterCallback { + + void enterCallback(); + void newRemoteClientCallback(); + void closeRemoteClientCallback(); + void consumerPauseCallback(); + void consumerResumeCallback(); + void producerPauseCallback(); + void producerResumeCallback(); + +} diff --git a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/LocalClient.java b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/LocalClient.java index 17410f7..f90e3df 100644 --- a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/LocalClient.java +++ b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/LocalClient.java @@ -16,7 +16,13 @@ public class LocalClient extends RoomClient { */ public enum TransportType { + /** + * RTP + */ RTP, + /** + * WebRTC + */ WEBRTC; } 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 6cb2400..cff1568 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 @@ -2,6 +2,8 @@ package com.acgist.taoyao.media; import android.content.Context; import android.content.Intent; +import android.media.MediaCodecInfo; +import android.media.MediaCodecList; import android.media.projection.MediaProjection; import android.os.Handler; import android.os.Looper; @@ -28,8 +30,6 @@ import org.webrtc.SurfaceViewRenderer; import org.webrtc.VideoCapturer; import org.webrtc.VideoDecoderFactory; import org.webrtc.VideoEncoderFactory; -import org.webrtc.VideoFrame; -import org.webrtc.VideoSink; import org.webrtc.VideoSource; import org.webrtc.VideoTrack; import org.webrtc.audio.JavaAudioDeviceModule; @@ -37,12 +37,14 @@ import org.webrtc.audio.JavaAudioDeviceModule; import java.util.Arrays; import java.util.Iterator; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; /** * 媒体来源管理器 * * @author acgist - * + *

* https://zhuanlan.zhihu.com/p/82446482 * https://www.jianshu.com/p/97acd9a51909 * https://juejin.cn/post/7036308428305727519 @@ -53,29 +55,37 @@ import java.util.List; * https://blog.csdn.net/u011418943/article/details/127108642 * https://blog.csdn.net/csdn_shen0221/article/details/120331004 * https://blog.csdn.net/csdn_shen0221/article/details/119982257 - * + *

* TODO:动态码率(BITRATE_MODE_VBR、BITRATE_MODE) */ public class MediaManager { /** - * 来源类型 + * 视频来源类型 * * @author acgist */ public enum Type { - // 文件共享 + /** + * 文件共享:FileVideoCapturer + */ FILE, - // 后置摄像头 + /** + * 后置摄像头:CameraVideoCapturer + */ BACK, - // 前置摄像头 + /** + * 前置摄像头:CameraVideoCapturer + */ FRONT, - // 屏幕共享 + /** + * 屏幕共享:ScreenCapturerAndroid + */ SCREEN; /** - * @return 是否摄像头 + * @return 是否是摄像头 */ public boolean isCamera() { return this == BACK || this == FRONT; @@ -89,9 +99,12 @@ public class MediaManager { return INSTANCE; } + /** + * 当前终端数量 + */ private volatile int clientCount; /** - * 视频类型 + * 视频来源类型 */ private Type type; /** @@ -103,31 +116,28 @@ public class MediaManager { */ private Context context; /** - * 本地终端 - */ - private LocalClient localClient; - /** - * + * EGL */ private EglBase eglBase; /** - * 媒体流:声音、主码流(预览流)、次码流 + * 媒体流:声音、视频 */ private MediaStream mediaStream; /** * 视频捕获 - * FileVideoCapturer - * CameraVideoCapturer - * ScreenCapturerAndroid */ private VideoCapturer videoCapturer; /** - * Peer连接工厂 + * PeerConnectionFactory */ private PeerConnectionFactory peerConnectionFactory; + /** + * 媒体录制 + */ + private final MediaRecorder mediaRecorder; static { - // 设置采样 + // 采样 // WebRtcAudioUtils.setDefaultSampleRateHz(); // 噪声消除 // WebRtcAudioUtils.setWebRtcBasedNoiseSuppressor(true); @@ -135,25 +145,59 @@ public class MediaManager { // WebRtcAudioUtils.setWebRtcBasedAcousticEchoCanceler(true); // 自动增益 // WebRtcAudioUtils.setWebRtcBasedAutomaticGainControl(true); - // 使用OpenSL ES + // OpenSL ES // WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(true); + final MediaCodecList mediaCodecList = new MediaCodecList(-1); + for (MediaCodecInfo mediaCodecInfo : mediaCodecList.getCodecInfos()) { +// if (mediaCodecInfo.isEncoder()) { + final String[] supportedTypes = mediaCodecInfo.getSupportedTypes(); + Log.d(MediaRecorder.class.getSimpleName(), "编码器名称:" + mediaCodecInfo.getName()); + Log.d(MediaRecorder.class.getSimpleName(), "编码器类型:" + String.join(" , ", supportedTypes)); + for (String supportType : supportedTypes) { + final MediaCodecInfo.CodecCapabilities codecCapabilities = mediaCodecInfo.getCapabilitiesForType(supportType); + final int[] colorFormats = codecCapabilities.colorFormats; + Log.d(MediaRecorder.class.getSimpleName(), "编码器格式:" + codecCapabilities.getMimeType()); +// MediaCodecInfo.CodecCapabilities.COLOR_* + Log.d(MediaRecorder.class.getSimpleName(), "编码器支持格式:" + IntStream.of(colorFormats).boxed().map(String::valueOf).collect(Collectors.joining(" , "))); + } +// } + } } private MediaManager() { this.clientCount = 0; + this.mediaRecorder = MediaRecorder.getInstance(); + } + + /** + * 初始化上下文 + * + * @param handler Handler + * @param context 上下文 + */ + public void initContext(Handler handler, Context context) { + this.handler = handler; + this.context = context; + PeerConnectionFactory.initialize( + PeerConnectionFactory.InitializationOptions.builder(this.context) +// .setEnableInternalTracer(true) + .createInitializationOptions() + ); } /** * 新建终端 + * 第一个终端进入时没有初始化时,初始化所有资源。 * - * @param type 视频类型:第一个终端进入有效 + * @param type 视频类型 * - * @return PeerConnectionFactory + * @return PeerConnectionFactory PeerConnectionFactory */ public PeerConnectionFactory newClient(Type type) { synchronized (this) { - if(this.clientCount <= 0) { + if (this.clientCount <= 0 && !MediaRecorder.getInstance().isActive()) { this.initMedia(type); + this.nativeInit(); } this.clientCount++; } @@ -162,36 +206,48 @@ public class MediaManager { /** * 关闭一个终端 + * 最后一个终端关闭时,释放所有资源。 * * @return 剩余终端数量 */ public int closeClient() { synchronized (this) { this.clientCount--; - if(this.clientCount <= 0) { + if (this.clientCount <= 0) { this.close(); + this.nativeStop(); } return this.clientCount; } } - public void initMedia(Handler handler, Context context) { - this.handler = handler; - this.context = context; + public void initRecord(Type type) { + synchronized (this) { + if(this.clientCount <= 0 && !MediaRecorder.getInstance().isActive()) { + this.initMedia(type); + this.nativeInit(); + } + } + } + + public void stopRecord() { + synchronized (this) { + if(this.clientCount <= 0) { + this.close(); + this.nativeStop(); + } + } } /** - * 加载媒体流 + * 加载媒体 + * + * @param type 视频来源类型 */ - public void initMedia(Type type) { + private void initMedia(Type type) { Log.i(MediaManager.class.getSimpleName(), "加载媒体:" + type); this.type = type; this.eglBase = EglBase.create(); - PeerConnectionFactory.initialize( - PeerConnectionFactory.InitializationOptions.builder(this.context) -// .setEnableInternalTracer(true) - .createInitializationOptions() - ); final VideoDecoderFactory videoDecoderFactory = new DefaultVideoDecoderFactory(this.eglBase.getEglBaseContext()); final VideoEncoderFactory videoEncoderFactory = new DefaultVideoEncoderFactory(this.eglBase.getEglBaseContext(), true, true); final JavaAudioDeviceModule javaAudioDeviceModule = JavaAudioDeviceModule.builder(this.context) @@ -223,22 +279,21 @@ public class MediaManager { * 切换视频来源 * * @param type 来源类型 - * - * TODO:设置分享 */ public void exchange(Type type) { - if(this.type == type) { + if (this.type == type) { return; } this.type = type; Log.i(MediaManager.class.getSimpleName(), "设置视频来源:" + type); - if(this.type.isCamera() && type.isCamera()) { + if (this.type.isCamera() && type.isCamera()) { // TODO:测试是否需要完全重置 final CameraVideoCapturer cameraVideoCapturer = (CameraVideoCapturer) this.videoCapturer; cameraVideoCapturer.switchCamera(new CameraVideoCapturer.CameraSwitchHandler() { @Override public void onCameraSwitchDone(boolean success) { } + @Override public void onCameraSwitchError(String message) { } @@ -283,40 +338,44 @@ public class MediaManager { */ private void initVideo() { this.closeVideoTrack(); - if(this.videoCapturer != null) { + if (this.videoCapturer != null) { this.videoCapturer.dispose(); } - 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); - } - } - this.initVideoTrack(); - } else if(this.type == Type.FILE) { - Log.i(MediaManager.class.getSimpleName(), "加载视频(文件)"); - } else if(this.type == Type.SCREEN) { - Log.i(MediaManager.class.getSimpleName(), "加载视频(录屏)"); + Log.i(MediaManager.class.getSimpleName(), "加载视频:" + this.type); + if (this.type.isCamera()) { + this.initCamera(); + } else if (this.type == Type.FILE) { + // 文件 + } else if (this.type == Type.SCREEN) { final Message message = new Message(); message.what = Config.WHAT_SCREEN_CAPTURE; this.handler.sendMessage(message); + } else { + // 其他类型 } } + private void initCamera() { + final CameraEnumerator cameraEnumerator = new Camera2Enumerator(this.context); + final String[] names = cameraEnumerator.getDeviceNames(); + for (String name : names) { + if (this.type == Type.FRONT && cameraEnumerator.isFrontFacing(name)) { + this.videoCapturer = cameraEnumerator.createCapturer(name, new MediaCameraEventsHandler()); + } else if (this.type == Type.BACK && cameraEnumerator.isBackFacing(name)) { + this.videoCapturer = cameraEnumerator.createCapturer(name, new MediaCameraEventsHandler()); + } else { + // 忽略其他摄像头 + } + } + this.initVideoTrack(); + } + public void screenRecord(Intent intent) { this.videoCapturer = new ScreenCapturerAndroid(intent, new MediaProjection.Callback() { @Override public void onStop() { - super.onStop(); - Log.i(MediaManager.class.getSimpleName(), "停止屏幕捕获"); + super.onStop(); + Log.i(MediaManager.class.getSimpleName(), "停止屏幕捕获"); } }); this.initVideoTrack(); @@ -326,7 +385,7 @@ public class MediaManager { * 加载视频 */ private void initVideoTrack() { - final SurfaceViewRenderer surfaceViewRenderer = this.preview(); + final SurfaceViewRenderer surfaceViewRenderer = this.localPreview(); // 加载视频 final SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create("MediaVideoThread", this.eglBase.getEglBaseContext()); // surfaceTextureHelper.setTextureSize(); @@ -338,7 +397,7 @@ public class MediaManager { this.videoCapturer.startCapture(480, 640, 30); final VideoTrack videoTrack = this.peerConnectionFactory.createVideoTrack("ARDAMSv0", videoSource); videoTrack.addSink(surfaceViewRenderer); - videoTrack.addSink(MediaRecorder.getInstance().videoRecoder); +// videoTrack.addSink(MediaRecorder.getInstance().videoRecoder); videoTrack.setEnabled(true); this.mediaStream.addTrack(videoTrack); Log.i(MediaManager.class.getSimpleName(), "加载视频:" + videoTrack.id()); @@ -348,7 +407,7 @@ public class MediaManager { return this.mediaStream; } - private SurfaceViewRenderer preview() { + private SurfaceViewRenderer localPreview() { // 设置预览 final SurfaceViewRenderer surfaceViewRenderer = new SurfaceViewRenderer(this.context); this.handler.post(() -> { @@ -370,7 +429,7 @@ public class MediaManager { message.obj = surfaceViewRenderer; message.what = Config.WHAT_NEW_LOCAL_VIDEO; // TODO:恢复 -// this.handler.sendMessage(message); + this.handler.sendMessage(message); // 暂停 // surfaceViewRenderer.pauseVideo(); // 恢复 @@ -443,7 +502,7 @@ public class MediaManager { synchronized (this.mediaStream.audioTracks) { AudioTrack track; final Iterator iterator = this.mediaStream.audioTracks.iterator(); - while(iterator.hasNext()) { + while (iterator.hasNext()) { track = iterator.next(); iterator.remove(); track.dispose(); @@ -470,7 +529,7 @@ public class MediaManager { synchronized (list) { VideoTrack track; final Iterator iterator = list.iterator(); - while(iterator.hasNext()) { + while (iterator.hasNext()) { track = iterator.next(); iterator.remove(); track.dispose(); @@ -481,27 +540,32 @@ public class MediaManager { /** * 释放资源 */ - public void close() { + private void close() { this.closeAudioTrack(); this.closeVideoTrack(); - if(this.eglBase != null) { + if (this.eglBase != null) { this.eglBase.release(); this.eglBase = null; } - if(this.videoCapturer != null) { + if (this.videoCapturer != null) { this.videoCapturer.dispose(); this.videoCapturer = null; } - if(this.mediaStream != null) { + if (this.mediaStream != null) { this.mediaStream.dispose(); this.mediaStream = null; } - if(this.peerConnectionFactory != null) { + if (this.peerConnectionFactory != null) { this.peerConnectionFactory.dispose(); this.peerConnectionFactory = null; } } + public void shutdown() { +// PeerConnectionFactory.shutdownInternalTracer(); +// PeerConnectionFactory.stopInternalTracingCapture(); + } + /** * 摄像头事件 * @@ -534,4 +598,8 @@ public class MediaManager { } } + private native void nativeInit(); + private native void nativeStop(); + public native long nativeNewRoom(String roomId); + } diff --git a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/MediaRecorder.java b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/MediaRecorder.java index 7c2b2f9..95f3453 100644 --- a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/MediaRecorder.java +++ b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/MediaRecorder.java @@ -13,13 +13,22 @@ import android.util.Log; import com.acgist.mediasoup.R; +import org.webrtc.JavaI420Buffer; +import org.webrtc.RtpSender; +import org.webrtc.RtpTransceiver; +import org.webrtc.TextureBufferImpl; +import org.webrtc.ThreadUtils; import org.webrtc.VideoFrame; import org.webrtc.VideoSink; +import org.webrtc.YuvConverter; import org.webrtc.YuvHelper; import org.webrtc.audio.JavaAudioDeviceModule; +import org.webrtc.voiceengine.WebRtcAudioRecord; +import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -74,24 +83,6 @@ public final class MediaRecorder { */ public final VideoSink videoRecoder; - static { - final MediaCodecList mediaCodecList = new MediaCodecList(-1); - for (MediaCodecInfo mediaCodecInfo : mediaCodecList.getCodecInfos()) { - if (mediaCodecInfo.isEncoder()) { - final String[] supportedTypes = mediaCodecInfo.getSupportedTypes(); - Log.d(MediaRecorder.class.getSimpleName(), "编码器名称:" + mediaCodecInfo.getName()); - Log.d(MediaRecorder.class.getSimpleName(), "编码器类型:" + String.join(" , ", supportedTypes)); - for (String supportType : supportedTypes) { - final MediaCodecInfo.CodecCapabilities codecCapabilities = mediaCodecInfo.getCapabilitiesForType(supportType); - final int[] colorFormats = codecCapabilities.colorFormats; - Log.d(MediaRecorder.class.getSimpleName(), "编码器格式:" + codecCapabilities.getMimeType()); -// MediaCodecInfo.CodecCapabilities.COLOR_* - Log.d(MediaRecorder.class.getSimpleName(), "编码器支持格式:" + IntStream.of(colorFormats).boxed().map(String::valueOf).collect(Collectors.joining(" , "))); - } - } - } - } - private MediaRecorder() { this.executorService = Executors.newFixedThreadPool(2); this.audioRecoder = audioSamples -> { @@ -99,12 +90,14 @@ public final class MediaRecorder { this.videoRecoder = videoFrame -> { if (this.active && this.videoActive) { this.executorService.submit(() -> { +// TextureBufferImpl // videoFrame.retain(); + final TextureBufferImpl buffer = (TextureBufferImpl) videoFrame.getBuffer(); final int outputFrameSize = videoFrame.getRotatedWidth() * videoFrame.getRotatedHeight() * 3 / 2; final ByteBuffer outputFrameBuffer = ByteBuffer.allocateDirect(outputFrameSize); - final int index = this.videoCodec.dequeueInputBuffer(1000L * 1000); + final int index = this.videoCodec.dequeueInputBuffer(1000L * 1000); // YuvImage:截图 - // YV12 + // YV12 VideoFrame.I420Buffer i420 = videoFrame.getBuffer().toI420(); // i420.retain(); Log.i(MediaRecorder.class.getSimpleName(), "视频信息:" + videoFrame.getRotatedWidth() + " - " + videoFrame.getRotatedHeight()); @@ -131,16 +124,20 @@ public final class MediaRecorder { } public void record(String path) { + if(this.active) { + return; + } this.record(path, System.currentTimeMillis() + ".mp4", null, null, 1, 1); } public void record(String path, String file, String audioFormat, String videoFormat, int width, int height) { synchronized (MediaRecorder.INSTANCE) { + MediaManager.getInstance().initRecord(MediaManager.Type.BACK); this.file = file; this.active = true; if ( this.audioThread == null || !this.audioThread.isAlive() || - this.videoThread == null || !this.videoThread.isAlive() + this.videoThread == null || !this.videoThread.isAlive() ) { this.initMediaMuxer(path, file); this.initAudioThread(MediaFormat.MIMETYPE_AUDIO_AAC, 96000, 44100, 1); @@ -244,7 +241,7 @@ public final class MediaRecorder { videoFormat.setInteger(MediaFormat.KEY_BIT_RATE, 800 * 1000); videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30); // videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible); - videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar); + videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible); videoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); this.videoCodec.configure(videoFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); } catch (Exception e) { @@ -325,8 +322,13 @@ public final class MediaRecorder { private void initMediaMuxer(String path, String file) { try { + final Path filePath = Paths.get(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath(), path, file); + final File parentFile = filePath.getParent().toFile(); + if(!parentFile.exists()) { + parentFile.mkdirs(); + } this.mediaMuxer = new MediaMuxer( - Paths.get(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath(), path, file).toAbsolutePath().toString(), + filePath.toAbsolutePath().toString(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4 ); // 设置方向 diff --git a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/Room.java b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/Room.java index 610a12b..6a02fbb 100644 --- a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/Room.java +++ b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/Room.java @@ -1,9 +1,18 @@ package com.acgist.taoyao.media; +import android.provider.MediaStore; import android.util.Log; +import com.acgist.taoyao.boot.model.Message; +import com.acgist.taoyao.boot.utils.JSONUtils; +import com.acgist.taoyao.boot.utils.MapUtils; +import com.acgist.taoyao.signal.ITaoyao; + +import org.webrtc.PeerConnection; +import org.webrtc.PeerConnectionFactory; + import java.io.Closeable; -import java.io.IOException; +import java.util.ArrayList; import java.util.List; /** @@ -13,10 +22,27 @@ import java.util.List; */ public class Room implements Closeable { - private final String id; + private final String roomId; + private final String password; + private final long nativePointer; + private final ITaoyao taoyao; + private final MediaManager mediaManager; + private volatile boolean enter; + private PeerConnection.RTCConfiguration rtcConfiguration; + private PeerConnectionFactory peerConnectionFactory; - public Room(String id) { - this.id = id; + public Room( + String roomId, String password, + boolean audioConsume, boolean videoConsume, + boolean audioProduce, boolean videoProduce, + long nativePointer, ITaoyao taoyao + ) { + this.roomId = roomId; + this.password = password; + this.nativePointer = nativePointer; + this.taoyao = taoyao; + this.mediaManager = MediaManager.getInstance(); + this.enter = false; } /** @@ -26,8 +52,51 @@ public class Room implements Closeable { @Override public void close() { - Log.i(Room.class.getSimpleName(), "关闭房间:" + this.id); + Log.i(Room.class.getSimpleName(), "关闭房间:" + this.roomId); + this.mediaManager.closeClient(); this.remoteClientList.forEach(RemoteClient::close); } + public synchronized void enter() { + if(this.enter) { + return; + } + final Message response = this.taoyao.request(this.taoyao.buildMessage("media::router::rtp::capabilities", "roomId", this.roomId)); + if(response == null) { + return; + } + // STUN | TURN + final List iceServers = new ArrayList<>(); + // TODO:读取配置 + final PeerConnection.IceServer iceServer = PeerConnection.IceServer.builder("stun:stun1.l.google.com:19302").createIceServer(); + iceServers.add(iceServer); + this.rtcConfiguration = new PeerConnection.RTCConfiguration(iceServers); + + this.rtcConfiguration.screencastMinBitrate = 100; + this.rtcConfiguration.enableDtlsSrtp = true; + + this.peerConnectionFactory = this.mediaManager.newClient(MediaManager.Type.BACK); + final Object rtpCapabilities = MapUtils.get(response.body(), "rtpCapabilities"); + this.nativeLoad(this.nativePointer, JSONUtils.toJSON(rtpCapabilities), this.peerConnectionFactory.getNativePeerConnectionFactory(), this.rtcConfiguration); + } + + public void enterCallback(String rtpCapabilities, String sctpCapabilities) { + this.taoyao.request(this.taoyao.buildMessage( + "room::enter", + "roomId", this.roomId, + "password", this.password, + "rtpCapabilities", rtpCapabilities, + "sctpCapabilities", sctpCapabilities + )); + this.enter = true; + } + + public void produceMedia() { + } + + private native void nativeLoad(long nativePointer, String rtpCapabilities, long peerConnectionFactory, PeerConnection.RTCConfiguration rtcConfiguration); + private native void nativeNewClient(); + private native void nativeCloseClient(); + private native void nativeCloseRoom(long nativePointer); + } diff --git a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/SessionClient.java b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/SessionClient.java index 9a33f9e..47ad065 100644 --- a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/SessionClient.java +++ b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/SessionClient.java @@ -9,6 +9,7 @@ import com.acgist.taoyao.signal.ITaoyao; import org.webrtc.DataChannel; import org.webrtc.IceCandidate; +import org.webrtc.JniCommon; import org.webrtc.MediaConstraints; import org.webrtc.MediaStream; import org.webrtc.PeerConnection; diff --git a/taoyao-client-web/src/components/Taoyao.js b/taoyao-client-web/src/components/Taoyao.js index 0fbdeba..9b319c6 100644 --- a/taoyao-client-web/src/components/Taoyao.js +++ b/taoyao-client-web/src/components/Taoyao.js @@ -1415,13 +1415,21 @@ class Taoyao extends RemoteClient { return; } me.roomId = roomId; - me.mediasoupDevice = new mediasoupClient.Device(); const response = await me.request( protocol.buildMessage("media::router::rtp::capabilities", { roomId: me.roomId, }) ); const routerRtpCapabilities = response.body.rtpCapabilities; + me.mediasoupDevice = new mediasoupClient.Device(); +// mediasoupClient.parseScalabilityMode("L2T3"); +// // => { spatialLayers: 2, temporalLayers: 3 } +// mediasoupClient.parseScalabilityMode("S3T3"); +// // => { spatialLayers: 3, temporalLayers: 3 } +// mediasoupClient.parseScalabilityMode("L4T7_KEY_SHIFT"); +// // => { spatialLayers: 4, temporalLayers: 7 } +// mediasoupClient.parseScalabilityMode(undefined); +// // => { spatialLayers: 1, temporalLayers: 1 } await me.mediasoupDevice.load({ routerRtpCapabilities }); await me.request( protocol.buildMessage("room::enter", { @@ -1916,7 +1924,7 @@ class Taoyao extends RemoteClient { this.videoProducer = null; }); this.videoProducer.on("trackended", () => { - console.warn("video producer trackended", this.audioProducer); + console.warn("video producer trackended", this.videoProducer); this.closeVideoProducer().catch(() => {}); }); } catch (error) { diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/IpRewriteProperties.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/IpRewriteProperties.java index 99b1262..f310f06 100644 --- a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/IpRewriteProperties.java +++ b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/IpRewriteProperties.java @@ -10,7 +10,6 @@ import lombok.Setter; /** * 地址重写 - * 内外网多网卡环境重写网络号保留主机号,通过重新地址实现多网互通。 * * @author acgist */ diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/IpRewriteRuleProperties.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/IpRewriteRuleProperties.java index 8df45f4..6f26d61 100644 --- a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/IpRewriteRuleProperties.java +++ b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/IpRewriteRuleProperties.java @@ -6,11 +6,7 @@ import lombok.Setter; /** * 重写规则 - * - * @author acgist - */ -/** - * WebRTC STUN配置 + * 没有配置内网地址等于网络号加上原始主机号 * * @author acgist */ @@ -21,7 +17,9 @@ public class IpRewriteRuleProperties { @Schema(title = "网络号", description = "网络号:匹配终端IP") private String network; - @Schema(title = "目标地址", description = "目标地址:没有配置等于网络号加上原始主机号") - private String targetHost; + @Schema(title = "内网地址", description = "内网地址") + private String innerHost; + @Schema(title = "外网地址", description = "外网地址") + private String outerHost; } diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/NetUtils.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/NetUtils.java index a1fd0ba..2d8981a 100644 --- a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/NetUtils.java +++ b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/NetUtils.java @@ -79,7 +79,9 @@ public final class NetUtils { try { final InetAddress sourceAddress = NetUtils.realAddress(sourceIp); final InetAddress clientAddress = NetUtils.realAddress(clientIp); - if(NetUtils.localAddress(sourceAddress) && NetUtils.localAddress(clientAddress)) { + final boolean sourceLocal = NetUtils.localAddress(sourceAddress); + final boolean clientLocal = NetUtils.localAddress(clientAddress); + if(sourceLocal && clientLocal) { final byte[] sourceBytes = sourceAddress.getAddress(); final byte[] clientBytes = clientAddress.getAddress(); final int length = (sourceBytes.length & clientBytes.length) * Byte.SIZE; @@ -109,21 +111,25 @@ public final class NetUtils { if(Boolean.FALSE.equals(NetUtils.ipRewriteProperties.getEnabled())) { return sourceIp; } - log.debug("重写地址:{} - {}", sourceIp, clientIp); + final IpRewriteRuleProperties rule = NetUtils.ipRewriteProperties.getRule().stream() + .filter(v -> NetUtils.subnetIp(v.getNetwork(), clientIp)) + .findFirst() + .orElse(null); + if(rule == null) { + return sourceIp; + } + log.debug("地址重写:{} - {} - {}", sourceIp, clientIp, rule.getNetwork()); try { final InetAddress sourceAddress = NetUtils.realAddress(sourceIp); final InetAddress clientAddress = NetUtils.realAddress(clientIp); - if(NetUtils.localAddress(sourceAddress) && NetUtils.localAddress(clientAddress)) { - final IpRewriteRuleProperties rule = NetUtils.ipRewriteProperties.getRule().stream() - .filter(v -> NetUtils.subnetIp(v.getNetwork(), clientIp)) - .findFirst() - .orElse(null); - if(rule == null) { - return sourceIp; - } - if(StringUtils.isNotEmpty(rule.getTargetHost())) { - return rule.getTargetHost(); + final boolean sourceLocal = NetUtils.localAddress(sourceAddress); + final boolean clientLocal = NetUtils.localAddress(clientAddress); + if(sourceLocal && clientLocal) { + // 明确配置 + if(StringUtils.isNotEmpty(rule.getInnerHost())) { + return rule.getInnerHost(); } + // 地址 = 网络号 + 主机号 final byte[] sourceBytes = sourceAddress.getAddress(); final byte[] clientBytes = clientAddress.getAddress(); final int length = (sourceBytes.length & clientBytes.length) * Byte.SIZE; @@ -142,8 +148,16 @@ public final class NetUtils { return InetAddress.getByAddress(bytes).getHostAddress(); } } + // 公网服务 && 内网设备 + if(sourceLocal && !clientLocal && StringUtils.isNotEmpty(rule.getInnerHost())) { + return rule.getInnerHost(); + } + // 内网服务 && 公网设备 + if(!sourceLocal && clientLocal && StringUtils.isNotEmpty(rule.getOuterHost())) { + return rule.getOuterHost(); + } } catch (UnknownHostException e) { - log.error("IP地址转换异常:{}-{}", sourceIp, clientIp, e); + log.error("地址重写异常:{}-{}", sourceIp, clientIp, e); } return sourceIp; } diff --git a/taoyao-signal-server/taoyao-server/src/main/resources/application.yml b/taoyao-signal-server/taoyao-server/src/main/resources/application.yml index d604491..856cbaf 100644 --- a/taoyao-signal-server/taoyao-server/src/main/resources/application.yml +++ b/taoyao-signal-server/taoyao-server/src/main/resources/application.yml @@ -230,12 +230,12 @@ taoyao: enabled: true prefix: 24 rule: - - prefix: 24 - network: 192.168.1.0 - target-host: - - prefix: 24 - network: 192.168.8.0 - target-host: + - network: 192.168.1.0 + inner-host: + outer-host: + - network: 192.168.8.0 + inner-host: + outer-host: # 脚本配置 script: enabled: true diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/session/SessionCallProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/session/SessionCallProtocol.java index e2932d4..ed51752 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/session/SessionCallProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/session/SessionCallProtocol.java @@ -55,8 +55,8 @@ public class SessionCallProtocol extends ProtocolSessionAdapter { Constant.NAME, client.status().getName(), Constant.CLIENT_ID, client.clientId(), Constant.SESSION_ID, session.getId(), - Constant.AUDIO, MapUtils.get(body, Constant.AUDIO), - Constant.VIDEO, MapUtils.get(body, Constant.VIDEO) + Constant.AUDIO, MapUtils.get(body, Constant.AUDIO, true), + Constant.VIDEO, MapUtils.get(body, Constant.VIDEO, true) )); target.push(callMessage); }