package com.aros.apron.manager; import android.os.Build; import android.os.Environment; import android.os.Handler; import android.text.TextUtils; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import com.amazonaws.ClientConfiguration; import com.amazonaws.auth.AWSCredentials; import com.amazonaws.regions.Region; import com.amazonaws.regions.Regions; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest; import com.amazonaws.services.s3.model.ProgressEvent; import com.amazonaws.services.s3.model.ProgressListener; import com.amazonaws.services.s3.model.PutObjectRequest; import com.aros.apron.base.BaseManager; import com.aros.apron.entity.ApronExecutionStatus; import com.aros.apron.tools.LogUtil; import com.aros.apron.tools.PreferenceUtils; import com.autonavi.base.amap.mapcore.FileUtil; import com.google.gson.Gson; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; import dji.sdk.keyvalue.key.FlightControllerKey; import dji.sdk.keyvalue.key.KeyTools; import dji.sdk.keyvalue.value.camera.MediaFileType; import dji.sdk.keyvalue.value.common.ComponentIndexType; import dji.v5.common.callback.CommonCallbacks; import dji.v5.common.error.IDJIError; import dji.v5.manager.KeyManager; import dji.v5.manager.datacenter.MediaDataCenter; import dji.v5.manager.datacenter.media.MediaFile; import dji.v5.manager.datacenter.media.MediaFileDownloadListener; import dji.v5.manager.datacenter.media.MediaFileListDataSource; import dji.v5.manager.datacenter.media.MediaFileListState; import dji.v5.manager.datacenter.media.MediaFileListStateListener; import dji.v5.manager.datacenter.media.PullMediaFileListParam; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.core.ObservableEmitter; import io.reactivex.rxjava3.core.ObservableOnSubscribe; import io.reactivex.rxjava3.core.Observer; import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.schedulers.Schedulers; public class MediaManager extends BaseManager { private final String TAG = "MediaManager"; private final String mediaFileDir = "/apronPic"; private MediaFileListState mState = null; private List mediaFiles = new ArrayList<>(); private MediaManager() { } private static class MediaManagerHolder { private static final MediaManager INSTANCE = new MediaManager(); } public static MediaManager getInstance() { return MediaManagerHolder.INSTANCE; } public void init() { Boolean isConnect = KeyManager.getInstance().getValue(KeyTools.createKey(FlightControllerKey.KeyConnection)); if (isConnect != null && isConnect) { MediaFileListDataSource source = new MediaFileListDataSource.Builder().setIndexType(ComponentIndexType.PORT_1).build(); MediaDataCenter.getInstance().getMediaManager().setMediaFileDataSource(source); MediaDataCenter.getInstance().getMediaManager().addMediaFileListStateListener(new MediaFileListStateListener() { @Override public void onUpdate(MediaFileListState mediaFileListState) { mState = mediaFileListState; LogUtil.log(TAG, "当前媒体文件状态:" + mediaFileListState.name()); } }); } } private int enterPlayBackFailTimes; private boolean isEnablePlayback; public void enablePlayback() { MediaDataCenter.getInstance().getMediaManager().enable(new CommonCallbacks.CompletionCallback() { @Override public void onSuccess() { LogUtil.log(TAG, "进入媒体模式成功"); new Handler().postDelayed(new Runnable() { @Override public void run() { pullMediaFileListFromCamera(); isEnablePlayback=true; } },1000); } @Override public void onFailure(@NonNull IDJIError idjiError) { LogUtil.log(TAG, "第"+enterPlayBackFailTimes+"次进入媒体模式失败:"+new Gson().toJson(idjiError)); if (!isEnablePlayback){ new Handler().postDelayed(new Runnable() { @Override public void run() { if (enterPlayBackFailTimes < 10) { enterPlayBackFailTimes++; enablePlayback(); }else{ ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true); sendEvent2Server( "媒体模式进入失败:关机",2); } } }, 1500); } } }); } private int pullMediaFileListFromCameraFailTimes; private boolean isPullMediaFileListFromCameraSuccess; private void pullMediaFileListFromCamera(){ MediaDataCenter.getInstance().getMediaManager().pullMediaFileListFromCamera(new PullMediaFileListParam.Builder().count(-1).build(), new CommonCallbacks.CompletionCallback() { @Override public void onSuccess() { isPullMediaFileListFromCameraSuccess=true; new Handler().postDelayed(new Runnable() { @Override public void run() { if (mState == MediaFileListState.UP_TO_DATE) { mediaFiles = MediaDataCenter.getInstance().getMediaManager().getMediaFileListData().getData(); if (mediaFiles != null&&mediaFiles.size()>0) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { pullOriginalMediaFileFromCamera(); } } else { ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true); sendEvent2Server("拉取媒体文件为空,可关机",2); disablePlayback(); } } else { ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true); sendEvent2Server("拉取媒体文件失败,可关机:"+mState,2); disablePlayback(); } } },1000); } @Override public void onFailure(@NonNull IDJIError idjiError) { LogUtil.log(TAG, "第"+pullMediaFileListFromCameraFailTimes+"拉取媒体文件失败:"+new Gson().toJson(idjiError)); if (!isPullMediaFileListFromCameraSuccess){ new Handler().postDelayed(new Runnable() { @Override public void run() { if (pullMediaFileListFromCameraFailTimes < 10) { pullMediaFileListFromCameraFailTimes++; pullMediaFileListFromCamera(); }else{ LogUtil.log(TAG, "拉取媒体文件失败:" + new Gson().toJson(idjiError)); ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true); sendEvent2Server("拉取媒体文件失败",2); disablePlayback(); LogUtil.log(TAG, "发送关闭无人机"); } } }, 1500); } } } ); } @RequiresApi(Build.VERSION_CODES.O) public void pullOriginalMediaFileFromCamera() { final MediaFile mediaFile = mediaFiles.get(downLoadMediaFileIndex); if ((!PreferenceUtils.getInstance().getNeedUpLoadVideo() && mediaFile.getFileType() == MediaFileType.MP4) || !mediaFile.getFileName().contains(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")))) { downLoadMediaFileIndex++; if (downLoadMediaFileIndex == mediaFiles.size()) { // This refers to when all files have been downloaded or failed. Clear SD card, cache, exit media mode, and shut down the drone downLoadMediaFileIndex = 0; removeAllFiles(); } else { LogUtil.log(TAG, "Skipping file download: " + mediaFile.getFileName()); pullOriginalMediaFileFromCamera(); } return; } LogUtil.log(TAG, "File size: " + mediaFile.getFileSize()); File dirs = new File(getSDCardPath() + mediaFileDir); if (!dirs.exists()) { dirs.mkdir(); } String filePath = getSDCardPath() + mediaFileDir + "/" + mediaFile.getFileName(); File file = new File(filePath); long offset = 0L; if (file.exists()) { offset = file.length(); } try { FileOutputStream outputStream = new FileOutputStream(file, true); long beginTime = System.currentTimeMillis(); BufferedOutputStream bos = new BufferedOutputStream(outputStream); mediaFile.pullOriginalMediaFileFromCamera(0L, new MediaFileDownloadListener() { @Override public void onStart() { // No action needed for start } @Override public void onProgress(long total, long current) { int tmpProgress = (int) ((1.0 * current / total) * 100); Log.e(TAG, "File " + downLoadMediaFileIndex + ": " + mediaFile.getFileName() + " Download Progress: " + tmpProgress + "%"); } @Override public void onRealtimeDataUpdate(byte[] data, long position) { try { bos.write(data); bos.flush(); } catch (IOException e) { Log.e(TAG, "Write error: " + e.getMessage()); } } @Override public void onFinish() { LogUtil.log(TAG, "File:" + downLoadMediaFileIndex+"fileName:"+mediaFile.getFileName() + " downloaded successfully."); minIOUpLoad(file, mediaFile); try { outputStream.close(); bos.close(); } catch (IOException error) { LogUtil.log(TAG, "File " + downLoadMediaFileIndex + " error: " + error.getMessage()); ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true); } } @Override public void onFailure(IDJIError error) { LogUtil.log(TAG, "File " + downLoadMediaFileIndex + ": " + mediaFile.getFileName() + " download failed: " + new Gson().toJson(error)); ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true); sendEvent2Server( "第" + downLoadMediaFileIndex + "个文件下载失败",2); downLoadMediaFileIndex = 0; } }); } catch (IOException e) { Log.e(TAG, "Error opening file: " + e.getMessage()); } } private AmazonS3 s3 = new AmazonS3Client(new AWSCredentials() { @Override public String getAWSAccessKeyId() { return PreferenceUtils.getInstance().getAccessKey(); // minio的key } @Override public String getAWSSecretKey() { return PreferenceUtils.getInstance().getSecretKey(); // minio的密钥 } }, Region.getRegion(Regions.US_EAST_1), new ClientConfiguration()); @RequiresApi(Build.VERSION_CODES.O) public void minIOUpLoad(final File file, final MediaFile mediaFile) { LogUtil.log(TAG, "文件路径=" + file.getAbsolutePath() + ", 文件大小=" + file.length()); Observable.create(new ObservableOnSubscribe() { @Override public void subscribe(ObservableEmitter emitter) throws Exception { // 服务器地址 s3.setEndpoint(PreferenceUtils.getInstance().getUploadUrl()); // http://ip:端口号 boolean bucketExists = s3.doesBucketExist(PreferenceUtils.getInstance().getBucketName()); if (!bucketExists) { s3.createBucket(PreferenceUtils.getInstance().getBucketName()); } // 上传文件到网关MINIO存储服务 s3.putObject( new PutObjectRequest( PreferenceUtils.getInstance().getBucketName(), "/" + PreferenceUtils.getInstance().getObjectKey() + "/" + mediaFile.getFileName(), file // new PutObjectRequest( // PreferenceUtils.getInstance().getBucketName(), // "/" + PreferenceUtils.getInstance().getObjectKey() + "/" + // PreferenceUtils.getInstance().getFlightId() + "/" + mediaFile.getFileName(), // file ).withProgressListener(new ProgressListener() { @Override public void progressChanged(ProgressEvent progressEvent) { switch (progressEvent.getEventCode()) { case ProgressEvent.PREPARING_EVENT_CODE: LogUtil.log(TAG, "Preparing to upload file " + downLoadMediaFileIndex); break; case ProgressEvent.STARTED_EVENT_CODE: long bytesTransferred = progressEvent.getBytesTransferred(); int percentage = (int) ((bytesTransferred * 100) / file.length()); LogUtil.log(TAG, "Upload started for file " + downLoadMediaFileIndex + ": " + percentage + "% (" + bytesTransferred + " out of " + file.length() + " bytes)"); break; case ProgressEvent.COMPLETED_EVENT_CODE: LogUtil.log(TAG, "Upload completed for file " + downLoadMediaFileIndex); break; case ProgressEvent.FAILED_EVENT_CODE: LogUtil.log(TAG, "Upload failed for file " + downLoadMediaFileIndex); break; case ProgressEvent.RESET_EVENT_CODE: LogUtil.log(TAG, "Upload reset for file " + downLoadMediaFileIndex); break; } } }) ); // 获取文件上传后访问地址url GeneratePresignedUrlRequest urlRequest = new GeneratePresignedUrlRequest( PreferenceUtils.getInstance().getBucketName(), "/" + PreferenceUtils.getInstance().getObjectKey() + "/" + mediaFile.getFileName() ); String url = s3.generatePresignedUrl(urlRequest).toString(); // 文件上传后访问地址url emitter.onNext(url); emitter.onComplete(); } }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer() { @Override public void onSubscribe(Disposable d) { // Handle on subscribe (optional) } @Override public void onNext(String url) { //上传完成发送事件 sendMediaUpload2Server(mediaFile.getFileName(),mediaFiles.size(),downLoadMediaFileIndex); } @RequiresApi(Build.VERSION_CODES.O) @Override public void onError(Throwable e) { // 每上传失败一张就清除缓存 FileUtil.deleteFile(file); LogUtil.log(TAG, "Error uploading file " + downLoadMediaFileIndex + ": " + e.getMessage()); downLoadMediaFileIndex++; if (downLoadMediaFileIndex == mediaFiles.size()) { // 所有文件已经下载完成或失败,清空SD卡,缓存,退出媒体模式,发送无人机关机 downLoadMediaFileIndex = 0; removeAllFiles(); } else { pullOriginalMediaFileFromCamera(); } } @RequiresApi(Build.VERSION_CODES.O) @Override public void onComplete() { // 每上传一张就清除缓存 FileUtil.deleteFile(file); LogUtil.log(TAG, "File " + downLoadMediaFileIndex + " uploaded successfully."); sendEvent2Server( "第" + downLoadMediaFileIndex + "个文件已上传",1); downLoadMediaFileIndex++; if (downLoadMediaFileIndex == mediaFiles.size()) { // 所有文件已上传完成,清空SD卡,缓存,退出媒体模式,发送无人机关机 sendEvent2Server( "媒体文件已上传完毕",1); removeAllFiles(); downLoadMediaFileIndex = 0; } else { pullOriginalMediaFileFromCamera(); } } }); } public void removeAllFiles() { MediaDataCenter.getInstance().getMediaManager().deleteMediaFiles(mediaFiles, new CommonCallbacks.CompletionCallback() { @Override public void onSuccess() { ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true); sendEvent2Server("媒体文件已清除",1); disablePlayback(); } @Override public void onFailure(@NonNull IDJIError idjiError) { ApronExecutionStatus.getInstance().setAircraftWaitShutDown(true); sendEvent2Server( "媒体文件清除失败",1); } }); } //退出媒体模式 public void disablePlayback() { MediaDataCenter.getInstance().getMediaManager().disable(new CommonCallbacks.CompletionCallback() { @Override public void onSuccess() { LogUtil.log(TAG, "退出媒体模式成功"); } @Override public void onFailure(@NonNull IDJIError idjiError) { LogUtil.log(TAG, "退出媒体模式失败:"+new Gson().toJson(idjiError)); } }); } private int downLoadMediaFileIndex = 0; private String getSDCardPath(){ if (checkSDCard()) { return Environment.getExternalStorageDirectory() .getPath(); } else { return Environment.getExternalStorageDirectory() .getParentFile().getPath(); } } private boolean checkSDCard() { return TextUtils.equals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState()); } }