Table of Contents |
---|
Пример Android приложения для демонстрации экрана устройства
Пример демонстрирует возможность трансляции экрана устройства. К видео может быть добавлено аудио с микрофона устройства
...
или (для Android 10 и выше) системный звук.
Работа с кодом примера
Для разбора кода возьмем класс ScreenSharingActivity.java примера screen-sharing, который доступен для скачивания в соответствующей сборке1.1.0.5564.
1. Инициализация API.
Flashphoner.init() code
При инициализации методу init() передается объект Сontext.
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.init(this); |
2. Разрешение на использование микрофонаСкрытие или отображение захвата системного звука в зависимости от версии Android
Code Block | ||||
---|---|---|---|---|
| ||||
mMicCheckBox.setOnClickListener(new View.OnClickListener() { @Overrideif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { public void onClickmAudioRadioGroup.setVisibility(View v) {.VISIBLE); } if (mMicCheckBox.isChecked()) else { mAudioRadioGroup.setVisibility(View.GONE); ActivityCompat.requestPermissions(ScreenSharingActivity.this, } |
3. Разрешение на захват звука
Code Block | ||||
---|---|---|---|---|
| ||||
mUseAudioCheckBox.setOnClickListener(v -> { if (mUseAudioCheckBox.isChecked()) { new String[]{Manifest.permission.RECORD_AUDIO}, ActivityCompat.requestPermissions(ScreenSharingActivity.this, PUBLISH_REQUEST_CODE); new String[]{Manifest.permission.RECORD_AUDIO}, } PUBLISH_REQUEST_CODE); } }); |
34. Выбор микрофона
Code Block | ||||
---|---|---|---|---|
| ||||
mMicSpinner = (Spinner) findViewById(R.id.spinner_mic); ArrayAdapter<MediaDevice> arrayAdapter = new ArrayAdapter<MediaDevice>(this, android.R.layout.simple_spinner_item, Flashphoner.getMediaDevices().getAudioList()); arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mMicSpinner.setAdapter(arrayAdapter); |
45. Создание сессии
Flashphoner.createSession() code
Методу передается объект SessionOptions со следующими параметрами
...
Code Block | ||||
---|---|---|---|---|
| ||||
SessionOptions sessionOptions = new SessionOptions(url); sessionOptions.setLocalRenderer(localRender); sessionOptions.setRemoteRenderer(remoteRender); /** * Session for connection to WCS server is created with method createSession(). */ session = Flashphoner.createSession(sessionOptions); |
56. Подключение к серверу.
Session.connect() . code
Code Block | ||||
---|---|---|---|---|
| ||||
session.connect(new Connection()); |
67. Получение от сервера события, подтверждающего успешное соединение.
session.onConnected() code
Code Block | ||||
---|---|---|---|---|
| ||||
@Override public void onConnected(final Connection connection) { runOnUiThread(new Runnable() -> { @Override mStartButton.setText(R.string.action_stop); public void run() { mStartButton.setText(R.string.action_stop); mStartButton.setTag(R.string.action_stop); mStartButton.setEnabled(true); mStatusView.setText(connection.getStatus()); }); .... } }); } |
78. Создание потока и подготовка к публикации
session.createStream() code
Code Block | ||||
---|---|---|---|---|
| ||||
StreamOptions streamOptions = new StreamOptions(streamName); VideoConstraints videoConstraints = new VideoConstraints(); DisplayMetrics metrics = getResources().getDisplayMetrics(); videoConstraints.setResolution(metrics.widthPixels, metrics.heightPixels); videoConstraints.setVideoFps(metrics.densityDpi); streamOptions.getConstraints().setVideoConstraints(videoConstraints); streamOptions.getConstraints().updateAudio(mMicCheckBoxmUseAudioCheckBox.isChecked()); /** * Stream is created with method Session.createStream(). */ publishStream = session.createStream(streamOptions); ... startScreenCapture(); |
89. Подготовка захвата экрана
Code Block | ||||
---|---|---|---|---|
| ||||
private void startScreenCapture() { mMediaProjectionManager = (MediaProjectionManager) getSystemService( Context.MEDIA_PROJECTION_SERVICE); Intent permissionIntent = mMediaProjectionManager.createScreenCaptureIntent(); startActivityForResult(permissionIntent, REQUEST_CODE_CAPTURE_PERM); } |
910. Запуск сервиса, захват экрана и публикация потокаstartService(), setVideoCapturer(), Stream.publish
context.startForegroundService() code
Code Block | ||||
---|---|---|---|---|
| ||||
@Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { if (REQUEST_CODE_CAPTURE_PERM == requestCode super.onActivityResult(requestCode, resultCode, data); if (REQUEST_CODE_CAPTURE_PERM == requestCode && resultCode == RESULT_OK) { serviceIntentthis.mediaProjectionData = new Intent(this, ScreenSharingService.class); data; Context context = startServicegetApplicationContext(serviceIntent); videoCapturer = new ScreenCapturerAndroid(data, new MediaProjection.Callback() {this.serviceIntent = new Intent(context, ScreenSharingService.class); context.startForegroundService(serviceIntent); @Override } else { public void onStoprunOnUiThread(() {-> mStartButton.setEnabled(false)); stop(); super.onStop(); Log.i(TAG, "Permission has been denied by user"); } } |
11. Захват экрана и публикация потока
ScreenCapturerAndroid(), Stream.publish() code
Code Block | ||||
---|---|---|---|---|
| ||||
private final BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent != null) {
if (ScreenSharingService.ACTION_START.equals(intent.getAction())) {
MediaProjection mediaProjection = null;
if (mUseAudioCheckBox.isChecked() && !mUseMicRadioButton.isChecked() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
mediaProjection = mediaProjectionManager.getMediaProjection(Activity.RESULT_OK, mediaProjectionData);
}
WebRTCMediaProvider.getInstance().setMediaProjection(mediaProjection);
videoCapturer = new ScreenCapturerAndroid(mediaProjection, mediaProjectionData, new MediaProjection.Callback() {
@Override
public void onStop() {
super.onStop();
handler.post(ScreenSharingActivity.this::stop);
}
});
WebRTCMediaProvider.getInstance().setVideoCapturer(videoCapturer);
publishStream.publish();
} else if (ScreenSharingService.ACTION_STOP.equals(intent.getAction())) {
handler.post(ScreenSharingActivity.this::stop);
}
}
}
}; |
12. Получение от сервера события, подтверждающего успешную публикацию потока
StreamStatus.PUBLISHING code
При получении данного события создается превью-видеопоток при помощи Session.createStream() и вызывается Stream.play() для его воспроизведения.
Code Block | ||||
---|---|---|---|---|
| ||||
} }); WebRTCMediaProviderpublishStream.getInstance().setVideoCapturer(videoCapturer); /**on(new StreamStatusEvent() { * Method Stream.publish() is called to publish stream. */ publishStream.publish();@Override Log.i(TAG, "Permission has been granted by user"); ... } public } |
10. Получение от сервера события, подтверждающего успешную публикацию потока
StreamStatusEvent PUBLISHING code
При получении данного события создается превью-видеопоток при помощи Session.createStream() и вызывается Stream.play() для его воспроизведения.
Code Block | ||||
---|---|---|---|---|
| ||||
void onStreamStatus(final Stream stream, final StreamStatus streamStatus) { publishStream.on(new StreamStatusEvent() { runOnUiThread(new Runnable() { @Override @Override public void onStreamStatus(final Stream stream, final StreamStatus streamStatus) { public void run() { runOnUiThread(new Runnable() { if (StreamStatus.PUBLISHING.equals(streamStatus)) { @Override /** public void run() { * The options for the stream to play ifare (StreamStatus.PUBLISHING.equals(streamStatus)) { set. /** The stream name is passed when StreamOptions object is created. * The options for the stream to play are set. */ * The stream nameStreamOptions isstreamOptions passed= whennew StreamOptions(streamName); object is created. streamOptions.getConstraints().updateAudio(mUseAudioCheckBox.isChecked()); */ /** StreamOptions streamOptions = new StreamOptions(streamName); * Stream is created with method streamOptionsSession.getConstraintscreateStream().updateAudio(mMicCheckBox.isChecked()); */** * Stream is created with method Session.createStream(). playStream = session.createStream(streamOptions); ... */ playStream.play(); playStream = session.createStream(streamOptions); } else { ... Log.e(TAG, "Can not publish stream " + stream.getName() + " " + playStream.play(streamStatus); } else { mStatusView.setText(streamStatus.toString()); Log.e(TAG, "Can not publish stream " + stream.getName() + " " + streamStatus); } } }); mStatusView.setText(streamStatus.toString()); } }); |
13. Закрытие соединения.
Session.disconnect() code
Code Block | ||||
---|---|---|---|---|
| ||||
private synchronized void }stop() { if (session != null) { session.disconnect(); session })= null; } WebRTCMediaProvider.getInstance().releaseLocalMediaAccess(); if }(serviceIntent != null) { stopService(serviceIntent); this.serviceIntent = null; }); |
11. Закрытие соединения.
Session.disconnect() code
Code Block | ||||
---|---|---|---|---|
| ||||
mStartButton.setEnabled(false); /** * Connection to WCS server is closed with method Session.disconnect()... */ session.disconnect(); |
...
} |
14. Создание сервиса
Service.onCreate(), startForeground() code
Code Block | ||||
---|---|---|---|---|
| ||||
@Override public void onCreate() { super.onCreate(); NotificationChannel chan = new NotificationChannel( { super.onCreate(); NotificationChannel chan = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_NONE); chan.setImportance(NotificationManager.IMPORTANCE_MIN); NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE); manager.createNotificationChannel(chan); final int notificationId = (int) System.currentTimeMillis(); NotificationCompat.Builder notificationBuilder = = (int) System.currentTimeMillis(); Notification.Builder notificationBuilder = new NotificationCompatNotification.Builder(this, CHANNEL_ID); Notification notification = notificationBuilder .setSmallIcon(R.drawable.service_icon) .setOngoing(true) .setShowWhen(true) .setContentTitle("ScreenSharingService is running in the foreground") .setPrioritysetCategory(NotificationManagerNotification.IMPORTANCECATEGORY_MINSERVICE) .setCategory(Notification.CATEGORY_SERVICEaddAction(createStopAction()) .build(); .build(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { NotificationManager notificationManager startForeground(notificationId, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION); } else { = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.notify(NOTIFICATION_IDstartForeground(notificationId, notification); startForeground(notificationId, notification);} } |
1315. Остановка сервиса
Service.onDestroy(), stopForeground() code
Code Block | ||||
---|---|---|---|---|
| ||||
@Override public void onDestroy() { stopForeground(true); super.onDestroy(); } |