diff options
author | Alex Ning <chineseperson5@gmail.com> | 2020-06-06 13:33:03 +0000 |
---|---|---|
committer | Alex Ning <chineseperson5@gmail.com> | 2020-06-06 13:33:03 +0000 |
commit | 22576dc003ddaeb0c06f94eaf5f66598e5b3554f (patch) | |
tree | 2f86b927d4ea614776a49ac67c1a910ff13b0897 /app | |
parent | ff5691aecb0f53ab11ea5a74244d10da7582cf26 (diff) | |
download | infinity-for-reddit-22576dc003ddaeb0c06f94eaf5f66598e5b3554f.tar infinity-for-reddit-22576dc003ddaeb0c06f94eaf5f66598e5b3554f.tar.gz infinity-for-reddit-22576dc003ddaeb0c06f94eaf5f66598e5b3554f.tar.bz2 infinity-for-reddit-22576dc003ddaeb0c06f94eaf5f66598e5b3554f.tar.lz infinity-for-reddit-22576dc003ddaeb0c06f94eaf5f66598e5b3554f.tar.xz infinity-for-reddit-22576dc003ddaeb0c06f94eaf5f66598e5b3554f.tar.zst infinity-for-reddit-22576dc003ddaeb0c06f94eaf5f66598e5b3554f.zip |
Downloading Reddit videos with sound is now available. For Android Q and above, the downloaded videos are stored in Movies/Infinity.
Diffstat (limited to '')
12 files changed, 582 insertions, 41 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9980720a..2a2a74bd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,6 +21,11 @@ android:theme="@style/AppTheme" android:usesCleartextTraffic="true" tools:replace="android:label"> + <service + android:name=".Service.DownloadRedditVideoService" + android:enabled="true" + android:exported="false"></service> + <activity android:name=".Activity.ViewImgurMediaActivity" android:configChanges="orientation|screenSize|layoutDirection" @@ -265,7 +270,7 @@ android:label="@string/comment_activity_label" android:parentActivityName=".Activity.MainActivity" android:theme="@style/AppTheme.NoActionBar" - android:windowSoftInputMode="adjustResize"/> + android:windowSoftInputMode="adjustResize" /> <activity android:name=".Activity.SearchResultActivity" android:label="@string/search_activity_label" diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/API/DownloadRedditVideo.java b/app/src/main/java/ml/docilealligator/infinityforreddit/API/DownloadRedditVideo.java new file mode 100644 index 00000000..7e51123d --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/API/DownloadRedditVideo.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 DownloadRedditVideo { + @Streaming + @GET() + Call<ResponseBody> downloadFile(@Url String fileUrl); +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/AccessTokenAuthenticator.java b/app/src/main/java/ml/docilealligator/infinityforreddit/AccessTokenAuthenticator.java index 709a8cd5..408d180d 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/AccessTokenAuthenticator.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/AccessTokenAuthenticator.java @@ -67,7 +67,7 @@ class AccessTokenAuthenticator implements Authenticator { Call<String> accessTokenCall = api.getAccessToken(APIUtils.getHttpBasicAuthHeader(), params); try { - retrofit2.Response response = accessTokenCall.execute(); + retrofit2.Response<String> response = accessTokenCall.execute(); if (response.isSuccessful() && response.body() != null) { JSONObject jsonObject = new JSONObject((String) response.body()); String newAccessToken = jsonObject.getString(APIUtils.ACCESS_TOKEN_KEY); 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 29b78878..0bd5f7fc 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/Activity/ViewVideoActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/Activity/ViewVideoActivity.java @@ -59,6 +59,7 @@ import butterknife.ButterKnife; import ml.docilealligator.infinityforreddit.FetchGfycatVideoLinks; import ml.docilealligator.infinityforreddit.Infinity; import ml.docilealligator.infinityforreddit.R; +import ml.docilealligator.infinityforreddit.Service.DownloadRedditVideoService; import ml.docilealligator.infinityforreddit.Utils.SharedPreferencesUtils; import retrofit2.Retrofit; @@ -94,11 +95,14 @@ public class ViewVideoActivity extends AppCompatActivity { private String videoDownloadUrl; private String videoFileName; + private String subredditName; + private String id; private boolean wasPlaying; private boolean isDownloading = false; private boolean isMute = false; private String postTitle; private long resumePosition = -1; + private int videoType; @Inject @Named("gfycat") @@ -181,7 +185,7 @@ public class ViewVideoActivity extends AppCompatActivity { TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory); player = ExoPlayerFactory.newSimpleInstance(this, trackSelector); videoPlayerView.setPlayer(player); - int videoType = getIntent().getIntExtra(EXTRA_VIDEO_TYPE, VIDEO_TYPE_NORMAL); + videoType = getIntent().getIntExtra(EXTRA_VIDEO_TYPE, VIDEO_TYPE_NORMAL); if (videoType == VIDEO_TYPE_GFYCAT || videoType == VIDEO_TYPE_REDGIFS) { if (savedInstanceState != null) { String videoUrl = savedInstanceState.getString(VIDEO_URI_STATE); @@ -243,7 +247,9 @@ public class ViewVideoActivity extends AppCompatActivity { player.prepare(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mVideoUri)); } else { videoDownloadUrl = intent.getStringExtra(EXTRA_VIDEO_DOWNLOAD_URL); - videoFileName = intent.getStringExtra(EXTRA_SUBREDDIT) + "-" + intent.getStringExtra(EXTRA_ID) + ".mp4"; + subredditName = intent.getStringExtra(EXTRA_SUBREDDIT); + id = intent.getStringExtra(EXTRA_ID); + videoFileName = subredditName + "-" + id + ".mp4"; // Produces DataSource instances through which media data is loaded. dataSourceFactory = new DefaultHttpDataSourceFactory(Util.getUserAgent(this, "Infinity")); // Prepare the player with the source. @@ -387,46 +393,59 @@ public class ViewVideoActivity extends AppCompatActivity { private void download() { isDownloading = false; - DownloadManager.Request request = new DownloadManager.Request(Uri.parse(videoDownloadUrl)); - request.setTitle(videoFileName); + if (videoType != VIDEO_TYPE_NORMAL) { + DownloadManager.Request request = new DownloadManager.Request(Uri.parse(videoDownloadUrl)); + request.setTitle(videoFileName); - request.allowScanningByMediaScanner(); - request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); + request.allowScanningByMediaScanner(); + request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); - //Android Q support - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - request.setDestinationInExternalPublicDir(Environment.DIRECTORY_PICTURES, videoFileName); - } else { - String path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString(); - File directory = new File(path + "/Infinity/"); - boolean saveToInfinityFolder = true; - if (!directory.exists()) { - if (!directory.mkdir()) { - saveToInfinityFolder = false; - } + //Android Q support + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + request.setDestinationInExternalPublicDir(Environment.DIRECTORY_PICTURES, videoFileName); } else { - if (directory.isFile()) { - if (!(directory.delete() && directory.mkdir())) { + String path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString(); + File directory = new File(path + "/Infinity/"); + boolean saveToInfinityFolder = true; + if (!directory.exists()) { + if (!directory.mkdir()) { saveToInfinityFolder = false; } + } else { + if (directory.isFile()) { + if (!(directory.delete() && directory.mkdir())) { + saveToInfinityFolder = false; + } + } + } + + if (saveToInfinityFolder) { + request.setDestinationInExternalPublicDir(Environment.DIRECTORY_PICTURES + "/Infinity/", videoFileName); + } else { + request.setDestinationInExternalPublicDir(Environment.DIRECTORY_PICTURES, videoFileName); } } - if (saveToInfinityFolder) { - request.setDestinationInExternalPublicDir(Environment.DIRECTORY_PICTURES + "/Infinity/", videoFileName); - } else { - request.setDestinationInExternalPublicDir(Environment.DIRECTORY_PICTURES, videoFileName); + DownloadManager manager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE); + + if (manager == null) { + Toast.makeText(this, R.string.download_failed, Toast.LENGTH_SHORT).show(); + return; } - } - DownloadManager manager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE); + manager.enqueue(request); + } 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); - if (manager == null) { - Toast.makeText(this, R.string.download_failed, Toast.LENGTH_SHORT).show(); - return; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + startForegroundService(intent); + } else { + startService(intent); + } } - - manager.enqueue(request); Toast.makeText(this, R.string.download_started, Toast.LENGTH_SHORT).show(); } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/Adapter/PostRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/Adapter/PostRecyclerViewAdapter.java index eea62a78..07009344 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/Adapter/PostRecyclerViewAdapter.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/Adapter/PostRecyclerViewAdapter.java @@ -879,7 +879,7 @@ public class PostRecyclerViewAdapter extends PagedListAdapter<Post, RecyclerView intent.setData(videoUri); intent.putExtra(ViewVideoActivity.EXTRA_VIDEO_DOWNLOAD_URL, post.getVideoDownloadUrl()); intent.putExtra(ViewVideoActivity.EXTRA_SUBREDDIT, subredditName); - intent.putExtra(ViewVideoActivity.EXTRA_ID, fullName); + intent.putExtra(ViewVideoActivity.EXTRA_ID, id); mActivity.startActivity(intent); }); @@ -2130,7 +2130,7 @@ public class PostRecyclerViewAdapter extends PagedListAdapter<Post, RecyclerView intent.setData(Uri.parse(post.getVideoUrl())); intent.putExtra(ViewVideoActivity.EXTRA_VIDEO_DOWNLOAD_URL, post.getVideoDownloadUrl()); intent.putExtra(ViewVideoActivity.EXTRA_SUBREDDIT, post.getSubredditName()); - intent.putExtra(ViewVideoActivity.EXTRA_ID, post.getFullName()); + intent.putExtra(ViewVideoActivity.EXTRA_ID, post.getId()); intent.putExtra(ViewVideoActivity.EXTRA_POST_TITLE, post.getTitle()); mActivity.startActivity(intent); } else if (post.getPostType() == Post.GIF_TYPE) { diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java b/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java index 86274fd6..4ee9fe85 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java @@ -51,6 +51,7 @@ import ml.docilealligator.infinityforreddit.Fragment.SubredditListingFragment; import ml.docilealligator.infinityforreddit.Fragment.SubscribedSubredditsListingFragment; import ml.docilealligator.infinityforreddit.Fragment.UserListingFragment; import ml.docilealligator.infinityforreddit.Fragment.ViewImgurVideoFragment; +import ml.docilealligator.infinityforreddit.Service.DownloadRedditVideoService; import ml.docilealligator.infinityforreddit.Service.SubmitPostService; import ml.docilealligator.infinityforreddit.Settings.AdvancedPreferenceFragment; import ml.docilealligator.infinityforreddit.Settings.CustomizeMainPageTabsFragment; @@ -173,4 +174,6 @@ public interface AppComponent { void inject(ViewImgurMediaActivity viewImgurMediaActivity); void inject(ViewImgurVideoFragment viewImgurVideoFragment); + + void inject(DownloadRedditVideoService downloadRedditVideoService); } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/AppModule.java b/app/src/main/java/ml/docilealligator/infinityforreddit/AppModule.java index 8fe1e28d..f4f32b20 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/AppModule.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/AppModule.java @@ -91,6 +91,16 @@ class AppModule { } @Provides + @Named("download_reddit_video") + @Singleton + Retrofit provideDownloadRedditVideoRetrofit() { + return new Retrofit.Builder() + .baseUrl("http://localhost/") + .addConverterFactory(ScalarsConverterFactory.create()) + .build(); + } + + @Provides @Named("gfycat") @Singleton Retrofit provideGfycatRetrofit() { diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/Event/DownloadRedditVideoEvent.java b/app/src/main/java/ml/docilealligator/infinityforreddit/Event/DownloadRedditVideoEvent.java new file mode 100644 index 00000000..2edb279e --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/Event/DownloadRedditVideoEvent.java @@ -0,0 +1,9 @@ +package ml.docilealligator.infinityforreddit.Event; + +public class DownloadRedditVideoEvent { + public boolean isSuccessful; + + public DownloadRedditVideoEvent(boolean isSuccessful) { + this.isSuccessful = isSuccessful; + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/NotificationUtils.java b/app/src/main/java/ml/docilealligator/infinityforreddit/NotificationUtils.java index 36160d0c..155e4b02 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/NotificationUtils.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/NotificationUtils.java @@ -11,7 +11,10 @@ public class NotificationUtils { public static final String CHANNEL_SUBMIT_POST = "Submit Post"; static final String CHANNEL_ID_NEW_MESSAGES = "new_messages"; 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 int SUBMIT_POST_SERVICE_NOTIFICATION_ID = 10000; + public static final int DOWNLOAD_REDDIT_VIDEO_NOTIFICATION_ID = 20000; 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 new file mode 100644 index 00000000..e5885c90 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/Service/DownloadRedditVideoService.java @@ -0,0 +1,480 @@ +package ml.docilealligator.infinityforreddit.Service; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.Service; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Intent; +import android.media.MediaCodec; +import android.media.MediaExtractor; +import android.media.MediaFormat; +import android.media.MediaMuxer; +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 android.util.Log; + +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.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"; + + @Inject + @Named("download_reddit_video") + Retrofit retrofit; + @Inject + CustomThemeWrapper mCustomThemeWrapper; + + 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); + } + + startForeground(NotificationUtils.DOWNLOAD_REDDIT_VIDEO_NOTIFICATION_ID, createNotification(R.string.downloading_reddit_video)); + + ml.docilealligator.infinityforreddit.API.DownloadRedditVideo downloadRedditVideo = retrofit.create(ml.docilealligator.infinityforreddit.API.DownloadRedditVideo.class); + downloadRedditVideo.downloadFile(videoUrl).enqueue(new Callback<ResponseBody>() { + @Override + public void onResponse(@NonNull Call<ResponseBody> call, @NonNull Response<ResponseBody> videoResponse) { + if (videoResponse.isSuccessful() && videoResponse.body() != null) { + String fileNameWithoutExtension = intent.getStringExtra(EXTRA_SUBREDDIT) + + "-" + intent.getStringExtra(EXTRA_POST_ID); + + downloadRedditVideo.downloadFile(audioUrl).enqueue(new Callback<ResponseBody>() { + @Override + public void onResponse(@NonNull Call<ResponseBody> call, @NonNull Response<ResponseBody> audioResponse) { + File directory = getExternalCacheDir(); + if (directory != null) { + String directoryPath = directory.getAbsolutePath() + "/"; + if (audioResponse.isSuccessful() && audioResponse.body() != null) { + String videoFilePath = writeResponseBodyToDisk(videoResponse.body(), directoryPath + fileNameWithoutExtension+ "-cache.mp4"); + if (videoFilePath != null) { + String audioFilePath = writeResponseBodyToDisk(audioResponse.body(), directoryPath + fileNameWithoutExtension + "-cache.mp3"); + if (audioFilePath != null) { + String outputFilePath = directoryPath + fileNameWithoutExtension + ".mp4"; + if(muxVideoAndAudio(videoFilePath, audioFilePath, outputFilePath)) { + new CopyFileAsyncTask(new File(outputFilePath), fileNameWithoutExtension + ".mp4", + getContentResolver(), new CopyFileAsyncTask.CopyFileAsyncTaskListener() { + @Override + public void successful() { + new File(videoFilePath).delete(); + new File(audioFilePath).delete(); + new File(outputFilePath).delete(); + + EventBus.getDefault().post(new DownloadRedditVideoEvent(true)); + + stopService(); + } + + @Override + public void failed() { + new File(videoFilePath).delete(); + new File(audioFilePath).delete(); + new File(outputFilePath).delete(); + + EventBus.getDefault().post(new DownloadRedditVideoEvent(false)); + + stopService(); + } + }).execute(); + } else { + EventBus.getDefault().post(new DownloadRedditVideoEvent(false)); + + stopService(); + } + } else { + new File(videoFilePath).delete(); + + EventBus.getDefault().post(new DownloadRedditVideoEvent(false)); + + stopService(); + } + } else { + EventBus.getDefault().post(new DownloadRedditVideoEvent(false)); + + stopService(); + } + } else { + //No audio + String videoFilePath = writeResponseBodyToDisk(videoResponse.body(), directoryPath + fileNameWithoutExtension+ ".mp4"); + if (videoFilePath != null) { + new CopyFileAsyncTask(new File(videoFilePath), fileNameWithoutExtension + ".mp4", + getContentResolver(), new CopyFileAsyncTask.CopyFileAsyncTaskListener() { + @Override + public void successful() { + new File(videoFilePath).delete(); + + EventBus.getDefault().post(new DownloadRedditVideoEvent(true)); + + stopService(); + } + + @Override + public void failed() { + new File(videoFilePath).delete(); + + EventBus.getDefault().post(new DownloadRedditVideoEvent(false)); + + stopService(); + } + }).execute(); + } else { + EventBus.getDefault().post(new DownloadRedditVideoEvent(false)); + + stopService(); + } + } + } else { + EventBus.getDefault().post(new DownloadRedditVideoEvent(false)); + + stopService(); + } + } + + @Override + public void onFailure(@NonNull Call<ResponseBody> call, @NonNull Throwable t) { + EventBus.getDefault().post(new DownloadRedditVideoEvent(false)); + + stopService(); + } + }); + } else { + EventBus.getDefault().post(new DownloadRedditVideoEvent(false)); + + stopService(); + } + } + + @Override + public void onFailure(@NonNull Call<ResponseBody> call, @NonNull Throwable t) { + EventBus.getDefault().post(new DownloadRedditVideoEvent(false)); + + stopService(); + } + }); + + return START_NOT_STICKY; + } + + private Notification createNotification(int stringResId) { + return new NotificationCompat.Builder(this, NotificationUtils.CHANNEL_ID_DOWNLOAD_REDDIT_VIDEO) + .setContentTitle(getString(stringResId)) + .setContentText(getString(R.string.please_wait)) + .setSmallIcon(R.drawable.ic_notification) + .setColor(mCustomThemeWrapper.getColorPrimaryLightTheme()) + .build(); + } + + 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; + + Log.i("asdfsadf", "file download: " + fileSizeDownloaded + " of " + fileSize); + } + + 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 = 256 * 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) + { + // Log.d(TAG, "saw input EOS."); + 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(); + + } + } + + muxer.stop(); + muxer.release(); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + + return true; + } + + private void stopService() { + stopForeground(true); + stopSelf(); + } + + private static class CopyFileAsyncTask extends AsyncTask<Void, Void, Void> { + private File src; + private String destinationFileName; + private ContentResolver contentResolver; + private CopyFileAsyncTaskListener copyFileAsyncTaskListener; + private boolean successful; + + interface CopyFileAsyncTaskListener { + void successful(); + void failed(); + } + + CopyFileAsyncTask(File src, String destinationFileName, ContentResolver contentResolver, CopyFileAsyncTaskListener copyFileAsyncTaskListener) { + this.src = src; + this.destinationFileName = destinationFileName; + this.contentResolver = contentResolver; + this.copyFileAsyncTaskListener = copyFileAsyncTaskListener; + } + + @Override + protected Void doInBackground(Void... voids) { + if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + successful = copy(src, destinationFileName); + } else { + try { + copyFileQ(src, destinationFileName); + successful = true; + } catch (IOException e) { + e.printStackTrace(); + successful = false; + } + } + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + super.onPostExecute(aVoid); + if (successful) { + copyFileAsyncTaskListener.successful(); + } else { + copyFileAsyncTaskListener.failed(); + } + } + + @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); + } 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/";; + File dst = new File(directoryPath, outputFileName); + + try (InputStream in = new FileInputStream(src)) { + try (OutputStream out = new FileOutputStream(dst)) { + 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; + } + } + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/Service/SubmitPostService.java b/app/src/main/java/ml/docilealligator/infinityforreddit/Service/SubmitPostService.java index cedf9730..5177e415 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/Service/SubmitPostService.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/Service/SubmitPostService.java @@ -4,7 +4,6 @@ import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.Service; -import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; @@ -110,22 +109,22 @@ public class SubmitPostService extends Service { if (postType == EXTRA_POST_TEXT_OR_LINK) { content = intent.getStringExtra(EXTRA_CONTENT); kind = intent.getStringExtra(EXTRA_KIND); - startForeground(NotificationUtils.SUBMIT_POST_SERVICE_NOTIFICATION_ID, createNotification(this, R.string.posting)); + startForeground(NotificationUtils.SUBMIT_POST_SERVICE_NOTIFICATION_ID, createNotification(R.string.posting)); submitTextOrLinkPost(); } else if (postType == EXTRA_POST_TYPE_IMAGE) { mediaUri = intent.getData(); - startForeground(NotificationUtils.SUBMIT_POST_SERVICE_NOTIFICATION_ID, createNotification(this, R.string.posting_image)); + startForeground(NotificationUtils.SUBMIT_POST_SERVICE_NOTIFICATION_ID, createNotification(R.string.posting_image)); submitImagePost(); } else { mediaUri = intent.getData(); - startForeground(NotificationUtils.SUBMIT_POST_SERVICE_NOTIFICATION_ID, createNotification(this, R.string.posting_video)); + startForeground(NotificationUtils.SUBMIT_POST_SERVICE_NOTIFICATION_ID, createNotification(R.string.posting_video)); submitVideoPost(); } return START_NOT_STICKY; } - private Notification createNotification(Context context, int stringResId) { + private Notification createNotification(int stringResId) { return new NotificationCompat.Builder(this, NotificationUtils.CHANNEL_SUBMIT_POST) .setContentTitle(getString(stringResId)) .setContentText(getString(R.string.please_wait)) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fc088ae2..6100247c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -739,7 +739,7 @@ <string name="fetching_video_info_please_wait">Fetching video info. Please wait.</string> <string name="error_fetching_imgur_media">Cannot load images</string> - <!-- TODO: Remove or change this placeholder text --> - <string name="hello_blank_fragment">Hello blank fragment</string> + + <string name="downloading_reddit_video">Downloading video.</string> </resources> |