Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Table of Contents

Пример Android приложения для демонстрации экрана устройства

Пример демонстрирует возможность трансляции экрана устройства. К видео может быть добавлено аудио с микрофона устройства

...

Image Removedили (для Android 10 и выше) системный звук.

Image Added

Работа с кодом примера

Для разбора кода возьмем класс ScreenSharingActivity.java примера screen-sharing, который доступен для скачивания в соответствующей сборке

...

1.1.0.

...

64.

1. Инициализация API.

Flashphoner.init() code

При инициализации методу init() передается объект Сontext.

Code Block
languagejava
themeRDark
Flashphoner.init(this);

2. Разрешение на использование микрофонаСкрытие или отображение захвата системного звука в зависимости от версии Android

code

Code Block
languagejava
themeRDark
        if mMicCheckBox.setOnClickListener(new View.OnClickListener() (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            @OverridemAudioRadioGroup.setVisibility(View.VISIBLE);
            public void onClick(View v)} else {
                if (mMicCheckBox.isChecked()) {mAudioRadioGroup.setVisibility(View.GONE);

              }

3. Разрешение на захват звука

code

Code Block
languagejava
themeRDark
        ActivityCompatmUseAudioCheckBox.requestPermissions(ScreenSharingActivity.this,
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

Code Block
languagejava
themeRDark
        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);

...

5. Создание сессии

Flashphoner.createSession() code

Методу передается объект SessionOptions со следующими параметрами

  • URL WCS-сервера
  • SurfaceViewRenderer localRenderer, который будет использоваться для отображения видео с

...

  • экрана
  • SurfaceViewRenderer remoteRenderer, который будет использоваться для воспроизведения опубликованного видеопотока
Code Block
languagejava
themeRDark
                    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
languagejava
themeRDark
session.connect(new Connection());

67. Получение от сервера события, подтверждающего успешное соединение.

session.onConnected() code

Code Block
languagejava
themeRDark
@Override
public void onConnected(final Connection connection) {
    runOnUiThread(new Runnable() -> {
        @OverridemStartButton.setText(R.string.action_stop);
       public void run() {
           mStartButton.setText(R mStartButton.setTag(R.string.action_stop);
           mStartButton.setTag(R.string.action_stopmStatusView.setText(connection.getStatus());
           mStartButton.setEnabled(true});
           mStatusView.setText(connection.getStatus());
           ...
       }
   });
}

...

...
}

8. Создание потока и подготовка к публикации

session.createStream() code

Code Block
languagejava
themeRDark
                                    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

Code Block
languagejava
themeRDark
    private void startScreenCapture() {
        mMediaProjectionManager = (MediaProjectionManager) getSystemService(
                Context.MEDIA_PROJECTION_SERVICE);
        Intent permissionIntent = mMediaProjectionManager.createScreenCaptureIntent();
        startActivityForResult(permissionIntent, REQUEST_CODE_CAPTURE_PERM);
    }

9. Захват экрана и публикация потока

setVideoCapturer(), Stream.publish10. Запуск сервиса

context.startForegroundService() code

Code Block
languagejava
themeRDark
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        if (super.onActivityResult(requestCode, resultCode, data);
        if (REQUEST_CODE_CAPTURE_PERM == requestCode && resultCode == RESULT_OK) {

            videoCapturerthis.mediaProjectionData = new ScreenCapturerAndroid(data, new MediaProjection.Callback() {
data;

            Context context = getApplicationContext();
     @Override
       this.serviceIntent = new Intent(context, ScreenSharingService.class);
     public void onStop() {
      context.startForegroundService(serviceIntent);
        } else {
            runOnUiThread(() -> supermStartButton.onStopsetEnabled(false));
            stop();
     }
       Log.i(TAG, "Permission has been denied by }user");
        }
    WebRTCMediaProvider.getInstance().setVideoCapturer(videoCapturer);

            /**
}

11. Захват экрана и публикация потока

ScreenCapturerAndroid(), Stream.publish() code

Code Block
languagejava
themeRDark
    private final BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
        @Override
        *public Methodvoid Stream.publishonReceive()Context iscontext, calledIntent to publish stream.intent) {
             */
if (intent != null) {
                 publishStream.publish();if (ScreenSharingService.ACTION_START.equals(intent.getAction())) {
            Log.i(TAG, "Permission has been granted by user");
            MediaProjection mediaProjection = null;
        ...
        }
    }

10. Получение от сервера события, подтверждающего успешную публикацию потока

StreamStatusEvent PUBLISHING code

При получении данного события создается превью-видеопоток при помощи Session.createStream() и вызывается Stream.play() для его воспроизведения.

Code Block
languagejava
themeRDark
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
languagejava
themeRDark
                                    publishStream.on(new StreamStatusEvent() {
                                        @Override
                                        public void onStreamStatus(final Stream stream, final StreamStatus streamStatus) {
                                            runOnUiThread(new Runnable() {
                                                @Override
                                                public void run() {
                                                    if (StreamStatus.PUBLISHING.equals(streamStatus)) {

                                                        /**
                                                         * The options for the stream to play are set.
                                                         * The stream name is passed when StreamOptions object is created.
                                         publishStream.on(new StreamStatusEvent() {
              */
                          @Override
                              StreamOptions streamOptions = new StreamOptions(streamName);
      public void onStreamStatus(final Stream stream, final StreamStatus streamStatus) {
                                            runOnUiThread(new Runnable() {streamOptions.getConstraints().updateAudio(mUseAudioCheckBox.isChecked());

                                                @Override
        /**
                                        public void run() {
              * Stream is created with method Session.createStream().
                                if (StreamStatus.PUBLISHING.equals(streamStatus)) {

                         */
                                     /**
                   playStream = session.createStream(streamOptions);
                                    * The options for the stream to play are set.
           ...
                                              * The stream name is passed when StreamOptions object is createdplayStream.play();
                                                    } else {
   */
                                                     Log.e(TAG, "Can not StreamOptionspublish streamOptionsstream =" new+ StreamOptionsstream.getName(streamName);
 + " " + streamStatus);
                                                    streamOptions.getConstraints().updateAudio(mMicCheckBox.isChecked());
}
                                                        /**mStatusView.setText(streamStatus.toString());
                                                }
         * Stream is created with method Session.createStream().
                             });
                            */
            }
                                            playStream = session.createStream(streamOptions);
});

13. Закрытие соединения.

Session.disconnect() code

Code Block
languagejava
themeRDark
      private synchronized void stop() {
        if (session != null) {
            session.disconnect();
            session = null;
        ...}

        WebRTCMediaProvider.getInstance().releaseLocalMediaAccess();

        if (serviceIntent != null) {
            stopService(serviceIntent);
            this.serviceIntent =  null;
         playStream.play();}

        ...
    }

14. Создание сервиса

Service.onCreate(), startForeground() code

Code Block
languagejava
themeRDark
    @Override
    public void onCreate() {
        super.onCreate();

        NotificationChannel chan = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_NONE);
       } else { chan.setImportance(NotificationManager.IMPORTANCE_MIN);

        NotificationManager manager =
                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
        manager.createNotificationChannel(chan);

        final int notificationId = (int) LogSystem.e(TAG, "Can not publish stream " + stream.getName() + " " + streamStatuscurrentTimeMillis();
        Notification.Builder notificationBuilder = new Notification.Builder(this, CHANNEL_ID);
        Notification notification =
                notificationBuilder
                          }
   .setSmallIcon(R.drawable.service_icon)
                         .setOngoing(true)
                        mStatusView.setText(streamStatus.toString());setShowWhen(true)
                        .setContentTitle("ScreenSharingService is running in the foreground")
                   }
     .setCategory(Notification.CATEGORY_SERVICE)
                         .addAction(createStopAction())
              });
          .build();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            startForeground(notificationId,      }notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION);
        } else {
            startForeground(notificationId, notification);
        }
     });

11. Закрытие соединения.

Session.disconnect15. Остановка сервиса

Service.onDestroy(), stopForeground() code

Code Block
languagejava
themeRDark
mStartButton.setEnabled(false);

/**
  * Connection to WCS server is closed with method Session.disconnect().
  */
session.disconnect();    @Override
    public void onDestroy() {
        stopForeground(true);
        super.onDestroy();
    }