Closed IakovlevAA closed 4 months ago
Why are you putting sleeping timer and not using (nesting your code) into success callback instead ?
For instance enable is followed by sleep, why don't you put the code after the sleep into the success callback of enable instead ?
I think I'll try your method, but theoretically I don't need to nest it because for example:
MediaDataCenter.getInstance().mediaManager.enable(object :
CommonCallbacks.CompletionCallback {
override fun onSuccess() {
var currentTime = System.currentTimeMillis()
MediaDataCenter.getInstance().mediaManager.pullMediaFileListFromCamera(
PullMediaFileListParam.Builder().mediaFileIndex(-1).count(-1).filter(MediaFileFilter.PHOTO).build(),
object :
CommonCallbacks.CompletionCallback {
override fun onSuccess() {
ToastUtils.showToast("Spend time:${(System.currentTimeMillis() - currentTime) / 1000}s")
println("fetch success")
mediafiles = MediaDataCenter.getInstance().mediaManager.mediaFileListData.data
}
override fun onFailure(error: IDJIError) {
ToastUtils.showToast("fetch failed$error")
}
})
}
override fun onFailure(error: IDJIError) {
ToastUtils.showToast("error is ${error.description()}")
}
})
This code, if I understood you, doesn't look correct to me...
This code is correct, this is kind of callback hell (will lead to nested callback inside nested callback and so on), but it is the correct way to handle asynchronous execution.
Promise Like with await/async is much better to read: https://kotlinlang.org/docs/composing-suspending-functions.html#concurrent-using-async
Thanks for tips! Interesting what others think about it. I'm just 100% confident, that there is another way, but not nesting callbacks
Yes async await as explained in the docs above.
Callback is error prone and not maintanable.
Promise with then is better for maintenance.
Async await is the best option
By the way, I tested right now (removing all the timers) I have no issue of slow down like you have, I believe that your timer does not help..
Do you mean slow down of fetching mediafiles or pulling image? Without timers I couldn't get code to work at all
As Antony mentioned, I tried nesting callbacks. Also I found, that if you call CameraKey.KeyStartShootPhoto.create().action() and after that trying to do something with MediaDataCenter or MediaManager it will process comands a very long time(enabling will always fail btw). So, that was the reason why fetching and downloading was too slow
Call enabling with MediaManager instead.
It will work then
No, still failing with this code
fun takePhoto(callback: CommonCallbacks.CompletionCallback) {
RxUtil.setValue(
KeyTools.createKey<CameraMode>(
CameraKey.KeyCameraMode
), CameraMode.PHOTO_NORMAL)
.andThen(RxUtil.performActionWithOutResult(KeyTools.createKey(CameraKey.KeyStartShootPhoto)))
.subscribe({ CallbackUtils.onSuccess(callback) }
) { throwable: Throwable ->
CallbackUtils.onFailure(
callback,
(throwable as RxError).djiError
)
}
}
fun shootPhoto()
{
takePhoto(object : CommonCallbacks.CompletionCallback {
override fun onSuccess() {
ToastUtils.showToast("take photo success")
var currentTime = System.currentTimeMillis()
MediaManager.getInstance().enable(object :
CommonCallbacks.CompletionCallback {
override fun onSuccess() {
ToastUtils.showToast("enable playback success")
val mediaSource = MediaFileListDataSource.Builder().setLocation(CameraStorageLocation.SDCARD).build()
MediaDataCenter.getInstance().mediaManager.setMediaFileDataSource(mediaSource)
MediaDataCenter.getInstance().mediaManager.pullMediaFileListFromCamera(
PullMediaFileListParam.Builder().mediaFileIndex(-1).count(-1).filter(MediaFileFilter.PHOTO).build(),
object :
CommonCallbacks.CompletionCallback {
override fun onSuccess() {
ToastUtils.showToast("Spend time:${(System.currentTimeMillis() - currentTime) / 1000}s")
println("fetch success")
mediafiles = MediaDataCenter.getInstance().mediaManager.mediaFileListData.data
downloadFile()
}
override fun onFailure(error: IDJIError) {
ToastUtils.showToast("fetch failed$error")
}
})
}
override fun onFailure(error: IDJIError) {
ToastUtils.showToast("error is ${error.description()}")
}
})
}
override fun onFailure(error: IDJIError) {
ToastUtils.showToast("take photo failed")
}
})
}
Noticed that there is an error with this code
java.lang.NoClassDefFoundError: Failed resolution of: Ldji/v5/common/callback/CommonCallbacks$CompletionCallback;
But if I insert this code in begining of function shootPhoto
if(CameraKey.KeyIsShootingPhoto.create().get() == true)
{
CameraKey.KeyStopShootPhoto.create().action()
}
MediaManager won't be enabled
Weird.
Working on my side, I use 300 RTK for testing
If ever you do not find with the official DJI support then go to my profile page and take the $50 sponsor I will give you support.
Tried another approach:
fun pullMedia()
{
var currentTime = System.currentTimeMillis()
MediaManager.getInstance().enable(object :
CommonCallbacks.CompletionCallback {
override fun onSuccess() {
ToastUtils.showToast("enable playback success")
val mediaSource = MediaFileListDataSource.Builder().setLocation(CameraStorageLocation.SDCARD).build()
MediaDataCenter.getInstance().mediaManager.setMediaFileDataSource(mediaSource)
MediaDataCenter.getInstance().mediaManager.pullMediaFileListFromCamera(
PullMediaFileListParam.Builder().mediaFileIndex(-1).count(-1).filter(MediaFileFilter.PHOTO).build(),
object :
CommonCallbacks.CompletionCallback {
override fun onSuccess() {
ToastUtils.showToast("Spend time:${(System.currentTimeMillis() - currentTime) / 1000}s")
println("fetch success")
mediafiles = MediaDataCenter.getInstance().mediaManager.mediaFileListData.data
}
override fun onFailure(error: IDJIError) {
ToastUtils.showToast("fetch failed$error")
}
})
}
override fun onFailure(error: IDJIError) {
ToastUtils.showToast("error is ${error.description()}")
}
})
}
fun shootPhoto(): ByteArray {
rotateCamera()
KeyManager.getInstance().performAction(KeyTools.createKey(CameraKey.KeyResetCameraSetting), object: CommonCallbacks.CompletionCallbackWithParam<EmptyMsg>{
override fun onSuccess(t: EmptyMsg?) {
ToastUtils.showToast("reset photo success")
KeyManager.getInstance().performAction(KeyTools.createKey(CameraKey.KeyStartShootPhoto), object: CommonCallbacks.CompletionCallbackWithParam<EmptyMsg>{
override fun onSuccess(t: EmptyMsg?) {
ToastUtils.showToast("take photo success")
pullMedia()
}
override fun onFailure(error: IDJIError) {
ToastUtils.showToast("take photo failed $error")
}
})
}
override fun onFailure(error: IDJIError) {
ToastUtils.showToast("reset photo failed $error")
}
})
First shoot is ok, but after that CameraKey.KeyStartShootPhoto returns CANNOT_START_TASK_VLOTAGE_ALARM error. I've read that I must set camera mode, but it doesn't help
Agent comment from yating.liao in Zendesk ticket #110093:
I have reviewed your code. After switching the camera mode, you directly take a photo and then use mediaManager to retrieve the information. The sequence is correct, but we need to consider the time required for the camera to switch modes and generate the photo.
I would recommend adjusting your code according to this logic:
°°°
Thanks, I'll try this. I have some questions:
Agent comment from yating.liao in Zendesk ticket #110093:I have reviewed your code. After switching the camera mode, you directly take a photo and then use mediaManager to retrieve the information. The sequence is correct, but we need to consider the time required for the camera to switch modes and generate the photo.
I would recommend adjusting your code according to this logic:
- Listen for changes in KeyCameraMode to determine when the camera has completed the mode switch. The success of setValue only indicates that the camera has successfully received the command.
- Add a MediaFileListStateListener to monitor the status of media files. After taking a photo, the camera will generate the corresponding file, and the file list status can indicate whether the camera has completed writing the file. UP_TO_DATE indicates that the file list is up to date, and you can directly retrieve the latest file type using getMediaFileListData. Typically, the latest photo is placed first. It is worth to noting that you need to call pullMediaFileListFromCamera at least once to initialize the listener and file list.
- Before downloading the file, use MediaManager.getInstance().enable to put the camera into playback mode. You can use CameraKey.KeyIsPlayingBack to determine if it is in playback mode. °°°
Just to make sure, you said "Listen for changes in KeyCameraMode", but, is it necessary to listen if we can simply use RxUtil.setValue
to cameraMode PHOTO_NORMAL and chain the promise using andThen
instead ?
Normally, if the Promise is returned after the CameraMode has resolved, it should already be in the right state to take the Photo. So, no need to create a dedicated listener for this, right?
Agent comment from yating.liao in Zendesk ticket #110093:
1. Do I need to disable MediaManager if I want to take photos again? -- Yes. After exiting the playback mode, you can take photos.
°°°
Thanks, it helped a lot! Yet, one another question: Is it necessary to enter playaback mode to download photos? As I can see, I can enable MediaManager, start pulling photos and as it updates without MediaManager, I can just look for photo by its index. So, in short - does pullOriginalMediaFileFromCamera need playback mode enabled?
And please do not forget my question about chained promises using RxUtil setValue.
It is quite critical to know if we can rely on promises in DJI's SDK or if we have to set listeners and trigger some logic on events received instead, as you proposed...
Agent comment from yating.liao in Zendesk ticket #110093:
Do you mean if the callback result of the SDK interface can be used as the result for the next step in RxUtil? I would recommend using the listeners provided by the SDK as the callback result of the SDK interface does not represent the execution result of the drone. This is also the approach that Mobile SDK has always taken.
°°°
Well, it answers to my question, but the fact that "this is also the approach that Mobile SDK has always taken." is not a justification if you place yourself at the users' perspective.
You said "the callback result of the SDK interface does not represent the execution result of the drone". So it confirms what I noticed and did not expect from an SDK. But I prefered to be sure and to have your confirmation.
Do you believe the DJI's SDK will handle callback/promises as expected in a near future?
Promise or Callback are designed to be chained, and if it is not possible with DJI's SDK then it is a design flaw inside the SDK.
@antonymarion We can discuss under the issue you created, which will continue to track the problem with @IakovlevAA .
Tried method with listening mediaFileListState. I come to a problem with pullMedia. When drone starts and I take photo mediaFileListState becomes UPDATING permanently, and this changes only if I call pullMedia once again or takePhoto. Also my question was left without answer.... does pullOriginalMediaFileFromCamera or pullPreviewFromCamera need playback mode enabled?
Agent comment from yating.liao in Zendesk ticket #110093:
Is the latest version of MSDK being used by you? This issue was present in earlier versions, but it has been fixed in the new version.
°°°
yes, just tried in Sample app. In Widget tool -> Media Playback -> take photo. State is UPDATING, until I push fetch media. Also found, if I take photo and immediately push enable(Media Manager), state is stucked in Updating, until new photo is taken
My goal is to download photo as soon as it was taken. Right now I'm struggling with strange behaviour
Start pulling the exact photo I've taken with CameraKey.KeyNewlyGeneratedMediaFile.create().get()?.index
val index = CameraKey.KeyNewlyGeneratedMediaFile.create().get()?.index ?: -1
val count = if(index == -1) -1 else 1
val mediaSource = MediaFileListDataSource.Builder().setLocation(CameraStorageLocation.SDCARD).build()
MediaDataCenter.getInstance().mediaManager.setMediaFileDataSource(mediaSource)
MediaDataCenter.getInstance().mediaManager.pullMediaFileListFromCamera(
PullMediaFileListParam.Builder().mediaFileIndex(index).count(count).filter(MediaFileFilter.PHOTO).build(), object : CommonCallbacks.CompletionCallback {
override fun onSuccess() {
cameraStateActivity.postValue("Pull media success")
}
override fun onFailure(error: IDJIError) {
cameraStateActivity.postValue("fetch failed ${error.description()}")
errorHappened = true
}
})
This called once fileListState is IDLE
Once mediaFileList is updated I'm enabling MediaManager
if (mediaFileListState == MediaFileListState.UP_TO_DATE)
{
MediaDataCenter.getInstance().mediaManager.stopPullMediaFileListFromCamera()
val data = MediaDataCenter.getInstance().mediaManager.mediaFileListData;
mediaFileListData.postValue(data)
MediaManager.getInstance().enable(object :
CommonCallbacks.CompletionCallback {
override fun onSuccess()
{
downloadFile()
}
override fun onFailure(error: IDJIError) {
println("$error")
}
})
}
P.S On this stage after taking photo may never ends, and so mediaFileListState is UPDATING permanently
Agent comment from yating.liao in Zendesk ticket #110093:
The MediaManager.getInstance().enable method puts the camera into playback mode. In playback mode, it is not possible to take photos. The take photo interface will instead callback a photo capture failure, rather than continuously attempting to capture a photo.
It seems illogical to use the take photo interface without confirming the result of enabling MediaManager first.
°°°
I do understand, that it is impossible to take photos while MediaManager is enabled. What I'm trying to show, that too fast switch between take photo and enable MediaManager causes troubles (even if I check that camera stopped shooting photo). Right now I achieved my goal to download photo with this steps Take photo -> sleep(5000) -> enable MediaManager -> wait for camera to enter playback mode -> pullMedia with index I got in CameraKey.KeyNewlyGeneratedMediaFile -> disable MediaManager(because in playback mode filestate stuck in infinite UPDATE) -> Wait for filestate to enter UP_TO_DATE -> enable MediaManager -> stopPullMediaFileListFromCamera -> download photo with mediaFile.pullPreviewFromCamera -> disable MediaManager Only this method works for me. Listening for states or anything else mentioned in this always caused different problems, that I mentioned above. Closing this as I am pleased with results I've got
Agent comment from yating.liao in Zendesk ticket #110093:
I understand what you mean. Enabling MediaManager quickly after taking a photo may indeed cause issues. That's why we mentioned using the file list status to determine if the SD card has finished writing the photo. After taking a photo, the file list will change to updating, and when the status becomes up_to_date, you can download the photo.
°°°
I do understand, that it is impossible to take photos while MediaManager is enabled. What I'm trying to show, that too fast switch between take photo and enable MediaManager causes troubles (even if I check that camera stopped shooting photo). Right now I achieved my goal to download photo with this steps Take photo -> sleep(5000) -> enable MediaManager -> wait for camera to enter playback mode -> pullMedia with index I got in CameraKey.KeyNewlyGeneratedMediaFile -> disable MediaManager(because in playback mode filestate stuck in infinite UPDATE) -> Wait for filestate to enter UP_TO_DATE -> enable MediaManager -> stopPullMediaFileListFromCamera -> download photo with mediaFile.pullPreviewFromCamera -> disable MediaManager Only this method works for me. Listening for states or anything else mentioned in this always caused different problems, that I mentioned above. Closing this as I am pleased with results I've got
I confirm the behaviour. putting old school wait on the code fix the issue on DJI's side
Hi Dji, I want to download the last photo I've taken. As for now I have code that pulls media from drone, and downloads photo, but it have some issues.
Device: Mini 3 pro