当我们在视频通话时,我有问题建立屏幕录制功能。我是说我想同时开始录屏和视频通话。尝试将https://www.simplifiedcoding.net/video-call-android-tutorial/和http://www.truiton.com/2015/05/capture-record-android-screen-using-mediaprojection-apis/but应用程序在视频通话时启动记录屏幕时突然崩溃。如下所示的代码
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode != REQUEST_CODE) {
Log.e(TAG, "Unknown request code: " + requestCode);
return;
}
if (resultCode != RESULT_OK) {
Toast.makeText(this,
"Screen Cast Permission Denied", Toast.LENGTH_SHORT).show();
mToggleButton.setChecked(false);
return;
}
mMediaProjectionCallback = new MediaProjectionCallback();
mMediaProjection = mProjectionManager.getMediaProjection(resultCode, data);
mMediaProjection.registerCallback(mMediaProjectionCallback, null);
mVirtualDisplay = createVirtualDisplay();
mMediaRecorder.start();
}
这是logcat
public class CallScreenActivity extends BaseActivity {
static final String TAG = CallScreenActivity.class.getSimpleName();
static final String CALL_START_TIME = "callStartTime";
static final String ADDED_LISTENER = "addedListener";
private AudioPlayer mAudioPlayer;
private Timer mTimer;
private UpdateCallDurationTask mDurationTask;
private String mCallId;
private long mCallStart = 0;
private boolean mAddedListener = false;
private boolean mVideoViewsAdded = false;
private TextView mCallDuration;
private TextView mCallState;
private TextView mCallerName;
private static final int REQUEST_CODE = 1000;
private int mScreenDensity;
private MediaProjectionManager mProjectionManager;
private static final int DISPLAY_WIDTH = 720;
private static final int DISPLAY_HEIGHT = 1280;
private MediaProjection mMediaProjection;
private VirtualDisplay mVirtualDisplay;
private MediaProjectionCallback mMediaProjectionCallback;
private ToggleButton mToggleButton;
private MediaRecorder mMediaRecorder;
private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
private static final int REQUEST_PERMISSIONS = 10;
static {
ORIENTATIONS.append(Surface.ROTATION_0, 90);
ORIENTATIONS.append(Surface.ROTATION_90, 0);
ORIENTATIONS.append(Surface.ROTATION_180, 270);
ORIENTATIONS.append(Surface.ROTATION_270, 180);
}
private class UpdateCallDurationTask extends TimerTask {
@Override
public void run() {
CallScreenActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
updateCallDuration();
}
});
}
}
@Override
protected void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putLong(CALL_START_TIME, mCallStart);
savedInstanceState.putBoolean(ADDED_LISTENER, mAddedListener);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
mCallStart = savedInstanceState.getLong(CALL_START_TIME);
mAddedListener = savedInstanceState.getBoolean(ADDED_LISTENER);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_call_screen);
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
mScreenDensity = metrics.densityDpi;
mMediaRecorder = new MediaRecorder();
mProjectionManager = (MediaProjectionManager) getSystemService
(Context.MEDIA_PROJECTION_SERVICE);
mAudioPlayer = new AudioPlayer(this);
mCallDuration = (TextView) findViewById(R.id.callDuration);
mCallerName = (TextView) findViewById(R.id.remoteUser);
mCallState = (TextView) findViewById(R.id.callState);
ImageButton endCallButton = (ImageButton) findViewById(R.id.hangupButton);
mToggleButton = (ToggleButton) findViewById(R.id.toggle);
mToggleButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (ContextCompat.checkSelfPermission(CallScreenActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE) + ContextCompat
.checkSelfPermission(CallScreenActivity.this,
Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale
(CallScreenActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) ||
ActivityCompat.shouldShowRequestPermissionRationale
(CallScreenActivity.this, Manifest.permission.RECORD_AUDIO)) {
mToggleButton.setChecked(false);
Snackbar.make(findViewById(android.R.id.content), R.string.label_permissions,
Snackbar.LENGTH_INDEFINITE).setAction("ENABLE",
new View.OnClickListener() {
@Override
public void onClick(View v) {
ActivityCompat.requestPermissions(CallScreenActivity.this,
new String[]{Manifest.permission
.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO},
REQUEST_PERMISSIONS);
}
}).show();
} else {
ActivityCompat.requestPermissions(CallScreenActivity.this,
new String[]{Manifest.permission
.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO},
REQUEST_PERMISSIONS);
}
} else {
onToggleScreenShare(v);
}
}
});
mCallId = getIntent().getStringExtra(SinchService.CALL_ID);
if (savedInstanceState == null) {
mCallStart = System.currentTimeMillis();
}
endCallButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
endCall();
mToggleButton.setChecked(false);
onToggleScreenShare(mToggleButton);
}
});
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode != REQUEST_CODE) {
Log.e(TAG, "Unknown request code: " + requestCode);
return;
}
if (resultCode != RESULT_OK) {
Toast.makeText(this,
"Screen Cast Permission Denied", Toast.LENGTH_SHORT).show();
mToggleButton.setChecked(false);
return;
}
mMediaProjectionCallback = new MediaProjectionCallback();
mMediaProjection = mProjectionManager.getMediaProjection(resultCode, data);
mMediaProjection.registerCallback(mMediaProjectionCallback, null);
mVirtualDisplay = createVirtualDisplay();
mMediaRecorder.start();
}
public void onToggleScreenShare(View view) {
if (((ToggleButton) view).isChecked()) {
initRecorder();
shareScreen();
} else {
mMediaRecorder.stop();
mMediaRecorder.reset();
Log.v(TAG, "Stopping Recording");
stopScreenSharing();
}
}
private void shareScreen() {
if (mMediaProjection == null) {
startActivityForResult(mProjectionManager.createScreenCaptureIntent(), REQUEST_CODE);
return;
}
mVirtualDisplay = createVirtualDisplay();
mMediaRecorder.start();
}
private VirtualDisplay createVirtualDisplay() {
return mMediaProjection.createVirtualDisplay("CallScreenActivity",
DISPLAY_WIDTH, DISPLAY_HEIGHT, mScreenDensity,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
mMediaRecorder.getSurface(), null /*Callbacks*/, null
/*Handler*/);
}
private void initRecorder() {
try {
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mMediaRecorder.setOutputFile(Environment
.getExternalStorageDirectory().getAbsolutePath() + "/video.mp4");
mMediaRecorder.setVideoSize(DISPLAY_WIDTH, DISPLAY_HEIGHT);
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mMediaRecorder.setVideoEncodingBitRate(512 * 1000);
mMediaRecorder.setVideoFrameRate(30);
int rotation = getWindowManager().getDefaultDisplay().getRotation();
int orientation = ORIENTATIONS.get(rotation + 90);
mMediaRecorder.setOrientationHint(orientation);
mMediaRecorder.prepare();
} catch (IOException e) {
e.printStackTrace();
}
}
private class MediaProjectionCallback extends MediaProjection.Callback {
@Override
public void onStop() {
if (mToggleButton.isChecked()) {
mToggleButton.setChecked(false);
mMediaRecorder.stop();
mMediaRecorder.reset();
Log.v(TAG, "Recording Stopped");
}
mMediaProjection = null;
stopScreenSharing();
}
}
private void stopScreenSharing() {
if (mVirtualDisplay == null) {
return;
}
mVirtualDisplay.release();
//mMediaRecorder.release(); //If used: mMediaRecorder object cannot
// be reused again
destroyMediaProjection();
}
@Override
public void onDestroy() {
super.onDestroy();
destroyMediaProjection();
}
private void destroyMediaProjection() {
if (mMediaProjection != null) {
mMediaProjection.unregisterCallback(mMediaProjectionCallback);
mMediaProjection.stop();
mMediaProjection = null;
}
Log.i(TAG, "MediaProjection Stopped");
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String permissions[],
@NonNull int[] grantResults) {
switch (requestCode) {
case REQUEST_PERMISSIONS: {
if ((grantResults.length > 0) && (grantResults[0] +
grantResults[1]) == PackageManager.PERMISSION_GRANTED) {
onToggleScreenShare(mToggleButton);
} else {
mToggleButton.setChecked(false);
Snackbar.make(findViewById(android.R.id.content), R.string.label_permissions,
Snackbar.LENGTH_INDEFINITE).setAction("ENABLE",
new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setData(Uri.parse("package:" + getPackageName()));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
startActivity(intent);
}
}).show();
}
return;
}
}
}
@Override
public void onServiceConnected() {
Call call = getSinchServiceInterface().getCall(mCallId);
if (call != null) {
if (!mAddedListener) {
call.addCallListener(new SinchCallListener());
mAddedListener = true;
}
} else {
Log.e(TAG, "Started with invalid callId, aborting.");
finish();
}
updateUI();
}
//method to update video feeds in the UI
private void updateUI() {
if (getSinchServiceInterface() == null) {
return; // early
}
Call call = getSinchServiceInterface().getCall(mCallId);
if (call != null) {
mCallerName.setText(call.getRemoteUserId());
mCallState.setText(call.getState().toString());
if (call.getState() == CallState.ESTABLISHED) {
//when the call is established, addVideoViews configures the video to be shown
addVideoViews();
}
}
}
//stop the timer when call is ended
@Override
public void onStop() {
super.onStop();
mDurationTask.cancel();
mTimer.cancel();
removeVideoViews();
}
//start the timer for the call duration here
@Override
public void onStart() {
super.onStart();
mTimer = new Timer();
mDurationTask = new UpdateCallDurationTask();
mTimer.schedule(mDurationTask, 0, 500);
updateUI();
}
@Override
public void onBackPressed() {
// User should exit activity by ending call, not by going back.
}
//method to end the call
private void endCall() {
mAudioPlayer.stopProgressTone();
Call call = getSinchServiceInterface().getCall(mCallId);
if (call != null) {
call.hangup();
}
finish();
}
private String formatTimespan(long timespan) {
long totalSeconds = timespan / 1000;
long minutes = totalSeconds / 60;
long seconds = totalSeconds % 60;
return String.format(Locale.US, "%02d:%02d", minutes, seconds);
}
//method to update live duration of the call
private void updateCallDuration() {
if (mCallStart > 0) {
mCallDuration.setText(formatTimespan(System.currentTimeMillis() - mCallStart));
}
}
//method which sets up the video feeds from the server to the UI of the activity
private void addVideoViews() {
if (mVideoViewsAdded || getSinchServiceInterface() == null) {
return; //early
}
final VideoController vc = getSinchServiceInterface().getVideoController();
if (vc != null) {
RelativeLayout localView = (RelativeLayout) findViewById(R.id.localVideo);
localView.addView(vc.getLocalView());
localView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//this toggles the front camera to rear camera and vice versa
vc.toggleCaptureDevicePosition();
}
});
LinearLayout view = (LinearLayout) findViewById(R.id.remoteVideo);
view.addView(vc.getRemoteView());
mVideoViewsAdded = true;
}
}
//removes video feeds from the app once the call is terminated
private void removeVideoViews() {
if (getSinchServiceInterface() == null) {
return; // early
}
VideoController vc = getSinchServiceInterface().getVideoController();
if (vc != null) {
LinearLayout view = (LinearLayout) findViewById(R.id.remoteVideo);
view.removeView(vc.getRemoteView());
RelativeLayout localView = (RelativeLayout) findViewById(R.id.localVideo);
localView.removeView(vc.getLocalView());
mVideoViewsAdded = false;
}
}
private class SinchCallListener implements VideoCallListener {
@Override
public void onCallEnded(Call call) {
CallEndCause cause = call.getDetails().getEndCause();
Log.d(TAG, "Call ended. Reason: " + cause.toString());
mAudioPlayer.stopProgressTone();
setVolumeControlStream(AudioManager.USE_DEFAULT_STREAM_TYPE);
String endMsg = "Call ended: " + call.getDetails().toString();
Toast.makeText(CallScreenActivity.this, endMsg, Toast.LENGTH_LONG).show();
endCall();
mToggleButton.setChecked(false);
onToggleScreenShare(mToggleButton);
}
@Override
public void onCallEstablished(Call call) {
Log.d(TAG, "Call established");
mAudioPlayer.stopProgressTone();
mCallState.setText(call.getState().toString());
setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
AudioController audioController = getSinchServiceInterface().getAudioController();
audioController.enableSpeaker();
mCallStart = System.currentTimeMillis();
Log.d(TAG, "Call offered video: " + call.getDetails().isVideoOffered());
}
@Override
public void onCallProgressing(Call call) {
Log.d(TAG, "Call progressing");
mAudioPlayer.playProgressTone();
}
@Override
public void onShouldSendPushNotification(Call call, List pushPairs) {
// Send a push through your push provider here, e.g. GCM
}
@Override
public void onVideoTrackAdded(Call call) {
Log.d(TAG, "Video track added");
addVideoViews();
}
}
}
希望有人能帮助我,谢谢大家!
我也面临着同样的问题。IllegalStateException是由于您从另一个线程访问主线程视图,//Play拨号音mdialtone.start();,它给出了异常。只需在主线程上调用initRecorder()即可。它会解决你的问题。
问题内容: (下面的示例代码是独立且可运行的,您可以尝试一下,它不会使系统崩溃:) Tom Hawtin在这里评论了这个问题:为什么人们在事件队列上运行JavaGUI 那: EDT不太可能崩溃。 EDT调度中抛出的未经检查的异常将被捕获,转储并且线程继续运行。 有人可以解释一下这是怎么回事(每次您单击 “引发未经检查的异常” 按钮时,都会有意除以零): 我收到以下消息(这是我期望的): 对我来说,
问题内容: 任何node.js专家都可以告诉我如何在机器启动时配置节点JS以自动启动服务器吗?我在Windows上 问题答案: 根本不需要在node.js中进行配置,这完全是操作系统的职责(在您的情况下为Windows)。实现此目的的最可靠方法是通过Windows服务。 有一个 超级简单的 模块,它可以将节点脚本安装为Windows服务,称为 节点窗口 (npm,github,documentat
问题内容: 我创建了一个简单的java“ echo”应用程序,该应用程序接收用户的输入并将其显示给他们以演示该问题。我可以使用IntelliJ的内部“运行”命令运行该应用程序,并且在执行产生的已编译Java文件时也可以正常运行。但是,如果尝试使用执行应用程序,则会从扫描仪抛出NoSuchElementException异常。 我认为gradle或应用程序插件正在对系统IO做一些奇怪的事情。 应用
我们将使用IntelliJ IDEA/Android Studio来创建这个工程,因此你会对截图看起来比较熟悉。 让我们开始创建一个新的Android工程。你可以创建你自己的工程或者用本书中提供的导入。选择你自己喜欢的创建方式这取决于你。 如果你想用Android Studio创建一个新的工程,通常你可以参考官方文档:http://developer.android.com/intl/zh-cn/
启动引导是指:在应用开始解析并处理新接受请求之前,一个预先准备环境的过程。 启动引导会在两个地方具体进行:入口脚本(Entry Script) 和 应用主体(application)。 在入口脚本里,需注册各个类库的类文件自动加载器(Class Autoloader,简称自动加载器)。 这主要包括通过其 autoload.php 文件加载的Composer 自动加载器,以及通过 Yii 类加载的
该协议通过命令行-c参数提供,并作为Envoy v2根配置。有关更多详细信息,请参阅v2配置概述。 Bootstrap Bootstrap.StaticResources Bootstrap.DynamicResources Bootstrap.DynamicResources.DeprecatedV1 LightstepConfig ZipkinConfig Tracing Tracing.Ht