I have created an android application using the DJI SDK. I have followed the instruction, and basically copied the code from the DJI Sample Code (https://github.com/dji-sdk/Mobile-SDK-Android/blob/master/Sample%20Code/app/src/main/java/com/dji/sdk/sample/demo/camera/LiveStreamView.java), since it was working properly.
After launching a Connectivity activity, which registers the SDK and connects to the Mavic 2 Zoom drone, another activity comes, which handles live streaming to a RTMP server. When using the sample code, streaming to the same RTMP server, it has no delay, but when using my app, it has a good 15 second delay. I can't figure out why, I'm using the same components. The only difference is that I'm setting the camera focus to the max, but I did the same in the Sample Code, so it shouldn't cause any problems. Also using the same VideoFeedView as in the Sample.
public class MainActivity extends Activity implements View.OnClickListener {
private static final String TAG = MainActivity.class.getName();
private String liveShowUrl = "rtmp://192.168.00.00/live";
private VideoFeedView primaryVideoFeedView;
private VideoFeedView fpvVideoFeedView;
private EditText showUrlInputEdit;
private Button startLiveShowBtn;
private Button enableVideoEncodingBtn;
private Button disableVideoEncodingBtn;
private Button stopLiveShowBtn;
private Button soundOnBtn;
private Button soundOffBtn;
private Button isLiveShowOnBtn;
private Button showInfoBtn;
private Button showLiveStartTimeBtn;
private Button showCurrentVideoSourceBtn;
private Button changeVideoSourceBtn;
private Camera camera;
private LiveStreamManager.OnLiveChangeListener listener;
private LiveStreamManager.LiveStreamVideoSource currentVideoSource = LiveStreamManager.LiveStreamVideoSource.Primary;
private CommonCallbacks.CompletionCallback focusSetCompletionCallback = new CommonCallbacks.CompletionCallback() {
@Override
public void onResult(DJIError djiError) {
Log.d(TAG, "Camera focus is set to manual");
Toast.makeText(getApplicationContext(), "camera focus set to manual", Toast.LENGTH_SHORT).show();
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initUI();
initListener();
camera = DronifyApplication.getCameraInstance();
camera.getFocusRingValueUpperBound(new CommonCallbacks.CompletionCallbackWith<Integer>() {
@Override
public void onSuccess(Integer integer) {
Toast.makeText(getApplicationContext(), "UPPER IS: " + integer.toString(),Toast.LENGTH_LONG).show();
Log.d(TAG, "UPPER IS: " + integer.toString());
}
@Override
public void onFailure(DJIError djiError) {
Toast.makeText(getApplicationContext(), "UPPER IS NOT SUPPORTED", Toast.LENGTH_LONG).show();
}
});
camera.setFocusMode(SettingsDefinitions.FocusMode.MANUAL, focusSetCompletionCallback);
if (camera.isAdjustableFocalPointSupported()) {
camera.setFocusRingValue(65, new CommonCallbacks.CompletionCallback() {
@Override
public void onResult(DJIError djiError) {
Log.i(TAG, "set focus ring value to max");
Toast.makeText(getApplicationContext(), "set focus ring value to max", Toast.LENGTH_SHORT).show();
}
});
}
Intent intent = new Intent(getApplication(), TCPService.class);
getApplication().startService(intent);
}
@Override
protected void onResume() {
super.onResume();
}
public static boolean isMultiStreamPlatform() {
if (DJISDKManager.getInstance() == null){
return false;
}
Model model = DJISDKManager.getInstance().getProduct().getModel();
return model != null && (model == Model.INSPIRE_2
|| model == Model.MATRICE_200
|| model == Model.MATRICE_210
|| model == Model.MATRICE_210_RTK
|| model == Model.MATRICE_600
|| model == Model.MATRICE_600_PRO
|| model == Model.A3
|| model == Model.N3);
}
private void initUI() {
primaryVideoFeedView = (VideoFeedView) findViewById(R.id.video_view_primary_video_feed);
primaryVideoFeedView.registerLiveVideo(VideoFeeder.getInstance().getPrimaryVideoFeed(), true);
fpvVideoFeedView = (VideoFeedView) findViewById(R.id.video_view_fpv_video_feed);
fpvVideoFeedView.registerLiveVideo(VideoFeeder.getInstance().getSecondaryVideoFeed(), false);
if (isMultiStreamPlatform()){
fpvVideoFeedView.setVisibility(View.VISIBLE);
}
showUrlInputEdit = (EditText) findViewById(R.id.edit_live_show_url_input);
showUrlInputEdit.setText(liveShowUrl);
startLiveShowBtn = (Button) findViewById(R.id.btn_start_live_show);
enableVideoEncodingBtn = (Button) findViewById(R.id.btn_enable_video_encode);
disableVideoEncodingBtn = (Button) findViewById(R.id.btn_disable_video_encode);
stopLiveShowBtn = (Button) findViewById(R.id.btn_stop_live_show);
soundOnBtn = (Button) findViewById(R.id.btn_sound_on);
soundOffBtn = (Button) findViewById(R.id.btn_sound_off);
isLiveShowOnBtn = (Button) findViewById(R.id.btn_is_live_show_on);
showInfoBtn = (Button) findViewById(R.id.btn_show_info);
showLiveStartTimeBtn = (Button) findViewById(R.id.btn_show_live_start_time);
showCurrentVideoSourceBtn = (Button) findViewById(R.id.btn_show_current_video_source);
changeVideoSourceBtn = (Button) findViewById(R.id.btn_change_video_source);
startLiveShowBtn.setOnClickListener(this);
enableVideoEncodingBtn.setOnClickListener(this);
disableVideoEncodingBtn.setOnClickListener(this);
stopLiveShowBtn.setOnClickListener(this);
soundOnBtn.setOnClickListener(this);
soundOffBtn.setOnClickListener(this);
isLiveShowOnBtn.setOnClickListener(this);
showInfoBtn.setOnClickListener(this);
showLiveStartTimeBtn.setOnClickListener(this);
showCurrentVideoSourceBtn.setOnClickListener(this);
changeVideoSourceBtn.setOnClickListener(this);
}
private void initListener() {
showUrlInputEdit.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
liveShowUrl = s.toString();
}
@Override
public void afterTextChanged(Editable s) {
}
});
listener = new LiveStreamManager.OnLiveChangeListener() {
@Override
public void onStatusChanged(int i) {
//Toast.makeText(getApplicationContext(), "status changed : " + i, Toast.LENGTH_SHORT).show();
}
};
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
BaseProduct product = DronifyApplication.getProductInstance();
if (product == null || !product.isConnected()) {
//Toast.makeText(getApplicationContext(), "disconnected", Toast.LENGTH_SHORT).show();
return;
}
if (isLiveStreamManagerOn()){
DJISDKManager.getInstance().getLiveStreamManager().registerListener(listener);
}
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (isLiveStreamManagerOn()){
DJISDKManager.getInstance().getLiveStreamManager().unregisterListener(listener);
}
}
private boolean isLiveStreamManagerOn() {
if (DJISDKManager.getInstance().getLiveStreamManager() == null) {
//Toast.makeText(getApplicationContext(), "no liveStream manager", Toast.LENGTH_SHORT).show();
return false;
}
return true;
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_start_live_show:
startLiveShow();
break;
case R.id.btn_enable_video_encode:
enableReEncoder();
break;
case R.id.btn_disable_video_encode:
disableReEncoder();
break;
case R.id.btn_stop_live_show:
stopLiveShow();
break;
case R.id.btn_sound_on:
soundOn();
break;
case R.id.btn_sound_off:
soundOff();
break;
case R.id.btn_is_live_show_on:
isLiveShowOn();
break;
case R.id.btn_show_info:
showInfo();
break;
case R.id.btn_show_live_start_time:
showLiveStartTime();
break;
case R.id.btn_show_current_video_source:
showCurrentVideoSource();
break;
case R.id.btn_change_video_source:
changeVideoSource();
break;
default:
break;
}
}
private void enableReEncoder() {
if (!isLiveStreamManagerOn()) {
return;
}
DJISDKManager.getInstance().getLiveStreamManager().setVideoEncodingEnabled(true);
Toast.makeText(getApplicationContext(), "Force Re-Encoder Enabled!", Toast.LENGTH_SHORT).show();
}
private void disableReEncoder() {
if (!isLiveStreamManagerOn()) {
return;
}
DJISDKManager.getInstance().getLiveStreamManager().setVideoEncodingEnabled(false);
Toast.makeText(getApplicationContext(), "Disable Force Re-Encoder!", Toast.LENGTH_SHORT).show();
}
private void soundOn() {
if (!isLiveStreamManagerOn()) {
return;
}
DJISDKManager.getInstance().getLiveStreamManager().setAudioMuted(false);
Toast.makeText(getApplicationContext(), "Sound ON", Toast.LENGTH_SHORT).show();
}
private void soundOff() {
if (!isLiveStreamManagerOn()) {
return;
}
DJISDKManager.getInstance().getLiveStreamManager().setAudioMuted(true);
Toast.makeText(getApplicationContext(), "Sound OFF", Toast.LENGTH_SHORT).show();
}
private void isLiveShowOn() {
if (!isLiveStreamManagerOn()) {
return;
}
Toast.makeText(getApplicationContext(), "Is Live Show On:" + DJISDKManager.getInstance().getLiveStreamManager().isStreaming(), Toast.LENGTH_SHORT).show();
}
private void showLiveStartTime() {
if (!isLiveStreamManagerOn()) {
return;
}
if (!DJISDKManager.getInstance().getLiveStreamManager().isStreaming()){
Toast.makeText(getApplicationContext(), "Please Start Live First", Toast.LENGTH_SHORT).show();
return;
}
long startTime = DJISDKManager.getInstance().getLiveStreamManager().getStartTime();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
String sd = sdf.format(new Date(Long.parseLong(String.valueOf(startTime))));
Toast.makeText(getApplicationContext(), "Live Start Time: " + sd, Toast.LENGTH_SHORT).show();
}
private void changeVideoSource() {
if (!isLiveStreamManagerOn()) {
return;
}
if (!isSupportSecondaryVideo()) {
return;
}
if (DJISDKManager.getInstance().getLiveStreamManager().isStreaming()) {
Toast.makeText(getApplicationContext(), "Before change live source, you should stop live stream!", Toast.LENGTH_SHORT).show();
return;
}
currentVideoSource = (currentVideoSource == LiveStreamManager.LiveStreamVideoSource.Primary) ?
LiveStreamManager.LiveStreamVideoSource.Secoundary :
LiveStreamManager.LiveStreamVideoSource.Primary;
DJISDKManager.getInstance().getLiveStreamManager().setVideoSource(currentVideoSource);
Toast.makeText(getApplicationContext(), "Change Success ! Video Source : " + currentVideoSource.name(), Toast.LENGTH_SHORT).show();
}
private void showCurrentVideoSource(){
Toast.makeText(getApplicationContext(), "Video Source : " + currentVideoSource.name(), Toast.LENGTH_SHORT).show();
}
private boolean isSupportSecondaryVideo(){
if (isMultiStreamPlatform()) {
Toast.makeText(getApplicationContext(), "No secondary video!", Toast.LENGTH_SHORT).show();
return false;
}
return true;
}
private void showInfo() {
StringBuilder sb = new StringBuilder();
sb.append("Video BitRate:").append(DJISDKManager.getInstance().getLiveStreamManager().getLiveVideoBitRate()).append(" kpbs\n");
sb.append("Audio BitRate:").append(DJISDKManager.getInstance().getLiveStreamManager().getLiveAudioBitRate()).append(" kpbs\n");
sb.append("Video FPS:").append(DJISDKManager.getInstance().getLiveStreamManager().getLiveVideoFps()).append("\n");
sb.append("Video Cache size:").append(DJISDKManager.getInstance().getLiveStreamManager().getLiveVideoCacheSize()).append(" frame");
Toast.makeText(getApplicationContext(), sb.toString(), Toast.LENGTH_LONG).show();
}
void startLiveShow() {
Toast.makeText(getApplicationContext(), "start live show: " + isLiveStreamManagerOn(), Toast.LENGTH_SHORT).show();
if (!isLiveStreamManagerOn()) {
Toast.makeText(getApplicationContext(), "1. return", Toast.LENGTH_SHORT).show();
return;
}
if (DJISDKManager.getInstance().getLiveStreamManager().isStreaming()) {
Toast.makeText(getApplicationContext(), "live show already started", Toast.LENGTH_SHORT).show();
return;
}
new Thread() {
@Override
public void run() {
DJISDKManager.getInstance().getLiveStreamManager().setLiveUrl(liveShowUrl);
DJISDKManager.getInstance().getLiveStreamManager().setVideoEncodingEnabled(true);
DJISDKManager.getInstance().getLiveStreamManager().setAudioMuted(false);
final int result = DJISDKManager.getInstance().getLiveStreamManager().startStream();
DJISDKManager.getInstance().getLiveStreamManager().setStartTime();
runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(getApplication(), "RESULT: " + result, Toast.LENGTH_SHORT).show();
}
});
}
}.start();
}
private void stopLiveShow() {
if (!isLiveStreamManagerOn()) {
return;
}
DJISDKManager.getInstance().getLiveStreamManager().stopStream();
Toast.makeText(getApplicationContext(), "stop live show", Toast.LENGTH_SHORT).show();
}
}
Any idea why? I have tested it on Google Pixel 2, and Huawei Mate 10. The sample has no problem on both devices, my app has the delay. Thanks!
Answering my own question, the only difference I noticed was that the SampleCode asked for 4 permission, and all the projects I've tried or copied the permissions, always just 3 permissions.
So Manifest:
< uses-permission android:name="android.permission.RECORD_AUDIO" />
your runtime permissions:
Manifest.permission.RECORD_AUDIO
and the delay is gone, all works fine. Still don't know why :)