Versions Compared

Key

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

...

Данный пример может использоваться как стример для публикации WebRTC-видеопотока с Web Call Server и позволяет выбрать медиа-устройства и параметры для публикуемого видео

Image Modified


Пример переключения объекта для вывода изображения с камеры

Image Modified

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

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

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

Flashphoner.init() код code

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

...

Flashphoner.getMediaDevices(), MediaDeviceList.getAudioList(), MediaDeviceList.getVideoList() код code

Code Block
languagejava
themeRDark
mMicSpinner = (LabelledSpinner) findViewById(R.id.microphone);
mMicSpinner.setItemsArray(Flashphoner.getMediaDevices().getAudioList());

mMicLevel = (TextView) findViewById(R.id.microphone_level);

mCameraSpinner = (LabelledSpinner) findViewById(R.id.camera);
mCameraSpinner.setItemsArray(Flashphoner.getMediaDevices().getVideoList());

3. Настройка параметров аудио и видео, заданных пользователем

AudioConstraints, VideoConstraints код

...

languagejava
themeRDark

...

Управление отображением видео

FPSurfaceViewRenderer.setMirror() code

При показе видео изображение выводится на объекты FPSurfaceViewRenderer:

  • localRender для отображения видео с камеры
  • remoteRender для отображения публикуемого потока
  • newSurfaceRenderer для демонстрации переключения объекта

Для этих объектов устанавливается позиция на экране, тип масштабирования и зеркалирование.

По умолчанию, для отображения видео с камеры устанавливается зеркальная ориентация при помощи метода setMirror(true). Для отображения публикуемого потока и объекта для демонстрации переключения зеркалирование отключается при помощи setMirror(false):

Code Block
languagejava
themeRDark
        remoteRenderLayout.setPosition(0, 0, 100, 100);
    if (mSendAudio.isChecked()) {    remoteRender.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);
        audioConstraints = new AudioConstraints(remoteRender.setMirror(false);
        if (mUseFEC.isChecked()) {remoteRender.requestLayout();

        localRenderLayout.setPosition(0, 0,   audioConstraints.setUseFEC(true100, 100);
        }
        if (mUseStereo.isChecked()) {localRender.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);
            audioConstraints.setUseStereolocalRender.setMirror(true);
        }localRender.requestLayout();

        if (!mDefaultPublishAudioBitrate.isChecked() && mDefaultPublishAudioBitrate.getText().length() > 0) {switchRenderLayout.setPosition(0, 0, 100, 100);
            audioConstraints.setBitrate(Integer.parseInt(mPublishAudioBitrate.getText().toString()))newSurfaceRenderer.setZOrderMediaOverlay(true);
        }
    }newSurfaceRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);
    VideoConstraints videoConstraints = null;
    if (mSendVideo.isChecked()) { newSurfaceRenderer.setMirror(true);
        videoConstraints = new VideoConstraints();
        videoConstraints.setCameraId(((MediaDevice) mCameraSpinner.getSpinner().getSelectedItem()).getId());
        if (mCameraFPS.getText().length() > 0) {
            videoConstraints.setVideoFps(Integer.parseInt(mCameraFPS.getText().toString()));
        }
        if (mWidth.getText().length() > 0 && mHeight.getText().length() > 0) newSurfaceRenderer.requestLayout();

В данном случае, при выборе фронтальной камеры изображение с камеры выглядит нормально, но публикуется зеркальным. При выборе тыловой камеры изображение с камеры будет выглядеть зеркальным, а публикуемый поток будет иметь нормальную ориентацию (см. скриншоты приложения выше).

4. Настройка параметров аудио и видео, заданных пользователем

AudioConstraints, VideoConstraints code

Code Block
languagejava
themeRDark
@NonNull
private Constraints getConstraints() {
    AudioConstraints audioConstraints = null;
    if (mSendAudio.isChecked()) {
        audioConstraints = new AudioConstraints();
        if (mUseFEC.isChecked()) {
            audioConstraints.setUseFEC(true);
        }
        if (mUseStereo.isChecked()) {
            audioConstraints.setUseStereo(true);
        }
        if (!mDefaultPublishAudioBitrate.isChecked() && mDefaultPublishAudioBitrate.getText().length() > 0) {
            audioConstraints.setBitrate(Integer.parseInt(mPublishAudioBitrate.getText().toString()));
        }
    }
    VideoConstraints videoConstraints = null;
    if (mSendVideo.isChecked()) {
        videoConstraints = new VideoConstraints();
        videoConstraints.setCameraId(((MediaDevice) mCameraSpinner.getSpinner().getSelectedItem()).getId());
        if (mCameraFPS.getText().length() > 0) {
            videoConstraints.setVideoFps(Integer.parseInt(mCameraFPS.getText().toString()));
        }
        if (mWidth.getText().length() > 0 && mHeight.getText().length() > 0) {
            videoConstraints.setResolution(Integer.parseInt(mWidth.getText().toString()),
                    Integer.parseInt(mHeight.getText().toString()));
        }
        if (!mDefaultPublishVideoBitrate.isChecked() && mPublishVideoBitrate.getText().length() > 0) {
             videoConstraints.setBitrate(Integer.parseInt(mPublishVideoBitrate.getText().toString()));
        }
    }
    return new Constraints(audioConstraints, videoConstraints);
}

5. Локальное тестирование камеры и микрофона

Flashphoner.getLocalMediaAccess() code

Методу передаются:

  • настройки аудио и видео, заданные пользователем
  • локальный объект SurfaceViewRenderer localRenderer для вывода изображения с выбранной камеры
Code Block
languagejava
themeRDark
case TEST_REQUEST_CODE: {
    if (grantResults.length == 0 ||
          grantResults[0] != PackageManager.PERMISSION_GRANTED ||
          grantResults[1] != PackageManager.PERMISSION_GRANTED) {
        Log.i(TAG, "Permission has been denied by user");
    } else {
        Flashphoner.getLocalMediaAccess(getConstraints(), localRender);
        mTestButton.setText(R.string.action_release);
        mTestButton.setTag(R.string.action_release);
        mStartButton.setEnabled(false);
        soundMeter = new SoundMeter();
        soundMeter.start();
        ...
        Log.i(TAG, "Permission has been granted by user");
    }
break;

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

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);

7. Подключение к серверу.

Session.connect(). code

Code Block
languagejava
themeRDark
session.connect(new Connection());

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

session.onConnected() code

Code Block
languagejava
themeRDark
@Override
public void onConnected(final Connection connection) {
   runOnUiThread(new Runnable() {
       @Override
       public void run() {
            videoConstraintsmStartButton.setResolutionsetText(IntegerR.parseInt(mWidth.getText().toString()),string.action_stop);
           mStartButton.setTag(R.string.action_stop);
         Integer  mStartButton.parseInt(mHeight.getText().toString()));
setEnabled(true);
          } mTestButton.setEnabled(false);
        if (!mDefaultPublishVideoBitrate.isChecked() && mPublishVideoBitratemStatusView.getTextsetText()connection.lengthgetStatus() > 0) {
  );;
           videoConstraints.setBitrate(Integer.parseInt(mPublishVideoBitrate.getText().toString()));
        }
    }
    return new Constraints(audioConstraints, videoConstraints);
}

4. Локальное тестирование камеры и микрофона

Flashphoner.getLocalMediaAccess() код

Методу передаются:

...

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

session.createStream() code

Code Block
languagejava
themeRDark
case TEST_REQUEST_CODE:publishStream = session.createStream(streamOptions);
if (mMuteAudio.isChecked()) {
    publishStream.muteAudio();
}
if (grantResults.length == 0 ||mMuteVideo.isChecked()) {
    publishStream.muteVideo();
}
...

ActivityCompat.requestPermissions(MediaDevicesActivity.this,
         new grantResultsString[0] != PackageManager.PERMISSION_GRANTED ||]{Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA},
          grantResults[1] != PackageManager.PERMISSION_GRANTED)PUBLISH_REQUEST_CODE);

10. Публикация потока

Stream.publish() code

Code Block
languagejava
themeRDark
case PUBLISH_REQUEST_CODE: {
    if (grantResults.length ==  Log.i(TAG, "Permission has been denied by user");
0 ||
          } else {
  grantResults[0] != PackageManager.PERMISSION_GRANTED ||
      Flashphoner.getLocalMediaAccess(getConstraints(), localRender);
    grantResults[1]    mTestButton.setText(R.string.action_release);!= PackageManager.PERMISSION_GRANTED) {
        mTestButtonmStartButton.setTag(R.string.action_releasesetEnabled(false);
        mStartButtonmTestButton.setEnabled(false);
        soundMeter = new SoundMetersession.disconnect();
        soundMeterLog.start(i(TAG, "Permission has been denied by user");
    } else   ...{
        Log.i(TAG, "Permission has been granted by user");
    }
break;

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

Flashphoner.createSession() код

Методу передается объект 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);

6. Подключение к серверу.

...

/**
          * Method Stream.publish() is called to publish stream.
          */
        publishStream.publish();
        Log.i(TAG, "Permission has been granted by user");
    }
    break;
}

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

StreamStatusEvent PUBLISHING code

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

Code Block
languagejava
themeRDark
sessionpublishStream.connecton(new Connection());

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

session.onConnected() код

Code Block
languagejava
themeRDark
@Override
public void onConnected(final Connection connection) {
   runOnUiThread(new Runnable() {
StreamStatusEvent() {
    @Override
    public void onStreamStatus(final Stream stream, final StreamStatus streamStatus) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (StreamStatus.PUBLISHING.equals(streamStatus)) {
              @Override
       public void run() {
/**
                 mStartButton.setText(R.string.action_stop);
     * The options for the  mStartButton.setTag(R.string.action_stop);
   stream to play are set.
        mStartButton.setEnabled(true);
           mTestButton.setEnabled(false);
   * The stream name is passed when  mStatusView.setText(connection.getStatus());;
 StreamOptions object is created.
          ...
       }
   });
}

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

session.createStream() код

Code Block
languagejava
themeRDark
publishStream = session.createStream(streamOptions);
if (mMuteAudio.isChecked()) {
 */
                  publishStream.muteAudio();
}
if (mMuteVideo.isChecked()) {
    publishStream.muteVideo();
}
...

ActivityCompat.requestPermissions(MediaDevicesActivity.this, StreamOptions streamOptions = new StreamOptions(streamName);

         new String[]{Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA},
            PUBLISH_REQUEST_CODE);

9. Публикация потока

Stream.publish() код

Code Block
languagejava
themeRDark
case PUBLISH_REQUEST_CODE: {
streamOptions.setConstraints(new Constraints(mReceiveAudio.isChecked(), mReceiveVideo.isChecked()));

      if (grantResults.length == 0 ||
          VideoConstraints grantResults[0]videoConstraints != PackageManager.PERMISSION_GRANTED ||
= null;
                   grantResults[1] != PackageManager.PERMISSION_GRANTED if (mReceiveVideo.isChecked()) {
        mStartButton.setEnabled(false);
        mTestButton.setEnabled(false);
        videoConstraints = new session.disconnectVideoConstraints();
        Log.i(TAG, "Permission has been denied by user");
              } else {...
        /**
          * Method Stream.publish() is called to publish stream.
 }
               */
     AudioConstraints audioConstraints = publishStream.publish()null;
        Log.i(TAG, "Permission has been granted by user");
    }
    break;
}

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

StreamStatusEvent PUBLISHING код

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

Code Block
languagejava
themeRDark
publishStream.on(new StreamStatusEvent() {
    @Override   if (mReceiveAudio.isChecked()) {
                        audioConstraints = new AudioConstraints();
                    }
    public void onStreamStatus(final Stream stream, final StreamStatus streamStatus) {
        runOnUiThreadstreamOptions.setConstraints(new RunnableConstraints() {audioConstraints, videoConstraints));
            @Override
        String[] stripCodec =  public void run() {
{(String) mStripPlayerCodec.getSpinner().getSelectedItem()};
                   if (StreamStatus.PUBLISHING.equals(streamStatus)) {streamOptions.setStripCodecs(stripCodec);
                    /**
                      * Stream is created with method Session.createStream().
   * The options for the stream to play are set.
          */
            * The stream name is passed when StreamOptions objectplayStream is= createdsession.createStream(streamOptions);
                    ...
  */
                  /**
      StreamOptions streamOptions = new StreamOptions(streamName);

            * Method Stream.play() is called to start playback streamOptions.setConstraints(new Constraints(mReceiveAudio.isChecked(), mReceiveVideo.isChecked()));

 of the stream.
                   VideoConstraints videoConstraints = null;*/
                    if (mReceiveVideo.isCheckedplayStream.play()) {;
                        videoConstraints = new VideoConstraints();if (mSendVideo.isChecked())
                        ...mSwitchCameraButton.setEnabled(true);
                    }mSwitchRendererButton.setEnabled(true);
               } else {
   AudioConstraints audioConstraints = null;
             Log.e(TAG, "Can not publish stream " + if (mReceiveAudio.isCheckedstream.getName()) {
+ " " + streamStatus);
               }
     audioConstraints  = new AudioConstraints();
         mStatusView.setText(streamStatus.toString());
           }
       });
             streamOptions.setConstraints(new Constraints(audioConstraints, videoConstraints));}
});

12. Переключение камеры во время трансляции

Stream.switchCamera() code

Code Block
languagejava
themeRDark
mSwitchCameraButton.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View view) {
        String[] stripCodec = {(String) mStripPlayerCodec.getSpinner().getSelectedItem()};if (publishStream != null) {
                    streamOptions.setStripCodecs(stripCodecmSwitchCameraButton.setEnabled(false);
            publishStream.switchCamera(new CameraSwitchHandler() {
      /**
          @Override
            * Stream is created with method Session.createStream().
        public void onCameraSwitchDone(boolean var1) {
               */
     runOnUiThread(new Runnable() {
             playStream = session.createStream(streamOptions);
         @Override
           ...
             public void run() {
    /**
                      * Method StreammSwitchCameraButton.playsetEnabled(true);
 is called to start playback of the stream.
                }
      */
              });

      playStream.play();
          }

          if (mSendVideo.isChecked())
     @Override
                public void  mSwitchCameraButton.setEnabled(true);
onCameraSwitchError(String var1) {
                     mSwitchRendererButton.setEnabled(true);runOnUiThread(new Runnable() {
               }   else {
     @Override
              Log.e(TAG, "Can not publish stream " + stream.getName() + " "public +void streamStatusrun(); {
               }
               mStatusViewmSwitchCameraButton.setText(streamStatus.toString()setEnabled(true);
              }
          });
      }
});

11. Переключение камеры во время трансляции

Stream.switchCamera() код

Code Block
languagejava
themeRDark
mSwitchCameraButton.setOnClickListener(new OnClickListener() {
       @Override
    public void onClick(View view}) {
;
            if (publishStream != null) {}
            mSwitchCameraButton.setEnabled(false});
        }
       publishStream.switchCamera}

});

13. Переключение объекта для отображения видеопотока во время трансляции

Stream.switchRenderer() code

Code Block
languagejava
themeRDark
mSwitchRendererButton.setOnClickListener(new CameraSwitchHandlerOnClickListener() {
                @Override
                public void onCameraSwitchDoneonClick(booleanView var1v) {
                    runOnUiThread(new Runnableif (spinner.getSelectedItemId() == 0){
            if     (isSwitchRemoteRenderer) {
       @Override
         playStream.switchRenderer(remoteRender);
               public voidisSwitchRemoteRenderer run() {= false;
            }
            if    mSwitchCameraButton.setEnabled(true);(!isSwitchLocalRenderer) {
                publishStream.switchRenderer(newSurfaceRenderer);
        }
        isSwitchLocalRenderer = true;
          });

  } else {
            }

      publishStream.switchRenderer(localRender);
          @Override
      isSwitchLocalRenderer = false;
        public void onCameraSwitchError(String var1) {}
        } else {
          runOnUiThread(new Runnable(  if (isSwitchLocalRenderer) {
                  publishStream.switchRenderer(localRender);
      @Override
          isSwitchLocalRenderer = false;
            public}
 void run() {
         if (!isSwitchRemoteRenderer) {
                 mSwitchCameraButtonplayStream.setEnabledswitchRenderer(truenewSurfaceRenderer);
                isSwitchRemoteRenderer = true;
            }
 else {
                  }playStream.switchRenderer(remoteRender);
                isSwitchRemoteRenderer }= false;
            });
        }
    }

});

12. Переключение объекта для отображения видеопотока во время трансляции

Stream.switchRenderer() код14. Управление звуком при помощи аппаратных кнопок

Flashphoner.setVolume() code

Code Block
languagejava
themeRDark
mSwitchRendererButton.setOnClickListener(new OnClickListener(    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
    @Override
    int publiccurrentVolume void= onClick(View v) {Flashphoner.getVolume();
        ifswitch (spinner.getSelectedItemId() == 0)keyCode) {
            if (isSwitchRemoteRenderer) {
case KeyEvent.KEYCODE_VOLUME_DOWN:
                if playStream.switchRenderer(remoteRender);
    (currentVolume == 1) {
            isSwitchRemoteRenderer = false;
        Flashphoner.setVolume(0);
    }
            if (!isSwitchLocalRenderer) {}
                publishStreammPlayVolume.switchRenderersetProgress(newSurfaceRenderercurrentVolume-1);
                isSwitchLocalRenderer = truebreak;
            } else {
case KeyEvent.KEYCODE_VOLUME_UP:
                if publishStream.switchRenderer(localRender);(currentVolume == 0) {
                isSwitchLocalRenderer   = false Flashphoner.setVolume(1);
            }
        }
 else  {
            if (isSwitchLocalRenderer) {mPlayVolume.setProgress(currentVolume+1);
                publishStream.switchRenderer(localRender)break;
        }
        isSwitchLocalRenderer = falsereturn super.onKeyDown(keyCode, event);
        }

15. Использование внешнего динамика телефона

Flashphoner.getAudioManager().isSpeakerphoneOn(), Flashphoner.getAudioManager().setUseSpeakerPhone() code

Code Block
languagejava
themeRDark
    }
    mSpeakerPhone =       if (!isSwitchRemoteRenderer) {(CheckBox) findViewById(R.id.use_speakerphone);
                playStream.switchRenderer(newSurfaceRenderermSpeakerPhone.setChecked(Flashphoner.getAudioManager().getAudioManager().isSpeakerphoneOn());
                isSwitchRemoteRenderer = true;mSpeakerPhone.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            }@Override
 else {
          public void onCheckedChanged(CompoundButton buttonView, boolean  playStream.switchRenderer(remoteRender);isChecked) {
                isSwitchRemoteRenderer = falseFlashphoner.getAudioManager().setUseSpeakerPhone(isChecked);
            }
        }
    }
});

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

Session.disconnect() код code

Code Block
languagejava
themeRDark
mStartButton.setEnabled(false);
mTestButton.setEnabled(false);

/**
  * Connection to WCS server is closed with method Session.disconnect().
  */
session.disconnect();

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

session.onDisconnection() код code

Code Block
languagejava
themeRDark
@Override
public void onDisconnection(final Connection connection) {
    runOnUiThread(new Runnable() {
       @Override
       public void run() {
           mStartButton.setText(R.string.action_start);
           mStartButton.setTag(R.string.action_start);
           mStartButton.setEnabled(true);
           mSwitchCameraButton.setEnabled(false);
           mSwitchRendererButton.setEnabled(false);
           mStatusView.setText(connection.getStatus());
           mTestButton.setEnabled(true);
      }
   });
}