[*] 信令+设置布局

This commit is contained in:
acgist
2023-03-26 12:42:29 +08:00
parent 1fb36da847
commit 4b706478d1
35 changed files with 656 additions and 289 deletions

3
.gitignore vendored
View File

@@ -3,6 +3,9 @@
*.settings
*.classpath
*.factorypath
.vscode
package-lock.json
logs

View File

@@ -55,6 +55,7 @@ Android还在学习之中...
## TODO
* P2P
* RTP
* 标识 -> ID
* 所有字段获取 -> get
* 优化JS错误回调 -> platform::error

72
docs/AOSP.md Normal file
View File

@@ -0,0 +1,72 @@
# AOSP
本文档内容旨在独立定制编译`AOSP`系统,非必需使用。
## 参考文档
* https://mirrors.tuna.tsinghua.edu.cn/help/AOSP/
* https://source.android.google.cn/source/initializing?hl=zh-cn
* https://source.android.google.cn/docs/setup/about/build-numbers?hl=zh-cn
* https://developers.google.cn/android/drivers#sargosp2a.220505.008
## 机器配置
* 内存`32G`
* 十六核`CPU`
* 硬盘`300G`
* 系统`Ubuntu 18.xx`
* 公司网络`100Mbps/s`
* 整个下载过程大概需要四到五个小时
* 整个编译过程大概需要半到一个小时
## 源码
```
# 工具
mkdir /data/android
cd /data/android
curl https://mirrors.tuna.tsinghua.edu.cn/git/git-repo -o repo
chmod a+x repo
export REPO_URL='https://mirrors.tuna.tsinghua.edu.cn/git/git-repo'
git config --global user.email "taoyao@acgist.com"
git config --global user.name "acgist"
# 源码
./repo init -u https://mirrors.tuna.tsinghua.edu.cn/git/AOSP/platform/manifest -b android-12.1.0_r27
./repo sync
```
ROOT权限
## 裁剪
```
# 应用
# 驱动
# root
userdebug
# framework
```
## 编译
```
lunch aosp_arm64-user
make -j 8
```
## 刷机
```
adb reboot bootloader
fastboot flashall
```
https://blog.csdn.net/u012932409/article/details/106792906
https://blog.csdn.net/qq_33240707/article/details/123704679
https://blog.csdn.net/weixin_42929891/article/details/122667831

View File

@@ -278,90 +278,6 @@ SELINUX=disabled
---
```
## libwebrtc可选
* https://webrtc.github.io/webrtc-org/native-code/android/
* https://webrtc.github.io/webrtc-org/native-code/development/
* https://webrtc.github.io/webrtc-org/native-code/development/prerequisite-sw/
* https://www.chromium.org/developers/how-tos/install-depot-tools/
建议直接购买国外的按需使用的主机,用完直接释放,配置建议:
* 内存`8G`
* 四核`CPU`
* 硬盘`100G`
* 系统`Ubuntu 20.xx`
* 宽带按需`100Mbps/s`(不要固定宽带)
* 整个编译过程大概需要两到三个小时(不会下载回来很慢)
```
# 编译工具
mkdir -p /data
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
# 源码
mkdir -p /data/webrtc
cd /data/webrtc
fetch --nohooks webrtc_android
/data/depot_tools/gclient sync
# 分支
cd src
git checkout -b m94 branch-heads/4606
/data/depot_tools/gclient sync
# 编译依赖
./build/install-build-deps.sh
./build/install-build-deps-android.sh
source ./build/android/envsetup.sh
---
'target_os': 'android',
'is_clang': True,
'is_debug': False,
'use_rtti': True,
'rtc_use_h264': True,
'use_custom_libcxx': False,
'rtc_include_tests': False,
'is_component_build': False,
'treat_warnings_as_errors': False,
'use_goma': use_goma,
'target_cpu': _GetTargetCpu(arch)
---
# 编译.so
./tools_webrtc/android/build_aar.py --build-dir ./out/release-build/
# 指定CPU架构--arch x86 x86_64 arm64-v8a armeabi-v7a
# 编译.a
/data/depot_tools/autoninja -C ./out/release-build/x86 webrtc &&
/data/depot_tools/autoninja -C ./out/release-build/x86_64 webrtc &&
/data/depot_tools/autoninja -C ./out/release-build/arm64-v8a webrtc &&
/data/depot_tools/autoninja -C ./out/release-build/armeabi-v7a webrtc
# 依赖打包
zip -r webrtc.zip out libwebrtc.aar
```
[WebRTC](https://pan.baidu.com/s/1E_DXv32D9ODyj5J-o-ji_g?pwd=hudc)
## libmediasoupclient可选
https://mediasoup.org/documentation/v3/libmediasoupclient/installation/
```
# 编译
cmake . -B build \
-DCMAKE_BUILD_TYPE=Debug | Release \
-DMEDIASOUPCLIENT_LOG_DEV=OFF \
-DMEDIASOUPCLIENT_LOG_TRACE=OFF \
-DMEDIASOUPCLIENT_BUILD_TESTS=OFF \
-DLIBWEBRTC_INCLUDE_PATH:PATH=PATH_TO_LIBWEBRTC_SOURCES \
-DLIBWEBRTC_BINARY_PATH:PATH=PATH_TO_LIBWEBRTC_BINARY
make -C build
make install -C build
```
## 下载源码
```

90
docs/WebRTC.md Normal file
View File

@@ -0,0 +1,90 @@
# WebRTC
本文档内容旨在独立编译`WebRTC`项目,非必需使用。
## libwebrtc
* https://webrtc.github.io/webrtc-org/native-code/android/
* https://webrtc.github.io/webrtc-org/native-code/development/
* https://webrtc.github.io/webrtc-org/native-code/development/prerequisite-sw/
* https://www.chromium.org/developers/how-tos/install-depot-tools/
国内镜像需要配置比较麻烦,建议直接按需购买能够访问外网的主机,用完直接释放,配置建议:
* 内存`8G`
* 四核`CPU`
* 硬盘`100G`
* 系统`Ubuntu 20.xx`
* 宽带按需`100Mbps/s`(不要固定宽带)
* 整个下载过程大概需要半到一个小时
* 整个编译过程大概需要一到两个小时
* 最痛苦的就是下载回来速度很慢
```
# 编译工具
mkdir -p /data
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
# 源码
mkdir -p /data/webrtc
cd /data/webrtc
fetch --nohooks webrtc_android
/data/depot_tools/gclient sync
# 分支
cd src
git checkout -b m94 branch-heads/4606
/data/depot_tools/gclient sync
# 编译依赖
./build/install-build-deps.sh
./build/install-build-deps-android.sh
source ./build/android/envsetup.sh
# 编译配置:./tools_webrtc/android/build_aar.py
---
'target_os': 'android',
'is_clang': True,
'is_debug': False,
'use_rtti': True,
'rtc_use_h264': True,
'use_custom_libcxx': False,
'rtc_include_tests': False,
'is_component_build': False,
'treat_warnings_as_errors': False,
'use_goma': use_goma,
'target_cpu': _GetTargetCpu(arch)
---
# 编译项目
./tools_webrtc/android/build_aar.py --build-dir ./out/release-build/
# 指定CPU架构--arch x86 x86_64 arm64-v8a armeabi-v7a
# 生成静态库
/data/depot_tools/autoninja -C ./out/release-build/x86 webrtc
/data/depot_tools/autoninja -C ./out/release-build/x86_64 webrtc
/data/depot_tools/autoninja -C ./out/release-build/arm64-v8a webrtc
/data/depot_tools/autoninja -C ./out/release-build/armeabi-v7a webrtc
# 打包
zip -r webrtc.zip out libwebrtc.aar
```
[WebRTC](https://pan.baidu.com/s/1E_DXv32D9ODyj5J-o-ji_g?pwd=hudc)
## libmediasoupclient
https://mediasoup.org/documentation/v3/libmediasoupclient/installation/
```
# 编译
cmake . -B build \
-DCMAKE_BUILD_TYPE=Debug | Release \
-DMEDIASOUPCLIENT_LOG_DEV=OFF \
-DMEDIASOUPCLIENT_LOG_TRACE=OFF \
-DMEDIASOUPCLIENT_BUILD_TESTS=OFF \
-DLIBWEBRTC_INCLUDE_PATH:PATH=PATH_TO_LIBWEBRTC_SOURCES \
-DLIBWEBRTC_BINARY_PATH:PATH=PATH_TO_LIBWEBRTC_BINARY
make -C build
make install -C build
```

View File

@@ -10,8 +10,7 @@
## 项目配置
可以自己编译`WebRTC`依赖或者下载已有依赖,
项目导入以后拷贝`libmediasoupclient`源码还有`WebRTC`头文件和二进制文件到`deps`目录。
可以自己编译`WebRTC`依赖或者下载已有依赖,项目导入以后拷贝`libmediasoupclient`源码还有`WebRTC`头文件和二进制文件到`deps`目录。
[WebRTC](https://pan.baidu.com/s/1E_DXv32D9ODyj5J-o-ji_g?pwd=hudc)
@@ -22,7 +21,7 @@
* https://developer.android.google.cn/docs?hl=zh-cn
* https://developer.android.google.cn/guide?hl=zh-cn
## 依赖编译
## 依赖编译(可选)
* https://webrtc.github.io/webrtc-org/native-code/android/
* https://webrtc.github.io/webrtc-org/native-code/development/
@@ -30,6 +29,11 @@
* https://www.chromium.org/developers/how-tos/install-depot-tools/
* https://mediasoup.org/documentation/v3/libmediasoupclient/installation/
## 安卓编译(可选)
* https://mirrors.tuna.tsinghua.edu.cn/help/AOSP/
* https://source.android.google.cn/source/initializing?hl=zh-cn
## 参考项目
* https://github.com/haiyangwu/webrtc-android-build

View File

@@ -4,6 +4,7 @@
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:allowBackup="true"
@@ -11,24 +12,33 @@
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.Taoyao">
<service
android:name=".MediaService"
android:enabled="true"
android:exported="false" />
<activity
android:name=".SettingsActivity"
android:exported="false"
android:label="@string/title_activity_settings" />
android:supportsRtl="true">
<activity
android:name=".MainActivity"
android:exported="true">
android:exported="true"
android:label="@string/title_activity_main">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SettingsActivity"
android:exported="false"
android:label="@string/title_activity_settings" />
<service
android:name=".MediaService"
android:enabled="true"
android:exported="false" />
<receiver
android:name=".BootReceiver"
android:enabled="true"
android:exported="true">
<intent-filter android:priority="1000">
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
</application>
</manifest>

View File

@@ -0,0 +1,23 @@
package com.acgist.taoyao.client;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
/**
* 开机启动
*
* @author acgist
*/
public class BootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
final Intent mainActivity = new Intent(context, MainActivity.class);
mainActivity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(mainActivity);
}
}
}

View File

@@ -1,32 +1,38 @@
package com.acgist.taoyao.client;
import android.content.Intent;
import android.os.Bundle;
import android.util.AttributeSet;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import com.acgist.taoyao.client.databinding.ActivityMainBinding;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import java.util.List;
import java.util.Map;
/**
* 预览界面
*
* @author acgist
*/
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("taoyao");
}
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
TextView tv = binding.sampleText;
tv.setText(stringFromJNI());
// 媒体服务
final Intent mediaService = new Intent(this, MediaService.class);
this.startService(mediaService);
// 布局
this.binding = ActivityMainBinding.inflate(this.getLayoutInflater());
this.setContentView(this.binding.getRoot());
// 设置按钮
this.binding.settings.setOnClickListener(view -> {
final Intent settings = new Intent(this, SettingsActivity.class);
this.startService(settings);
});
}
public native String stringFromJNI();
}

View File

@@ -4,7 +4,19 @@ import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import com.acgist.taoyao.client.signal.Taoyao;
/**
* 媒体服务
*
* @author acgist
*/
public class MediaService extends Service {
static {
System.loadLibrary("taoyao");
}
public MediaService() {
}

View File

@@ -1,18 +1,38 @@
package com.acgist.taoyao.client;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import com.acgist.taoyao.client.R;
import com.acgist.taoyao.client.databinding.ActivitySettingsBinding;
import com.acgist.taoyao.client.signal.Taoyao;
/**
* 设置界面
*
* @author acgist
*/
public class SettingsActivity extends AppCompatActivity {
private ActivitySettingsBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
// 布局
this.binding = ActivitySettingsBinding.inflate(this.getLayoutInflater());
this.setContentView(this.binding.getRoot());
// 设置按钮
this.binding.connect.setOnClickListener(view -> {
// final Taoyao taoyao = new Taoyao(
//
// );
// Log.d(SettingsActivity.class.getSimpleName(), "连接信令:" + taoyao);
final Intent main = new Intent(this, MainActivity.class);
this.startService(main);
});
}
}

View File

@@ -1,5 +1,8 @@
package com.acgist.taoyao.client.signal;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationManager;
@@ -7,8 +10,12 @@ import android.net.wifi.WifiManager;
import android.os.BatteryManager;
import android.util.Log;
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.JSONUtils;
import com.acgist.taoyao.client.media.Recorder;
@@ -26,8 +33,11 @@ import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
@@ -51,14 +61,21 @@ public final class Taoyao {
*/
private final String host;
/**
* 终端ID
* 信令版本
*/
private final String clientId;
private final String clientType = "camera";
private final String version;
/**
* 终端名称
*/
private final String name;
/**
* 终端ID
*/
private final String clientId;
/**
* 终端类型
*/
private final String clientType;
/**
* 桃夭帐号
*/
@@ -67,55 +84,122 @@ public final class Taoyao {
* 桃夭密码
*/
private final String password;
private String version;
/**
* Socket
* 是否关闭
*/
private Socket socket;
private InputStream input;
private OutputStream output;
private boolean close;
/**
* 是否连接
*/
private boolean connect;
/**
* 超时时间
*/
private final int timeout;
/**
* Socket
*/
private Socket socket;
/**
* 信令输入
*/
private InputStream input;
/**
* 信令输出
*/
private OutputStream output;
/**
* 加密工具
*/
private final Cipher encrypt;
/**
* 解密工具
*/
private final Cipher decrypt;
/**
* 服务上下文
*/
private final Context context;
/**
* Wifi管理器
*/
private final WifiManager wifiManager;
/**
* 电池管理器
*/
private final BatteryManager batteryManager;
/**
* 位置管理器
*/
private final LocationManager locationManager;
// 线程池
private final ExecutorService executor = Executors.newFixedThreadPool(8);
// 定时任务线程池
private final ExecutorService scheduled = Executors.newScheduledThreadPool(2);
/**
* 请求消息:同步消息
*/
private final Map<Long, Message> requestMessage;
/**
* 线程池
*/
private final ExecutorService executor;
/**
* 定时任务线程池
*/
private final ScheduledExecutorService scheduled;
/**
* 全局单例
*/
private static Taoyao instance;
public Taoyao(
int port, String host, String algo, String secret, String clientId, String name, String username, String password,
WifiManager wifiManager, BatteryManager batteryManager, LocationManager locationManager
int port, String host, String version,
String name, String clientId, String clientType, String username, String password,
int timeout, String algo, String secret,
Context context, WifiManager wifiManager, BatteryManager batteryManager, LocationManager locationManager
) {
this.port = port;
this.host = host;
this.close = false;
this.connect = false;
if (algo == null || algo.isEmpty() || algo.equals("PLAINTEXT")) {
// 明文
this.encrypt = null;
this.decrypt = null;
} else {
this.encrypt = this.buildCipher(Cipher.ENCRYPT_MODE, algo, secret);
this.decrypt = this.buildCipher(Cipher.DECRYPT_MODE, algo, secret);
}
this.clientId = clientId;
this.port = port;
this.host = host;
this.version = version;
this.name = name;
this.clientId = clientId;
this.clientType = clientType;
this.username = username;
this.password = password;
this.timeout = timeout;
final boolean plaintext = algo == null || algo.isEmpty() || algo.equals("PLAINTEXT");
this.encrypt = plaintext ? null : this.buildCipher(Cipher.ENCRYPT_MODE, algo, secret);
this.decrypt = plaintext ? null : this.buildCipher(Cipher.DECRYPT_MODE, algo, secret);
this.context = context;
this.wifiManager = wifiManager;
this.batteryManager = batteryManager;
this.locationManager = locationManager;
executor.submit(this::read);
scheduled.submit(this::heartbeat);
this.requestMessage = new ConcurrentHashMap<>();
// 读取线程 + 两条处理线程
this.executor = Executors.newFixedThreadPool(3);
// 心跳线程
this.scheduled = Executors.newScheduledThreadPool(1);
executor.submit(this::loopMessage);
scheduled.scheduleWithFixedDelay(this::heartbeat, 30, 30, TimeUnit.SECONDS);
if(Taoyao.instance != null) {
Taoyao.instance.close();
}
Taoyao.instance = this;
}
/**
* @return 信令
*/
public static final Taoyao getInstance() {
return Taoyao.instance;
}
/**
* @param mode 加密/解密
* @param name 算法名称
* @param secret 密钥
*
* @return 加解密工具
*/
private Cipher buildCipher(int mode, String name, String secret) {
try {
final String algo = name.equals("DES") ? "DES/ECB/PKCS5Padding" : "AES/ECB/PKCS5Padding";
@@ -123,7 +207,7 @@ public final class Taoyao {
cipher.init(mode, new SecretKeySpec(Base64.getMimeDecoder().decode(secret), name));
return cipher;
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) {
// TODO日志
Log.e(Taoyao.class.getSimpleName(), "创建加解密工具异常", e);
}
return null;
}
@@ -131,44 +215,51 @@ public final class Taoyao {
/**
* 连接信令
*/
public void connect() {
this.close();
// HiLog.debug(this.label, "连接信令:%s:%d", this.host, this.port);
public synchronized void connect() {
if(this.close) {
return;
}
// 释放连接
this.disconnect();
// 开始连接
Log.d(Taoyao.class.getSimpleName(), "连接信令:" + this.host + ":" + this.port);
this.socket = new Socket();
try {
// socket.setSoTimeout(5000);
this.socket.connect(new InetSocketAddress(this.host, this.port), 5000);
// 设置读取超时时间:不要设置一直阻塞
// socket.setSoTimeout(this.timeout);
this.socket.connect(new InetSocketAddress(this.host, this.port), this.timeout);
if (this.socket.isConnected()) {
this.input = this.socket.getInputStream();
this.output = this.socket.getOutputStream();
this.register();
this.connect = true;
synchronized (this) {
this.notifyAll();
}
}
} catch (Exception e) {
e.printStackTrace();
// HiLog.error(this.label, "连接信令异常:%s:%d", this.host, this.port);
Log.e(Taoyao.class.getSimpleName(), "连接信令异常:" + this.host + ":" + this.port, e);
}
}
private void heartbeat() {
}
private void read() {
/**
* 循环读取信令消息
*/
private void loopMessage() {
int length = 0;
short messageLength = 0;
final byte[] bytes = new byte[1024];
final ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);
while (!this.close) {
try {
while (this.input == null) {
// 重连
while (!this.close && !this.connect) {
this.connect();
synchronized (this) {
this.wait(5000);
this.wait(this.timeout);
}
}
// 读取
while ((length = this.input.read(bytes)) >= 0) {
buffer.put(bytes, 0, length);
while (buffer.position() > 0) {
@@ -195,6 +286,7 @@ public final class Taoyao {
buffer.get(message);
buffer.compact();
final String content = new String(this.decrypt.doFinal(message));
Log.d(Taoyao.class.getSimpleName(), "处理信令:" + content);
executor.submit(() -> {
try {
Taoyao.this.on(content);
@@ -207,16 +299,15 @@ public final class Taoyao {
}
}
} catch (Exception e) {
e.printStackTrace();
Log.e(Taoyao.class.getSimpleName(), "接收信令异常", e);
this.connect();
// TODO日志
// log.error("读取异常", e);
}
}
}
/**
* @param message 消息
* @param message 原始消息
*
* @return 加密消息
*/
private byte[] encrypt(Message message) {
@@ -225,41 +316,67 @@ public final class Taoyao {
try {
// 加密
final byte[] encryptBytes = this.encrypt.doFinal(bytes);
// 发送
// 编码
final ByteBuffer buffer = ByteBuffer.allocateDirect(Short.BYTES + encryptBytes.length);
buffer.putShort((short) encryptBytes.length);
buffer.put(encryptBytes);
buffer.flip();
final byte[] sendBytes = new byte[buffer.capacity()];
buffer.get(sendBytes);
return sendBytes;
// 编码
final byte[] encodingBytes = new byte[buffer.capacity()];
buffer.get(encodingBytes);
return encodingBytes;
} catch (IllegalBlockSizeException | BadPaddingException e) {
e.printStackTrace();
// log.error("加密异常:{}", message);
Log.e(Taoyao.class.getSimpleName(), "加密异常:" + message, e);
}
}
return bytes;
}
/**
* @param message 信令消息
*/
public void push(Message message) {
if (this.output == null) {
Log.w(Taoyao.class.getSimpleName(), "通道没有打开:" + message);
return;
}
try {
this.output.write(this.encrypt(message));
} catch (Exception e) {
e.printStackTrace();
Log.e(Taoyao.class.getSimpleName(), "请求信令异常:" + message, e);
}
}
/**
* @param request 信令请求消息
*
* @return 信令响应消息
*/
public Message request(Message request) {
return null;
final Header header = request.getHeader();
final Long id = header.getId();
this.requestMessage.put(id, request);
synchronized (request) {
this.push(request);
try {
request.wait(this.timeout);
} catch (InterruptedException e) {
Log.e(Taoyao.class.getSimpleName(), "请求信令等待异常:" + request, e);
}
}
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 response;
}
/**
* 释放连接
*/
private void close() {
private void disconnect() {
Log.d(Taoyao.class.getSimpleName(), "释放信令:" + this.host + ":" + this.port);
this.connect = false;
CloseableUtils.close(this.input);
CloseableUtils.close(this.output);
@@ -272,8 +389,8 @@ public final class Taoyao {
/**
* 关闭信令
*/
private void shutdown() {
this.close();
private void close() {
this.disconnect();
this.close = true;
executor.shutdownNow();
scheduled.shutdownNow();
@@ -285,7 +402,7 @@ public final class Taoyao {
*
* @return 消息
*/
public Message buildMessage(String signal, Object ... args) {
public Message buildMessage(String signal, Object... args) {
final Map<Object, Object> map = new HashMap<>();
if (ArrayUtils.isNotEmpty(args)) {
for (int index = 0; index < args.length; index += 2) {
@@ -325,10 +442,21 @@ public final class Taoyao {
if (header == null) {
return;
}
final Map<String, Object> body = message.body();
switch (header.getSignal()) {
case "client::register" -> this.register(message, body);
default -> Log.i(Taoyao.class.getSimpleName(), "没有适配信令:" + content);
final Long id = header.getId();
final Message request = this.requestMessage.get(id);
if (request != null) {
// 同步处理:重新设置响应消息
this.requestMessage.put(id, message);
// 唤醒等待线程
synchronized (request) {
request.notifyAll();
}
} else {
final Map<String, Object> body = message.body();
switch (header.getSignal()) {
case "client::register" -> this.register(message, body);
default -> Log.i(Taoyao.class.getSimpleName(), "没有适配信令:" + content);
}
}
}
@@ -359,18 +487,45 @@ public final class Taoyao {
*/
private void register(Message message, Map<String, Object> body) {
final Integer index = (Integer) body.get("index");
if (index == null) {
return;
}
IdUtils.setClientIndex(index);
}
/**
* 心跳
*/
private void heartbeat() {
while(!this.close) {
final Location location = this.location();
this.push(this.buildMessage(
"client::heartbeat",
"latitude", location == null ? -1 : location.getLatitude(),
"longitude", location == null ? -1 : location.getLongitude(),
"signal", this.wifiManager == null ? -1 : this.wifiManager.getMaxSignalLevel(),
"batter", this.batteryManager == null ? -1 : this.batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY),
"charging", this.batteryManager == null ? -1 : this.batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_STATUS),
"recording", Recorder.getInstance().isActive()
));
}
}
/**
* @return 位置
*/
private Location location() {
if(this.locationManager == null) {
if (this.locationManager == null) {
return null;
}
if (
ActivityCompat.checkSelfPermission(this.context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(this.context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED
) {
return null;
}
final Criteria criteria = new Criteria();
// 耗
//
criteria.setCostAllowed(false);
// 不要海拔
criteria.setAltitudeRequired(false);
@@ -378,9 +533,13 @@ public final class Taoyao {
criteria.setBearingRequired(false);
// 精度
criteria.setAccuracy(Criteria.ACCURACY_FINE);
// 功耗
// 功耗
criteria.setPowerRequirement(Criteria.POWER_LOW);
final String provider = locationManager.getBestProvider(criteria, true);
// 最佳提供者
final String provider = this.locationManager.getBestProvider(criteria, true);
if (provider == null) {
return null;
}
return this.locationManager.getLastKnownLocation(provider);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@@ -2,18 +2,22 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.acgist.taoyao.client.MainActivity">
<TextView
android:id="@+id/sample_text"
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/settings"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:contentDescription="@string/button_setting"
android:src="@drawable/settings"
app:borderWidth="16dp"
app:fabSize="normal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,5 +1,121 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/settings"
android:layout_width="match_parent"
android:layout_height="match_parent">
</LinearLayout>
<EditText
android:id="@+id/port"
android:layout_width="320dp"
android:layout_height="48dp"
android:layout_marginTop="16dp"
android:hint="@string/signal_port"
android:importantForAutofill="no"
android:inputType="number"
android:text="9999"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="HardcodedText" />
<EditText
android:id="@+id/host"
android:layout_width="320dp"
android:layout_height="48dp"
android:layout_marginTop="32dp"
android:hint="@string/signal_host"
android:importantForAutofill="no"
android:inputType="text"
android:text="192.168.0.100"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@+id/port"
app:layout_constraintVertical_bias="0.02"
tools:ignore="HardcodedText" />
<EditText
android:id="@+id/name"
android:layout_width="320dp"
android:layout_height="48dp"
android:layout_marginTop="32dp"
android:hint="@string/signal_name"
android:importantForAutofill="no"
android:inputType="text"
android:text="mobile"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@+id/host"
app:layout_constraintVertical_bias="0.02"
tools:ignore="HardcodedText" />
<EditText
android:id="@+id/clientId"
android:layout_width="320dp"
android:layout_height="48dp"
android:layout_marginTop="32dp"
android:hint="@string/signal_client_id"
android:importantForAutofill="no"
android:inputType="text"
android:text="mobile"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@+id/name"
app:layout_constraintVertical_bias="0.02"
tools:ignore="HardcodedText" />
<EditText
android:id="@+id/username"
android:layout_width="320dp"
android:layout_height="48dp"
android:layout_marginTop="32dp"
android:hint="@string/signal_username"
android:importantForAutofill="no"
android:inputType="text"
android:text="taoyao"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@+id/clientId"
app:layout_constraintVertical_bias="0.02"
tools:ignore="HardcodedText" />
<EditText
android:id="@+id/password"
android:layout_width="320dp"
android:layout_height="48dp"
android:layout_marginTop="32dp"
android:hint="@string/signal_password"
android:importantForAutofill="no"
android:inputType="textPassword"
android:text="taoyao"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@+id/username"
app:layout_constraintVertical_bias="0.02"
tools:ignore="HardcodedText" />
<Button
android:id="@+id/connect"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="@string/button_connect"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@+id/password"
app:layout_constraintVertical_bias="0.02" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="Theme.Taoyao" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
</style>
</resources>

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="reply_entries">
<item>Reply</item>
<item>Reply to all</item>
</string-array>
<string-array name="reply_values">
<item>reply</item>
<item>reply_all</item>
</string-array>
</resources>

View File

@@ -1,10 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 是否开启预览:关闭预览不会播放视频 -->
<bool name="preview">true</bool>
<string name="version">1.0.0</string>
<integer name="timeout">5000</integer>
<string name="clientType">MOBILE</string>
<string name="algo">DES</string>
<string name="secret">DES</string>
</resources>

View File

@@ -1,16 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">桃夭</string>
<string name="title_activity_settings">SettingsActivity</string>
<string name="messages_header">Messages</string>
<string name="sync_header">Sync</string>
<string name="signature_title">Your signature</string>
<string name="reply_title">Default reply action</string>
<string name="sync_title">Sync email periodically</string>
<string name="attachment_title">Download incoming attachments</string>
<string name="attachment_summary_on">Automatically download attachments for incoming emails</string>
<string name="attachment_summary_off">Only download attachments when manually requested</string>
<string name="title_activity_main">桃夭终端预览</string>
<string name="title_activity_settings">桃夭终端设置</string>
<string name="button_setting">设置</string>
<string name="button_connect">连接</string>
<string name="signal_port">信令端口</string>
<string name="signal_host">信令地址</string>
<string name="signal_name">终端名称</string>
<string name="signal_client_id">终端标识</string>
<string name="signal_username">信令帐号</string>
<string name="signal_password">信令密码</string>
</resources>

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="Theme.Taoyao" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
</style>
</resources>

View File

@@ -1,3 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content></full-backup-content>

View File

@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<data-extraction-rules>
<cloud-backup></cloud-backup>
</data-extraction-rules>

View File

@@ -25,8 +25,10 @@ set(
set(
SOURCE_FILES
${SOURCE_DIR}/main.hpp
${SOURCE_DIR}/main.cpp
${SOURCE_DIR}/rtp.hpp
${SOURCE_DIR}/rtp.cpp
${SOURCE_DIR}/webrtc.hpp
${SOURCE_DIR}/webrtc.cpp
)
set(LIBWEBRTC_BINARY_PATH ${LIBWEBRTC_BINARY_PATH}/${ANDROID_ABI} CACHE STRING "libwebrtc binary path" FORCE)

View File

@@ -1,19 +0,0 @@
#include <jni.h>
#include <string>
#include "Device.hpp"
extern "C" JNIEXPORT jstring JNICALL
Java_com_acgist_taoyao_client_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */
) {
mediasoupclient::Device device;
if (device.IsLoaded()) {
std::string hello = "Hello from C++ true";
return env->NewStringUTF(hello.c_str());
} else {
std::string hello = "Hello from C++ false";
return env->NewStringUTF(hello.c_str());
}
}

View File

@@ -0,0 +1 @@
#pragma once

View File

@@ -0,0 +1 @@
#include <jni.h>

View File

@@ -15,6 +15,7 @@ public enum ClientType {
WEB("Web"),
MEDIA("媒体服务"),
CAMERA("摄像头"),
MOBILE("移动端"),
OTHER("其他终端");
/**
@@ -26,32 +27,11 @@ public enum ClientType {
this.name = name;
}
/**
* @return 是否是Web
*/
public boolean web() {
return this == WEB;
}
/**
* @return 是否是媒体服务
*/
public boolean media() {
return this == MEDIA;
}
/**
* @return 是否是摄像头
*/
public boolean camera() {
return this == CAMERA;
}
/**
* @return 是否是媒体终端
*/
public boolean mediaClient() {
return this == WEB || this == CAMERA;
return this == WEB || this == CAMERA || this == MOBILE;
}
/**

View File

@@ -57,7 +57,7 @@ public class RoomCreateProtocol extends ProtocolClientAdapter implements Applica
@Override
public void execute(String clientId, ClientType clientType, Client client, Message message, Map<String, Object> body) {
if(clientType.web()) {
if(clientType.mediaClient()) {
// WEB同步创建房间
final Room room = this.roomManager.create(
MapUtils.get(body, Constant.NAME),