From 4b4785171cce739cb70bc0693a50edf97587f910 Mon Sep 17 00:00:00 2001 From: Alex Ning Date: Sun, 9 Aug 2020 10:41:24 +0800 Subject: Use DownloadVideoService to download videos. --- app/src/main/AndroidManifest.xml | 2 +- .../infinityforreddit/API/DownloadRedditVideo.java | 13 - .../infinityforreddit/API/DownloadVideo.java | 13 + .../Activity/ViewVideoActivity.java | 11 +- .../infinityforreddit/AppComponent.java | 4 +- .../infinityforreddit/MediaDownloaderImpl.java | 21 +- .../infinityforreddit/NotificationUtils.java | 3 + .../Service/DownloadRedditVideoService.java | 554 ------------------- .../Service/DownloadVideoService.java | 608 +++++++++++++++++++++ app/src/main/res/values/strings.xml | 8 +- 10 files changed, 652 insertions(+), 585 deletions(-) delete mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/API/DownloadRedditVideo.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/API/DownloadVideo.java delete mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/Service/DownloadRedditVideoService.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/Service/DownloadVideoService.java (limited to 'app/src') diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 809a15c4..01d0e36b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -35,7 +35,7 @@ android:windowSoftInputMode="adjustResize" /> diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/API/DownloadRedditVideo.java b/app/src/main/java/ml/docilealligator/infinityforreddit/API/DownloadRedditVideo.java deleted file mode 100644 index 7e51123d..00000000 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/API/DownloadRedditVideo.java +++ /dev/null @@ -1,13 +0,0 @@ -package ml.docilealligator.infinityforreddit.API; - -import okhttp3.ResponseBody; -import retrofit2.Call; -import retrofit2.http.GET; -import retrofit2.http.Streaming; -import retrofit2.http.Url; - -public interface DownloadRedditVideo { - @Streaming - @GET() - Call downloadFile(@Url String fileUrl); -} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/API/DownloadVideo.java b/app/src/main/java/ml/docilealligator/infinityforreddit/API/DownloadVideo.java new file mode 100644 index 00000000..e3d9d238 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/API/DownloadVideo.java @@ -0,0 +1,13 @@ +package ml.docilealligator.infinityforreddit.API; + +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.Streaming; +import retrofit2.http.Url; + +public interface DownloadVideo { + @Streaming + @GET() + Call downloadFile(@Url String fileUrl); +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/Activity/ViewVideoActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/Activity/ViewVideoActivity.java index 82621a6c..79ef0477 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/Activity/ViewVideoActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/Activity/ViewVideoActivity.java @@ -62,7 +62,7 @@ import ml.docilealligator.infinityforreddit.Infinity; import ml.docilealligator.infinityforreddit.MediaDownloader; import ml.docilealligator.infinityforreddit.MediaDownloaderImpl; import ml.docilealligator.infinityforreddit.R; -import ml.docilealligator.infinityforreddit.Service.DownloadRedditVideoService; +import ml.docilealligator.infinityforreddit.Service.DownloadVideoService; import ml.docilealligator.infinityforreddit.Utils.SharedPreferencesUtils; import retrofit2.Retrofit; @@ -436,10 +436,11 @@ public class ViewVideoActivity extends AppCompatActivity { if (videoType != VIDEO_TYPE_NORMAL) { mediaDownloader.download(videoDownloadUrl, videoFileName, this); } else { - Intent intent = new Intent(this, DownloadRedditVideoService.class); - intent.putExtra(DownloadRedditVideoService.EXTRA_VIDEO_URL, videoDownloadUrl); - intent.putExtra(DownloadRedditVideoService.EXTRA_POST_ID, id); - intent.putExtra(DownloadRedditVideoService.EXTRA_SUBREDDIT, subredditName); + Intent intent = new Intent(this, DownloadVideoService.class); + intent.putExtra(DownloadVideoService.EXTRA_VIDEO_URL, videoDownloadUrl); + intent.putExtra(DownloadVideoService.EXTRA_POST_ID, id); + intent.putExtra(DownloadVideoService.EXTRA_SUBREDDIT, subredditName); + intent.putExtra(DownloadVideoService.EXTRA_IS_REDDIT_VIDEO, true); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForegroundService(intent); diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java b/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java index 3542c686..2952e741 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java @@ -54,7 +54,7 @@ import ml.docilealligator.infinityforreddit.Fragment.SubscribedSubredditsListing import ml.docilealligator.infinityforreddit.Fragment.UserListingFragment; import ml.docilealligator.infinityforreddit.Fragment.ViewImgurVideoFragment; import ml.docilealligator.infinityforreddit.Fragment.ViewRedditGalleryVideoFragment; -import ml.docilealligator.infinityforreddit.Service.DownloadRedditVideoService; +import ml.docilealligator.infinityforreddit.Service.DownloadVideoService; import ml.docilealligator.infinityforreddit.Service.SubmitPostService; import ml.docilealligator.infinityforreddit.Settings.AdvancedPreferenceFragment; import ml.docilealligator.infinityforreddit.Settings.CustomizeMainPageTabsFragment; @@ -173,7 +173,7 @@ public interface AppComponent { void inject(ViewImgurVideoFragment viewImgurVideoFragment); - void inject(DownloadRedditVideoService downloadRedditVideoService); + void inject(DownloadVideoService downloadVideoService); void inject(MultiRedditListingFragment multiRedditListingFragment); diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/MediaDownloaderImpl.java b/app/src/main/java/ml/docilealligator/infinityforreddit/MediaDownloaderImpl.java index 638bdf93..79002741 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/MediaDownloaderImpl.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/MediaDownloaderImpl.java @@ -1,19 +1,17 @@ package ml.docilealligator.infinityforreddit; -import android.app.DownloadManager; import android.content.Context; -import android.net.Uri; +import android.content.Intent; import android.os.Build; -import android.os.Environment; import android.widget.Toast; -import java.io.File; +import ml.docilealligator.infinityforreddit.Service.DownloadVideoService; public class MediaDownloaderImpl implements MediaDownloader { @Override public void download(String url, String fileName, Context ctx) { - DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url)); + /*DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url)); request.setTitle(fileName); request.allowScanningByMediaScanner(); @@ -53,6 +51,19 @@ public class MediaDownloaderImpl implements MediaDownloader { } manager.enqueue(request); + Toast.makeText(ctx, R.string.download_started, Toast.LENGTH_SHORT).show();*/ + + + Intent intent = new Intent(ctx, DownloadVideoService.class); + intent.putExtra(DownloadVideoService.EXTRA_VIDEO_URL, url); + intent.putExtra(DownloadVideoService.EXTRA_FILE_NAME, fileName); + intent.putExtra(DownloadVideoService.EXTRA_IS_REDDIT_VIDEO, false); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + ctx.startForegroundService(intent); + } else { + ctx.startService(intent); + } Toast.makeText(ctx, R.string.download_started, Toast.LENGTH_SHORT).show(); } } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/NotificationUtils.java b/app/src/main/java/ml/docilealligator/infinityforreddit/NotificationUtils.java index 155e4b02..8fe4b33e 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/NotificationUtils.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/NotificationUtils.java @@ -13,8 +13,11 @@ public class NotificationUtils { static final String CHANNEL_NEW_MESSAGES = "New Messages"; public static final String CHANNEL_ID_DOWNLOAD_REDDIT_VIDEO = "download_reddit_video"; public static final String CHANNEL_DOWNLOAD_REDDIT_VIDEO = "Download Reddit Video"; + public static final String CHANNEL_ID_DOWNLOAD_VIDEO = "download_video"; + public static final String CHANNEL_DOWNLOAD_VIDEO = "Download Video"; public static final int SUBMIT_POST_SERVICE_NOTIFICATION_ID = 10000; public static final int DOWNLOAD_REDDIT_VIDEO_NOTIFICATION_ID = 20000; + public static final int DOWNLOAD_VIDEO_NOTIFICATION_ID = 30000; private static final int SUMMARY_BASE_ID_UNREAD_MESSAGE = 0; private static final int NOTIFICATION_BASE_ID_UNREAD_MESSAGE = 1; diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/Service/DownloadRedditVideoService.java b/app/src/main/java/ml/docilealligator/infinityforreddit/Service/DownloadRedditVideoService.java deleted file mode 100644 index 562ff432..00000000 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/Service/DownloadRedditVideoService.java +++ /dev/null @@ -1,554 +0,0 @@ -package ml.docilealligator.infinityforreddit.Service; - -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.Service; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.media.MediaCodec; -import android.media.MediaExtractor; -import android.media.MediaFormat; -import android.media.MediaMuxer; -import android.media.MediaScannerConnection; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Build; -import android.os.Environment; -import android.os.IBinder; -import android.provider.MediaStore; - -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; -import androidx.core.app.NotificationCompat; -import androidx.core.app.NotificationManagerCompat; - -import org.greenrobot.eventbus.EventBus; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; - -import javax.inject.Inject; -import javax.inject.Named; - -import ml.docilealligator.infinityforreddit.API.DownloadRedditVideo; -import ml.docilealligator.infinityforreddit.CustomTheme.CustomThemeWrapper; -import ml.docilealligator.infinityforreddit.Event.DownloadRedditVideoEvent; -import ml.docilealligator.infinityforreddit.Infinity; -import ml.docilealligator.infinityforreddit.NotificationUtils; -import ml.docilealligator.infinityforreddit.R; -import okhttp3.ResponseBody; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; -import retrofit2.Retrofit; - -import static android.os.Environment.getExternalStoragePublicDirectory; - -public class DownloadRedditVideoService extends Service { - - public static final String EXTRA_VIDEO_URL = "EVU"; - public static final String EXTRA_SUBREDDIT = "ES"; - public static final String EXTRA_POST_ID = "EPI"; - - private static final int NO_ERROR = -1; - private static final int ERROR_CANNOT_GET_CACHE_DIRECTORY = 0; - private static final int ERROR_VIDEO_FILE_CANNOT_DOWNLOAD = 1; - private static final int ERROR_VIDEO_FILE_CANNOT_SAVE = 2; - private static final int ERROR_AUDIO_FILE_CANNOT_SAVE = 3; - private static final int ERROR_MUX_FAILED = 4; - private static final int ERROR_MUXED_VIDEO_FILE_CANNOT_SAVE = 5; - - @Inject - @Named("download_reddit_video") - Retrofit retrofit; - @Inject - CustomThemeWrapper mCustomThemeWrapper; - String resultFile; - - public DownloadRedditVideoService() { - } - - @Override - public IBinder onBind(Intent intent) { - return null; - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - ((Infinity) getApplication()).getAppComponent().inject(this); - - String videoUrl = intent.getStringExtra(EXTRA_VIDEO_URL); - String audioUrl = videoUrl.substring(0, videoUrl.lastIndexOf('/')) + "/audio"; - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - NotificationChannel serviceChannel = new NotificationChannel( - NotificationUtils.CHANNEL_ID_DOWNLOAD_REDDIT_VIDEO, - NotificationUtils.CHANNEL_DOWNLOAD_REDDIT_VIDEO, - NotificationManager.IMPORTANCE_LOW - ); - - NotificationManagerCompat manager = NotificationManagerCompat.from(this); - manager.createNotificationChannel(serviceChannel); - } - - String fileName = intent.getStringExtra(EXTRA_SUBREDDIT) + "-" + intent.getStringExtra(EXTRA_POST_ID); - - startForeground( - NotificationUtils.DOWNLOAD_REDDIT_VIDEO_NOTIFICATION_ID, - createNotification(R.string.downloading_reddit_video, fileName + ".mp4", null) - ); - - DownloadRedditVideo downloadRedditVideo = retrofit.create(DownloadRedditVideo.class); - - File directory = getExternalCacheDir(); - String destinationFileName = fileName + ".mp4"; - if (directory != null) { - String directoryPath = directory.getAbsolutePath() + "/"; - downloadRedditVideo.downloadFile(videoUrl).enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response videoResponse) { - if (videoResponse.isSuccessful() && videoResponse.body() != null) { - updateNotification(R.string.downloading_reddit_video_audio_track, destinationFileName, null); - downloadRedditVideo.downloadFile(audioUrl).enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response audioResponse) { - if (audioResponse.isSuccessful() && audioResponse.body() != null) { - String videoFilePath = directoryPath + fileName + "-cache.mp4"; - String audioFilePath = directoryPath + fileName + "-cache.mp3"; - String outputFilePath = directoryPath + fileName + ".mp4"; - new SaveTempMuxAndCopyAsyncTask(videoResponse.body(), - audioResponse.body(), videoFilePath, audioFilePath, outputFilePath, - destinationFileName, getContentResolver(), - new SaveTempMuxAndCopyAsyncTask.SaveTempMuxAndCopyAsyncTaskListener() { - @Override - public void finished(Uri destinationFileUri, int errorCode) { - new File(videoFilePath).delete(); - new File(audioFilePath).delete(); - new File(outputFilePath).delete(); - downloadFinished(destinationFileUri, destinationFileName, errorCode); - } - - @Override - public void updateProgressNotification(int stringResId) { - updateNotification(stringResId, destinationFileName, null); - } - }).execute(); - } else { - String videoFilePath = directoryPath + fileName + "-cache.mp4"; - String destinationFileName = fileName + ".mp4"; - new SaveTempMuxAndCopyAsyncTask(videoResponse.body(), - null, videoFilePath, null, null, - destinationFileName, getContentResolver(), - new SaveTempMuxAndCopyAsyncTask.SaveTempMuxAndCopyAsyncTaskListener() { - @Override - public void finished(Uri destinationFileUri, int errorCode) { - new File(videoFilePath).delete(); - downloadFinished(destinationFileUri, destinationFileName, errorCode); - } - - @Override - public void updateProgressNotification(int stringResId) { - updateNotification(stringResId, destinationFileName, null); - } - }).execute(); - } - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - String videoFilePath = directoryPath + fileName + "-cache.mp4"; - String destinationFileName = fileName + ".mp4"; - new SaveTempMuxAndCopyAsyncTask(videoResponse.body(), - null, videoFilePath, null, null, - destinationFileName, getContentResolver(), - new SaveTempMuxAndCopyAsyncTask.SaveTempMuxAndCopyAsyncTaskListener() { - @Override - public void finished(Uri destinationFileUri, int errorCode) { - new File(videoFilePath).delete(); - downloadFinished(destinationFileUri, destinationFileName, errorCode); - } - - @Override - public void updateProgressNotification(int stringResId) { - updateNotification(stringResId, destinationFileName, null); - } - }).execute(); - } - }); - } else { - downloadFinished(null, destinationFileName, ERROR_VIDEO_FILE_CANNOT_DOWNLOAD); - } - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - downloadFinished(null, destinationFileName, ERROR_VIDEO_FILE_CANNOT_DOWNLOAD); - } - }); - } else { - downloadFinished(null, destinationFileName, ERROR_CANNOT_GET_CACHE_DIRECTORY); - } - - return START_NOT_STICKY; - } - - private void downloadFinished(Uri destinationFileUri, String fileName, int errorCode) { - if (errorCode != NO_ERROR) { - switch (errorCode) { - case ERROR_CANNOT_GET_CACHE_DIRECTORY: - updateNotification(R.string.downloading_reddit_video_failed_cannot_get_cache_directory, fileName, null); - break; - case ERROR_VIDEO_FILE_CANNOT_DOWNLOAD: - updateNotification(R.string.downloading_reddit_video_failed_cannot_download_video, fileName, null); - break; - case ERROR_VIDEO_FILE_CANNOT_SAVE: - updateNotification(R.string.downloading_reddit_video_failed_cannot_save_video, fileName, null); - break; - case ERROR_AUDIO_FILE_CANNOT_SAVE: - updateNotification(R.string.downloading_reddit_video_failed_cannot_save_audio, fileName, null); - break; - case ERROR_MUX_FAILED: - updateNotification(R.string.downloading_reddit_video_failed_cannot_mux, fileName, null); - break; - case ERROR_MUXED_VIDEO_FILE_CANNOT_SAVE: - updateNotification(R.string.downloading_reddit_video_failed_cannot_save_mux_video, fileName, null); - break; - } - EventBus.getDefault().post(new DownloadRedditVideoEvent(false)); - } else { - MediaScannerConnection.scanFile( - this, new String[]{destinationFileUri.toString()}, null, - (path, uri) -> { - Intent intent = new Intent(); - intent.setAction(android.content.Intent.ACTION_VIEW); - intent.setDataAndType(destinationFileUri, "video/*"); - PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); - updateNotification(R.string.downloading_reddit_video_finished, fileName, pendingIntent); - EventBus.getDefault().post(new DownloadRedditVideoEvent(true)); - } - ); - } - stopForeground(false); - } - - private Notification createNotification(int stringResId, String fileName, PendingIntent pendingIntent) { - NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NotificationUtils.CHANNEL_ID_DOWNLOAD_REDDIT_VIDEO); - builder.setContentTitle(fileName).setContentText(getString(stringResId)); - if (pendingIntent != null) { - builder.setContentIntent(pendingIntent); - } - return builder.setSmallIcon(R.drawable.ic_notification) - .setColor(mCustomThemeWrapper.getColorPrimaryLightTheme()) - .build(); - } - - private void updateNotification(int stringResId, String fileName, PendingIntent pendingIntent) { - NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - if (notificationManager != null) { - notificationManager.notify(NotificationUtils.DOWNLOAD_REDDIT_VIDEO_NOTIFICATION_ID, - createNotification(stringResId, fileName, pendingIntent)); - } - } - - private static class SaveTempMuxAndCopyAsyncTask extends AsyncTask { - - private ResponseBody videoResponse; - private ResponseBody audioResponse; - private String videoFilePath; - private String audioFilePath; - private String outputFilePath; - private String destinationFileName; - private ContentResolver contentResolver; - private SaveTempMuxAndCopyAsyncTaskListener saveTempMuxAndCopyAsyncTaskListener; - private Uri destinationFileUri; - private int errorCode = NO_ERROR; - - public SaveTempMuxAndCopyAsyncTask(ResponseBody videoResponse, ResponseBody audioResponse, - String videoFilePath, String audioFilePath, String outputFilePath, - String destinationFileName, ContentResolver contentResolver, - SaveTempMuxAndCopyAsyncTaskListener saveTempMuxAndCopyAsyncTaskListener) { - this.videoResponse = videoResponse; - this.audioResponse = audioResponse; - this.videoFilePath = videoFilePath; - this.audioFilePath = audioFilePath; - this.outputFilePath = outputFilePath; - this.destinationFileName = destinationFileName; - this.contentResolver = contentResolver; - this.saveTempMuxAndCopyAsyncTaskListener = saveTempMuxAndCopyAsyncTaskListener; - } - - @Override - protected void onProgressUpdate(Integer... values) { - super.onProgressUpdate(values); - saveTempMuxAndCopyAsyncTaskListener.updateProgressNotification(values[0]); - } - - @Override - protected Void doInBackground(Void... voids) { - publishProgress(R.string.downloading_reddit_video_save_video); - String savedVideoFilePath = writeResponseBodyToDisk(videoResponse, videoFilePath); - if (savedVideoFilePath == null) { - errorCode = ERROR_VIDEO_FILE_CANNOT_SAVE; - return null; - } - if (audioResponse != null) { - publishProgress(R.string.downloading_reddit_video_save_audio); - String savedAudioFilePath = writeResponseBodyToDisk(audioResponse, audioFilePath); - if (savedAudioFilePath == null) { - errorCode = ERROR_AUDIO_FILE_CANNOT_SAVE; - return null; - } - - publishProgress(R.string.downloading_reddit_video_muxing); - if (!muxVideoAndAudio(videoFilePath, audioFilePath, outputFilePath)) { - errorCode = ERROR_MUX_FAILED; - return null; - } - - publishProgress(R.string.downloading_reddit_video_save_file_to_public_dir); - copyToDestination(outputFilePath); - } else { - publishProgress(R.string.downloading_reddit_video_save_file_to_public_dir); - copyToDestination(videoFilePath); - } - return null; - } - - @Override - protected void onPostExecute(Void aVoid) { - super.onPostExecute(aVoid); - saveTempMuxAndCopyAsyncTaskListener.finished(destinationFileUri, errorCode); - } - - private String writeResponseBodyToDisk(ResponseBody body, String filePath) { - try { - File file = new File(filePath); - - InputStream inputStream = null; - OutputStream outputStream = null; - - try { - byte[] fileReader = new byte[4096]; - - long fileSize = body.contentLength(); - long fileSizeDownloaded = 0; - - inputStream = body.byteStream(); - outputStream = new FileOutputStream(file); - - while (true) { - int read = inputStream.read(fileReader); - - if (read == -1) { - break; - } - - outputStream.write(fileReader, 0, read); - - fileSizeDownloaded += read; - } - - outputStream.flush(); - - return file.getPath(); - } catch (IOException e) { - return null; - } finally { - if (inputStream != null) { - inputStream.close(); - } - - if (outputStream != null) { - outputStream.close(); - } - } - } catch (IOException e) { - return null; - } - } - - private boolean muxVideoAndAudio(String videoFilePath, String audioFilePath, String outputFilePath) { - try { - File file = new File(outputFilePath); - file.createNewFile(); - MediaExtractor videoExtractor = new MediaExtractor(); - videoExtractor.setDataSource(videoFilePath); - MediaExtractor audioExtractor = new MediaExtractor(); - audioExtractor.setDataSource(audioFilePath); - MediaMuxer muxer = new MediaMuxer(outputFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); - - videoExtractor.selectTrack(0); - MediaFormat videoFormat = videoExtractor.getTrackFormat(0); - int videoTrack = muxer.addTrack(videoFormat); - - audioExtractor.selectTrack(0); - MediaFormat audioFormat = audioExtractor.getTrackFormat(0); - int audioTrack = muxer.addTrack(audioFormat); - boolean sawEOS = false; - int offset = 100; - int sampleSize = 2048 * 1024; - ByteBuffer videoBuf = ByteBuffer.allocate(sampleSize); - ByteBuffer audioBuf = ByteBuffer.allocate(sampleSize); - MediaCodec.BufferInfo videoBufferInfo = new MediaCodec.BufferInfo(); - MediaCodec.BufferInfo audioBufferInfo = new MediaCodec.BufferInfo(); - - videoExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC); - audioExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC); - - muxer.start(); - - while (!sawEOS) { - videoBufferInfo.offset = offset; - videoBufferInfo.size = videoExtractor.readSampleData(videoBuf, offset); - - if (videoBufferInfo.size < 0 || audioBufferInfo.size < 0) { - sawEOS = true; - videoBufferInfo.size = 0; - } else { - videoBufferInfo.presentationTimeUs = videoExtractor.getSampleTime(); - videoBufferInfo.flags = videoExtractor.getSampleFlags(); - muxer.writeSampleData(videoTrack, videoBuf, videoBufferInfo); - videoExtractor.advance(); - } - } - - boolean sawEOS2 = false; - while (!sawEOS2) { - audioBufferInfo.offset = offset; - audioBufferInfo.size = audioExtractor.readSampleData(audioBuf, offset); - - if (videoBufferInfo.size < 0 || audioBufferInfo.size < 0) { - sawEOS2 = true; - audioBufferInfo.size = 0; - } else { - audioBufferInfo.presentationTimeUs = audioExtractor.getSampleTime(); - audioBufferInfo.flags = audioExtractor.getSampleFlags(); - muxer.writeSampleData(audioTrack, audioBuf, audioBufferInfo); - audioExtractor.advance(); - - } - } - - try { - muxer.stop(); - muxer.release(); - } catch (IllegalStateException ignore) {} - } catch (IOException e) { - e.printStackTrace(); - return false; - } - - return true; - } - - private void copyToDestination(String srcPath) { - if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - if (!copy(new File(srcPath), destinationFileName)) { - errorCode = ERROR_MUXED_VIDEO_FILE_CANNOT_SAVE; - } - } else { - try { - copyFileQ(new File(srcPath), destinationFileName); - } catch (IOException e) { - e.printStackTrace(); - errorCode = ERROR_MUXED_VIDEO_FILE_CANNOT_SAVE; - } - } - } - - @RequiresApi(api = Build.VERSION_CODES.Q) - private void copyFileQ(File src, String outputFileName) throws IOException { - String relativeLocation = Environment.DIRECTORY_MOVIES + "/Infinity/"; - - ContentValues contentValues = new ContentValues(); - contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, outputFileName); - contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, relativeLocation); - contentValues.put(MediaStore.Video.Media.IS_PENDING, 1); - - OutputStream stream = null; - Uri uri = null; - - try { - final Uri contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; - uri = contentResolver.insert(contentUri, contentValues); - - if (uri == null) { - throw new IOException("Failed to create new MediaStore record."); - } - - stream = contentResolver.openOutputStream(uri); - - if (stream == null) { - throw new IOException("Failed to get output stream."); - } - - InputStream in = new FileInputStream(src); - - byte[] buf = new byte[1024]; - int len; - while ((len = in.read(buf)) > 0) { - stream.write(buf, 0, len); - } - - contentValues.clear(); - contentValues.put(MediaStore.Images.Media.IS_PENDING, 0); - contentResolver.update(uri, contentValues, null, null); - destinationFileUri = uri; - } catch (IOException e) { - if (uri != null) { - // Don't leave an orphan entry in the MediaStore - contentResolver.delete(uri, null, null); - } - - throw e; - } finally { - if (stream != null) { - stream.close(); - } - } - } - - private boolean copy(File src, String outputFileName) { - File directory = getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); - if (directory != null) { - String directoryPath = directory.getAbsolutePath() + "/Infinity/"; - String destinationFilePath = directoryPath + outputFileName; - destinationFileUri = Uri.parse(destinationFilePath); - - try (InputStream in = new FileInputStream(src)) { - try (OutputStream out = new FileOutputStream(destinationFilePath)) { - byte[] buf = new byte[1024]; - int len; - while ((len = in.read(buf)) > 0) { - out.write(buf, 0, len); - } - - src.delete(); - return true; - } - } catch (IOException e) { - e.printStackTrace(); - src.delete(); - return false; - } - } else { - src.delete(); - return false; - } - } - - interface SaveTempMuxAndCopyAsyncTaskListener { - void finished(Uri destinationFileUri, int errorCode); - void updateProgressNotification(int stringResId); - } - } -} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/Service/DownloadVideoService.java b/app/src/main/java/ml/docilealligator/infinityforreddit/Service/DownloadVideoService.java new file mode 100644 index 00000000..b3360bb4 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/Service/DownloadVideoService.java @@ -0,0 +1,608 @@ +package ml.docilealligator.infinityforreddit.Service; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.media.MediaCodec; +import android.media.MediaExtractor; +import android.media.MediaFormat; +import android.media.MediaMuxer; +import android.media.MediaScannerConnection; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Environment; +import android.os.IBinder; +import android.provider.MediaStore; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; + +import org.greenrobot.eventbus.EventBus; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; + +import javax.inject.Inject; +import javax.inject.Named; + +import ml.docilealligator.infinityforreddit.API.DownloadVideo; +import ml.docilealligator.infinityforreddit.CustomTheme.CustomThemeWrapper; +import ml.docilealligator.infinityforreddit.Event.DownloadRedditVideoEvent; +import ml.docilealligator.infinityforreddit.Infinity; +import ml.docilealligator.infinityforreddit.NotificationUtils; +import ml.docilealligator.infinityforreddit.R; +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import retrofit2.Retrofit; + +import static android.os.Environment.getExternalStoragePublicDirectory; + +public class DownloadVideoService extends Service { + + public static final String EXTRA_VIDEO_URL = "EVU"; + public static final String EXTRA_SUBREDDIT = "ES"; + public static final String EXTRA_POST_ID = "EPI"; + public static final String EXTRA_IS_REDDIT_VIDEO = "EIRV"; + public static final String EXTRA_FILE_NAME = "EFN"; + + private static final int NO_ERROR = -1; + private static final int ERROR_CANNOT_GET_CACHE_DIRECTORY = 0; + private static final int ERROR_VIDEO_FILE_CANNOT_DOWNLOAD = 1; + private static final int ERROR_VIDEO_FILE_CANNOT_SAVE = 2; + private static final int ERROR_AUDIO_FILE_CANNOT_SAVE = 3; + private static final int ERROR_MUX_FAILED = 4; + private static final int ERROR_MUXED_VIDEO_FILE_CANNOT_SAVE = 5; + + private boolean isRedditVideo; + + @Inject + @Named("download_reddit_video") + Retrofit retrofit; + @Inject + CustomThemeWrapper mCustomThemeWrapper; + String resultFile; + + public DownloadVideoService() { + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + ((Infinity) getApplication()).getAppComponent().inject(this); + + isRedditVideo = intent.getBooleanExtra(EXTRA_IS_REDDIT_VIDEO, false); + String videoUrl = intent.getStringExtra(EXTRA_VIDEO_URL); + String audioUrl = isRedditVideo ? videoUrl.substring(0, videoUrl.lastIndexOf('/')) + "/audio" : ""; + String fileName; + if (isRedditVideo) { + fileName = intent.getStringExtra(EXTRA_SUBREDDIT) + "-" + intent.getStringExtra(EXTRA_POST_ID); + } else { + fileName = intent.getStringExtra(EXTRA_FILE_NAME); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel serviceChannel; + if (isRedditVideo) { + serviceChannel = new NotificationChannel( + NotificationUtils.CHANNEL_ID_DOWNLOAD_REDDIT_VIDEO, + NotificationUtils.CHANNEL_DOWNLOAD_REDDIT_VIDEO, + NotificationManager.IMPORTANCE_LOW + ); + } else { + serviceChannel = new NotificationChannel( + NotificationUtils.CHANNEL_ID_DOWNLOAD_VIDEO, + NotificationUtils.CHANNEL_DOWNLOAD_VIDEO, + NotificationManager.IMPORTANCE_LOW + ); + } + + NotificationManagerCompat manager = NotificationManagerCompat.from(this); + manager.createNotificationChannel(serviceChannel); + } + + if (isRedditVideo) { + startForeground( + NotificationUtils.DOWNLOAD_REDDIT_VIDEO_NOTIFICATION_ID, + createNotification(R.string.downloading_reddit_video, fileName + ".mp4", null) + ); + } else { + startForeground( + NotificationUtils.DOWNLOAD_VIDEO_NOTIFICATION_ID, + createNotification(R.string.downloading_video, fileName, null) + ); + } + + DownloadVideo downloadVideo = retrofit.create(DownloadVideo.class); + + File directory = getExternalCacheDir(); + String destinationFileName = fileName + ".mp4"; + if (directory != null) { + String directoryPath = directory.getAbsolutePath() + "/"; + downloadVideo.downloadFile(videoUrl).enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response videoResponse) { + if (videoResponse.isSuccessful() && videoResponse.body() != null) { + if (isRedditVideo) { + updateNotification(R.string.downloading_reddit_video_audio_track, destinationFileName, null); + downloadVideo.downloadFile(audioUrl).enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response audioResponse) { + if (audioResponse.isSuccessful() && audioResponse.body() != null) { + String videoFilePath = directoryPath + fileName + "-cache.mp4"; + String audioFilePath = directoryPath + fileName + "-cache.mp3"; + String outputFilePath = directoryPath + fileName + ".mp4"; + new SaveTempMuxAndCopyAsyncTask(videoResponse.body(), + audioResponse.body(), videoFilePath, audioFilePath, outputFilePath, + destinationFileName, getContentResolver(), + new SaveTempMuxAndCopyAsyncTask.SaveTempMuxAndCopyAsyncTaskListener() { + @Override + public void finished(Uri destinationFileUri, int errorCode) { + new File(videoFilePath).delete(); + new File(audioFilePath).delete(); + new File(outputFilePath).delete(); + downloadFinished(destinationFileUri, destinationFileName, errorCode); + } + + @Override + public void updateProgressNotification(int stringResId) { + updateNotification(stringResId, destinationFileName, null); + } + }).execute(); + } else { + String videoFilePath = directoryPath + fileName + "-cache.mp4"; + String destinationFileName = fileName + ".mp4"; + new SaveTempMuxAndCopyAsyncTask(videoResponse.body(), + null, videoFilePath, null, null, + destinationFileName, getContentResolver(), + new SaveTempMuxAndCopyAsyncTask.SaveTempMuxAndCopyAsyncTaskListener() { + @Override + public void finished(Uri destinationFileUri, int errorCode) { + new File(videoFilePath).delete(); + downloadFinished(destinationFileUri, destinationFileName, errorCode); + } + + @Override + public void updateProgressNotification(int stringResId) { + updateNotification(stringResId, destinationFileName, null); + } + }).execute(); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + String videoFilePath = directoryPath + fileName + "-cache.mp4"; + String destinationFileName = fileName + ".mp4"; + new SaveTempMuxAndCopyAsyncTask(videoResponse.body(), + null, videoFilePath, null, null, + destinationFileName, getContentResolver(), + new SaveTempMuxAndCopyAsyncTask.SaveTempMuxAndCopyAsyncTaskListener() { + @Override + public void finished(Uri destinationFileUri, int errorCode) { + new File(videoFilePath).delete(); + downloadFinished(destinationFileUri, destinationFileName, errorCode); + } + + @Override + public void updateProgressNotification(int stringResId) { + updateNotification(stringResId, destinationFileName, null); + } + }).execute(); + } + }); + } else { + String videoFilePath = directoryPath + fileName; + new SaveTempMuxAndCopyAsyncTask(videoResponse.body(), + null, videoFilePath, null, null, + fileName, getContentResolver(), + new SaveTempMuxAndCopyAsyncTask.SaveTempMuxAndCopyAsyncTaskListener() { + @Override + public void finished(Uri destinationFileUri, int errorCode) { + new File(videoFilePath).delete(); + downloadFinished(destinationFileUri, fileName, errorCode); + } + + @Override + public void updateProgressNotification(int stringResId) { + updateNotification(stringResId, fileName, null); + } + }).execute(); + } + } else { + downloadFinished(null, destinationFileName, ERROR_VIDEO_FILE_CANNOT_DOWNLOAD); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + downloadFinished(null, destinationFileName, ERROR_VIDEO_FILE_CANNOT_DOWNLOAD); + } + }); + } else { + downloadFinished(null, destinationFileName, ERROR_CANNOT_GET_CACHE_DIRECTORY); + } + + return START_NOT_STICKY; + } + + private void downloadFinished(Uri destinationFileUri, String fileName, int errorCode) { + if (errorCode != NO_ERROR) { + switch (errorCode) { + case ERROR_CANNOT_GET_CACHE_DIRECTORY: + updateNotification(R.string.downloading_reddit_video_failed_cannot_get_cache_directory, fileName, null); + break; + case ERROR_VIDEO_FILE_CANNOT_DOWNLOAD: + updateNotification(R.string.downloading_reddit_video_failed_cannot_download_video, fileName, null); + break; + case ERROR_VIDEO_FILE_CANNOT_SAVE: + updateNotification(R.string.downloading_reddit_video_failed_cannot_save_video, fileName, null); + break; + case ERROR_AUDIO_FILE_CANNOT_SAVE: + updateNotification(R.string.downloading_reddit_video_failed_cannot_save_audio, fileName, null); + break; + case ERROR_MUX_FAILED: + updateNotification(R.string.downloading_reddit_video_failed_cannot_mux, fileName, null); + break; + case ERROR_MUXED_VIDEO_FILE_CANNOT_SAVE: + updateNotification(R.string.downloading_reddit_video_failed_cannot_save_mux_video, fileName, null); + break; + } + EventBus.getDefault().post(new DownloadRedditVideoEvent(false)); + } else { + MediaScannerConnection.scanFile( + this, new String[]{destinationFileUri.toString()}, null, + (path, uri) -> { + Intent intent = new Intent(); + intent.setAction(android.content.Intent.ACTION_VIEW); + intent.setDataAndType(destinationFileUri, "video/*"); + PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); + updateNotification(R.string.downloading_reddit_video_finished, fileName, pendingIntent); + EventBus.getDefault().post(new DownloadRedditVideoEvent(true)); + } + ); + } + stopForeground(false); + } + + private Notification createNotification(int stringResId, String fileName, PendingIntent pendingIntent) { + NotificationCompat.Builder builder; + if (isRedditVideo) { + builder = new NotificationCompat.Builder(this, NotificationUtils.CHANNEL_ID_DOWNLOAD_REDDIT_VIDEO); + } else { + builder = new NotificationCompat.Builder(this, NotificationUtils.CHANNEL_ID_DOWNLOAD_VIDEO); + } + builder.setContentTitle(fileName).setContentText(getString(stringResId)); + if (pendingIntent != null) { + builder.setContentIntent(pendingIntent); + } + return builder.setSmallIcon(R.drawable.ic_notification) + .setColor(mCustomThemeWrapper.getColorPrimaryLightTheme()) + .build(); + } + + private void updateNotification(int stringResId, String fileName, PendingIntent pendingIntent) { + NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + if (notificationManager != null) { + if (isRedditVideo) { + notificationManager.notify(NotificationUtils.DOWNLOAD_REDDIT_VIDEO_NOTIFICATION_ID, + createNotification(stringResId, fileName, pendingIntent)); + } else { + notificationManager.notify(NotificationUtils.DOWNLOAD_VIDEO_NOTIFICATION_ID, + createNotification(stringResId, fileName, pendingIntent)); + } + } + } + + private static class SaveTempMuxAndCopyAsyncTask extends AsyncTask { + + private ResponseBody videoResponse; + private ResponseBody audioResponse; + private String videoFilePath; + private String audioFilePath; + private String outputFilePath; + private String destinationFileName; + private ContentResolver contentResolver; + private SaveTempMuxAndCopyAsyncTaskListener saveTempMuxAndCopyAsyncTaskListener; + private Uri destinationFileUri; + private int errorCode = NO_ERROR; + + public SaveTempMuxAndCopyAsyncTask(ResponseBody videoResponse, ResponseBody audioResponse, + String videoFilePath, String audioFilePath, String outputFilePath, + String destinationFileName, ContentResolver contentResolver, + SaveTempMuxAndCopyAsyncTaskListener saveTempMuxAndCopyAsyncTaskListener) { + this.videoResponse = videoResponse; + this.audioResponse = audioResponse; + this.videoFilePath = videoFilePath; + this.audioFilePath = audioFilePath; + this.outputFilePath = outputFilePath; + this.destinationFileName = destinationFileName; + this.contentResolver = contentResolver; + this.saveTempMuxAndCopyAsyncTaskListener = saveTempMuxAndCopyAsyncTaskListener; + } + + @Override + protected void onProgressUpdate(Integer... values) { + super.onProgressUpdate(values); + saveTempMuxAndCopyAsyncTaskListener.updateProgressNotification(values[0]); + } + + @Override + protected Void doInBackground(Void... voids) { + publishProgress(R.string.downloading_reddit_video_save_video); + String savedVideoFilePath = writeResponseBodyToDisk(videoResponse, videoFilePath); + if (savedVideoFilePath == null) { + errorCode = ERROR_VIDEO_FILE_CANNOT_SAVE; + return null; + } + if (audioResponse != null) { + publishProgress(R.string.downloading_reddit_video_save_audio); + String savedAudioFilePath = writeResponseBodyToDisk(audioResponse, audioFilePath); + if (savedAudioFilePath == null) { + errorCode = ERROR_AUDIO_FILE_CANNOT_SAVE; + return null; + } + + publishProgress(R.string.downloading_reddit_video_muxing); + if (!muxVideoAndAudio(videoFilePath, audioFilePath, outputFilePath)) { + errorCode = ERROR_MUX_FAILED; + return null; + } + + publishProgress(R.string.downloading_reddit_video_save_file_to_public_dir); + copyToDestination(outputFilePath); + } else { + publishProgress(R.string.downloading_reddit_video_save_file_to_public_dir); + copyToDestination(videoFilePath); + } + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + super.onPostExecute(aVoid); + saveTempMuxAndCopyAsyncTaskListener.finished(destinationFileUri, errorCode); + } + + private String writeResponseBodyToDisk(ResponseBody body, String filePath) { + try { + File file = new File(filePath); + + InputStream inputStream = null; + OutputStream outputStream = null; + + try { + byte[] fileReader = new byte[4096]; + + long fileSize = body.contentLength(); + long fileSizeDownloaded = 0; + + inputStream = body.byteStream(); + outputStream = new FileOutputStream(file); + + while (true) { + int read = inputStream.read(fileReader); + + if (read == -1) { + break; + } + + outputStream.write(fileReader, 0, read); + + fileSizeDownloaded += read; + } + + outputStream.flush(); + + return file.getPath(); + } catch (IOException e) { + return null; + } finally { + if (inputStream != null) { + inputStream.close(); + } + + if (outputStream != null) { + outputStream.close(); + } + } + } catch (IOException e) { + return null; + } + } + + private boolean muxVideoAndAudio(String videoFilePath, String audioFilePath, String outputFilePath) { + try { + File file = new File(outputFilePath); + file.createNewFile(); + MediaExtractor videoExtractor = new MediaExtractor(); + videoExtractor.setDataSource(videoFilePath); + MediaExtractor audioExtractor = new MediaExtractor(); + audioExtractor.setDataSource(audioFilePath); + MediaMuxer muxer = new MediaMuxer(outputFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); + + videoExtractor.selectTrack(0); + MediaFormat videoFormat = videoExtractor.getTrackFormat(0); + int videoTrack = muxer.addTrack(videoFormat); + + audioExtractor.selectTrack(0); + MediaFormat audioFormat = audioExtractor.getTrackFormat(0); + int audioTrack = muxer.addTrack(audioFormat); + boolean sawEOS = false; + int offset = 100; + int sampleSize = 2048 * 1024; + ByteBuffer videoBuf = ByteBuffer.allocate(sampleSize); + ByteBuffer audioBuf = ByteBuffer.allocate(sampleSize); + MediaCodec.BufferInfo videoBufferInfo = new MediaCodec.BufferInfo(); + MediaCodec.BufferInfo audioBufferInfo = new MediaCodec.BufferInfo(); + + videoExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC); + audioExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC); + + muxer.start(); + + while (!sawEOS) { + videoBufferInfo.offset = offset; + videoBufferInfo.size = videoExtractor.readSampleData(videoBuf, offset); + + if (videoBufferInfo.size < 0 || audioBufferInfo.size < 0) { + sawEOS = true; + videoBufferInfo.size = 0; + } else { + videoBufferInfo.presentationTimeUs = videoExtractor.getSampleTime(); + videoBufferInfo.flags = videoExtractor.getSampleFlags(); + muxer.writeSampleData(videoTrack, videoBuf, videoBufferInfo); + videoExtractor.advance(); + } + } + + boolean sawEOS2 = false; + while (!sawEOS2) { + audioBufferInfo.offset = offset; + audioBufferInfo.size = audioExtractor.readSampleData(audioBuf, offset); + + if (videoBufferInfo.size < 0 || audioBufferInfo.size < 0) { + sawEOS2 = true; + audioBufferInfo.size = 0; + } else { + audioBufferInfo.presentationTimeUs = audioExtractor.getSampleTime(); + audioBufferInfo.flags = audioExtractor.getSampleFlags(); + muxer.writeSampleData(audioTrack, audioBuf, audioBufferInfo); + audioExtractor.advance(); + + } + } + + try { + muxer.stop(); + muxer.release(); + } catch (IllegalStateException ignore) {} + } catch (IOException e) { + e.printStackTrace(); + return false; + } + + return true; + } + + private void copyToDestination(String srcPath) { + if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + if (!copy(new File(srcPath), destinationFileName)) { + errorCode = ERROR_MUXED_VIDEO_FILE_CANNOT_SAVE; + } + } else { + try { + copyFileQ(new File(srcPath), destinationFileName); + } catch (IOException e) { + e.printStackTrace(); + errorCode = ERROR_MUXED_VIDEO_FILE_CANNOT_SAVE; + } + } + } + + @RequiresApi(api = Build.VERSION_CODES.Q) + private void copyFileQ(File src, String outputFileName) throws IOException { + String relativeLocation = Environment.DIRECTORY_MOVIES + "/Infinity/"; + + ContentValues contentValues = new ContentValues(); + contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, outputFileName); + contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, relativeLocation); + contentValues.put(MediaStore.Video.Media.IS_PENDING, 1); + + OutputStream stream = null; + Uri uri = null; + + try { + final Uri contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + uri = contentResolver.insert(contentUri, contentValues); + + if (uri == null) { + throw new IOException("Failed to create new MediaStore record."); + } + + stream = contentResolver.openOutputStream(uri); + + if (stream == null) { + throw new IOException("Failed to get output stream."); + } + + InputStream in = new FileInputStream(src); + + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0) { + stream.write(buf, 0, len); + } + + contentValues.clear(); + contentValues.put(MediaStore.Images.Media.IS_PENDING, 0); + contentResolver.update(uri, contentValues, null, null); + destinationFileUri = uri; + } catch (IOException e) { + if (uri != null) { + // Don't leave an orphan entry in the MediaStore + contentResolver.delete(uri, null, null); + } + + throw e; + } finally { + if (stream != null) { + stream.close(); + } + } + } + + private boolean copy(File src, String outputFileName) { + File directory = getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); + if (directory != null) { + String directoryPath = directory.getAbsolutePath() + "/Infinity/"; + String destinationFilePath = directoryPath + outputFileName; + destinationFileUri = Uri.parse(destinationFilePath); + + try (InputStream in = new FileInputStream(src)) { + try (OutputStream out = new FileOutputStream(destinationFilePath)) { + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + + src.delete(); + return true; + } + } catch (IOException e) { + e.printStackTrace(); + src.delete(); + return false; + } + } else { + src.delete(); + return false; + } + } + + interface SaveTempMuxAndCopyAsyncTaskListener { + void finished(Uri destinationFileUri, int errorCode); + void updateProgressNotification(int stringResId); + } + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a752cf3a..109b22f0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,5 +1,5 @@ - Infinity + Infinity Login Search Send Comment @@ -125,9 +125,6 @@ Cannot fetch sidebar Cannot fetch multireddit info - x%1$d - ViewUserDetailActivity - Subscribe Unsubscribe Subscribed" @@ -765,7 +762,7 @@ Parse Theme Failed Share it to r/Infinity_For_Reddit? The theme configuration is copied! - Do you want to share it to r/Infinity so that other people can use it? + Do you want to share it to r/Infinity_For_Reddit so that other people can use it? Duplicate Theme Found A theme in the database is also called %1$s. Do you want to change the name of this imported theme? Rename @@ -837,6 +834,7 @@ Download failed: cannot save audio to cache directory Download failed: cannot mux video and audio Download failed: cannot save the video to public directory + Downloading Video Wallpaper set Cannot set wallpaper -- cgit v1.2.3