动态获取摄像头和录音权限

本文介绍如何动态获取摄像头和录音权限。

1. Android SDK动态获取权限

1.1. 动态获取摄像头权限

1.1.1. 工程AndroidManifest.xml配置

<uses-feature android:glEsVersion="0x00020000" android:required="true" />
<uses-feature
    android:name="android.hardware.camera"
    android:required="false" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA"/>
<application
  android:allowBackup="true"
  android:debuggable="true"
  android:extractNativeLibs="true" //必需
  android:icon="@mipmap/ic_launcher"
  android:label="@string/app_name"
  android:requestLegacyExternalStorage="true"
  android:roundIcon="@mipmap/ic_launcher_round"
  android:supportsRtl="true"
  android:theme="@style/Theme.Aspdemo"
  tools:ignore="HardcodedDebugMode">

1.1.2. 云手机实例配置

//禁止SDK start的时候获取本机权限
bundle.putBoolean(StreamView.CONFIG_TAKE_PERMISION_REQ, false);
//或者
IASPEngine engine = mBuilder.takePermissionReq(false).enableRTC(true).build(context);
//禁止SDK默认加载本地摄像头
mStreamView.enableSDKLoadCpdToStart(false);

1.1.3. 监听云手机需要申请本机摄像头权限

mStreamView.getASPEngineDelegate().addDataChannel(new DataChannel("wy_vdagent_default_dc") {
    @Override
    protected void onConnectStateChanged(DataChannelConnectState state) {
        Log.i(TAG, "wy_vdagent_default_dc dc connection state changed to " + state);
    }
    @Override
    protected void onReceiveData(byte[] buf) {
        String str = "";
        try {
            str = new String(buf, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            str = new String(buf);
        }
        Log.i(TAG, "wy_vdagent_default_dc dc received " + buf.length + " bytes data:" + str);
        CommandUtils.parseCommand(str, new CommandUtils.CommandListener() {
            @Override
            public void onCameraAuthorize() {
                checkStartCpd();
            }
            @Override
            public void onRotation(int rotation) {
              //云手机旋转状态
            }
            @Override
            public void onUnknownCommand(String cmd) {
                showError("未知命令: " + cmd);
            }
        });
    }
});
package com.example.aspdemo.cloudphone.Util;
import android.util.Log;
import org.json.JSONObject;

public class CommandUtils {
    public interface CommandListener {
        void onCameraAuthorize();
        void onRotation(int rotation);
        void onUnknownCommand(String cmd);
    }

    public static void parseCommand(String data, CommandListener listener) {
        try {
            // 统一处理前缀
            String jsonStr = data.replaceFirst("^data:", "").trim();

            JSONObject json = new JSONObject(jsonStr);
            if (json.has("action")) {
                String action = json.getString("action");
                handleCommand(action, json, listener);
            }
            else if (json.has("cmd")) {
                String cmd = json.getString("cmd");
                handleCommand(cmd, json, listener);
            }

        } catch (Exception e) {
            Log.e("CommandUtils", "解析失败: " + data, e);
        }
    }

    private static void handleCommand(String cmd, JSONObject json, CommandListener listener) {
        if (listener == null) return;

        try {
            switch (cmd) {
                case "authorizeCamera":
                    listener.onCameraAuthorize();
                    break;
                case "rotation":
                    listener.onRotation(json.getInt("value"));
                    break;
                // 添加其他命令...
                default:
                    listener.onUnknownCommand(cmd);
            }
        } catch (Exception e) {
            Log.e("handleCommand", "失败: ", e);
        }
    }
}

1.1.4. 权限申请处理

private void checkStartCpd() {
    runOnUiThread(() -> {
        if (checkCameraPermission()) {
            startCpd();
        } else {
            requestMultiplePermissions();
        }
    });
}

private void startCpd() { //加载本地摄像头
    Log.i(TAG, "to startCpd");
    mStreamView.getEngine().startCpd();
}

private boolean checkCameraPermission() {
    return ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
            == PackageManager.PERMISSION_GRANTED;
}

private void requestMultiplePermissions() {
    Log.i(TAG, "requestMultiplePermissions ");
    String[] permissions = {
            Manifest.permission.CAMERA,
            Manifest.permission.RECORD_AUDIO
    };

    ActivityCompat.requestPermissions(
            this,
            permissions,
            MULTI_PERMISSION_REQUEST_CODE
    );
}

@Override
public void onRequestPermissionsResult(int requestCode,
                                       @NonNull String[] permissions,
                                       @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == MULTI_PERMISSION_REQUEST_CODE) {
        handleMultiPermissionResult(permissions, grantResults);
    }
}

private void handleMultiPermissionResult(String[] permissions, int[] grantResults) {
    boolean allGranted = true;
    for (int result : grantResults) {
        if (result != PackageManager.PERMISSION_GRANTED) {
            allGranted = false;
            break;
        }
    }

    if (allGranted) {
        // 所有权限均已授予
        startCpd();
    } else {
        // 有权限被拒绝
        handlePermissionDenied();
    }
}

// 处理有权限被拒绝的情况
private void handlePermissionDenied() {
    // 检查用户是否选择了"不再询问"
    if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
        // 显示引导用户去设置页面的对话框
        new AlertDialog.Builder(this)
                .setTitle("权限被永久拒绝")
                .setMessage("您已永久拒绝相机权限,如需使用此功能,请到设置中手动启用权限")
                .setPositiveButton("去设置", (dialog, which) -> openAppSettings())
                .setNegativeButton("取消", null)
                .create()
                .show();
    } else {
        Toast.makeText(this, "相机权限被拒绝", Toast.LENGTH_SHORT).show();
    }
}

// 打开应用设置页面
private void openAppSettings() {
    Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
    Uri uri = Uri.fromParts("package", getPackageName(), null);
    intent.setData(uri);
    startActivity(intent);
}

1.2. 动态获取录音权限

1.2.1. 工程AndroidManifest.xml配置

<uses-permission android:name="android.permission.RECORD_AUDIO" />

1.2.2. 监听云手机需要申请本机录音权限

mStreamView.getASPEngineDelegate().registerSystemPermissionListener(new IRequestSystemPermissionListener() {
    @Override
    public boolean onRequestSystemPermission(SystemPermission systemPermission) {
        Log.i(TAG, "onRequestSystemPermission " + systemPermission);
        if (systemPermission == RECORD_AUDIO) {
            if (!checkAudioPermission()) {
                new Handler(Looper.getMainLooper()).post(() -> {
                    requestAudioPermission();
                });
                return false;
            }
            return true;
        }
        return false;
    }
});

1.2.3. 权限申请处理

private boolean checkAudioPermission() {
    return ContextCompat.checkSelfPermission(this,
            Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED;
}

private void requestAudioPermission() {
    if (ActivityCompat.shouldShowRequestPermissionRationale(
            this, Manifest.permission.RECORD_AUDIO)) {
        // 显示解释对话框
        new androidx.appcompat.app.AlertDialog.Builder(this)
                .setTitle("需要麦克风权限")
                .setMessage("此功能需要使用您的麦克风进行录音")
                .setPositiveButton("确定", (dialog, which) -> {
                    // 请求权限
                    ActivityCompat.requestPermissions(
                            this,
                            new String[]{Manifest.permission.RECORD_AUDIO},
                            AUDIO_PERMISSION_REQUEST_CODE
                    );
                })
                .setNegativeButton("取消", null)
                .show();
    } else {
        // 直接请求权限
        ActivityCompat.requestPermissions(
                this,
                new String[]{Manifest.permission.RECORD_AUDIO},
                AUDIO_PERMISSION_REQUEST_CODE
        );
    }
}

2. iOS SDK动态获取摄像头权限

2.1. 工程info.plist配置

录音权限
Privacy - Microphone Usage Description
相机权限
Privacy - Camera Usage Description

2.2. 云手机实例配置

//禁止SDK默认加载本地摄像头
streamView.enableSDKLoadCpdToStart = false;

2.3. 监听云手机需要申请本机摄像头权限

@implementation DemoEDSAgentChannel

- (void)onConnectStateChanged:(ASPDCConnectState)state {
    NSLog(@"[DemoEDSAgentChannel] onConnectStateChanged %ld", state);
}
    
- (void)onReceiveData:(NSData * _Nonnull)buf {
    NSString *string = [[NSString alloc] initWithData:buf encoding:NSUTF8StringEncoding];
    NSLog(@"[DemoEDSAgentChannel] onConnectStateChanged %@", string);
    [CommandUtils parseCommand:string listener:self];
}

#pragma mark - CommandListener
- (void)onCameraAuthorize {
    NSLog(@"[DemoEDSAgentChannel] onCameraAuthorize");
    dispatch_async(dispatch_get_main_queue(), ^{
        [self requestCameraPermission];
    });
}
- (void)requestCameraPermission {
    if ([AVCaptureDevice respondsToSelector:@selector(authorizationStatusForMediaType:)]) {
        AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
        switch (status) {
            case AVAuthorizationStatusNotDetermined:
                // 用户尚未授权,请求权限
                [self requestAccess];
                break;
            case AVAuthorizationStatusAuthorized:
                // 已授权,直接启动摄像头
                [self.streamView startCpd];
                break;
            case AVAuthorizationStatusDenied:
            case AVAuthorizationStatusRestricted:
                // 被拒绝或受限,显示提示信息
                [self showPermissionDeniedAlert];
                break;
        }
    } else {
        NSLog(@"DemoEDSAgentChannel 设备不支持摄像头");
        [self showNoCameraAlert];
    }
}

- (void)requestAccess {
    [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
        if (granted) {
            NSLog(@"DemoEDSAgentChannel 摄像头权限已授予");
            [self.streamView startCpd];
        } else {
            NSLog(@"DemoEDSAgentChannel 摄像头权限被拒绝");
            [self showPermissionDeniedAlert];
        }
    }];
}

3. Web SDK动态获取权限

3.1. 禁止连接使用摄像头

connConfig: {
	disableCamera: true, 
}

3.2. 监听云手机需要申请本机摄像头权限

session.addHandle('onConnected', (data) => {
      session.addDataChannelListener('wy_vdagent_default_dc', 'data', data => {
        console.log('data from wy_vdagent_default_dc', data)
        parseCommand(data)
      });
});

function parseCommand(data) {
      try {
          // 移除前缀 "data:"
          const jsonStr = data.replace(/^data:/, '').trim();
          const json = JSON.parse(jsonStr);
          if (json.action !== undefined) {
              handleCommand(json.action, json);
          } else if (json.cmd !== undefined) {
              handleCommand(json.cmd, json);
          } else {
              console.warn("无效命令格式:", jsonStr);
          }
      } catch (e) {
          console.error("解析失败:", data, e);
      }
  }

function handleCommand(cmd, json) {
      try {
          switch (cmd) {
              case "authorizeCamera": 
                  if (session != null) {
                    console.log(`setCameraCanUse true`);
                    session.setLocalConfig('setCameraCanUse', true);
                  }
                  break;
              case "rotation":
                  const rotationValue = parseInt(json.value, 10);
                  break;
              default:
                  break;
          }
      } catch (e) {
          console.error("命令处理失败:", cmd, e);
      }
  }

4. 相关文档