package com.kpstv.youtube.services; import android.annotation.SuppressLint; import android.app.IntentService; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.drawable.Drawable; import android.media.MediaCodec; import android.media.MediaExtractor; import android.media.MediaFormat; import android.media.MediaMetadataRetriever; import android.media.MediaMuxer; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Environment; import android.os.Handler; import android.os.PowerManager; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.JobIntentService; import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationManagerCompat; import android.util.Log; import android.widget.Toast; import com.arthenica.mobileffmpeg.Config; import com.arthenica.mobileffmpeg.FFmpeg; import com.bumptech.glide.Glide; import com.bumptech.glide.request.target.CustomTarget; import com.bumptech.glide.request.transition.Transition; import com.coremedia.iso.boxes.Container; import com.downloader.Error; import com.downloader.OnDownloadListener; import com.downloader.PRDownloader; import com.googlecode.mp4parser.authoring.Movie; import com.googlecode.mp4parser.authoring.Track; import com.googlecode.mp4parser.authoring.builder.DefaultMp4Builder; import com.googlecode.mp4parser.authoring.container.mp4.MovieCreator; import com.kpstv.youtube.DownloadActivity; import com.kpstv.youtube.MainActivity; import com.kpstv.youtube.R; import com.kpstv.youtube.models.OFModel; import com.kpstv.youtube.models.YTConfig; import com.kpstv.youtube.receivers.SongBroadCast; import com.kpstv.youtube.utils.FileUtils; import com.kpstv.youtube.utils.SoundCloud; import com.kpstv.youtube.utils.YTMeta; import com.kpstv.youtube.utils.YTutils; import com.naveed.ytextractor.ExtractorException; import com.naveed.ytextractor.YoutubeStreamExtractor; import com.naveed.ytextractor.model.YTMedia; import com.naveed.ytextractor.model.YoutubeMeta; import org.cmc.music.metadata.ImageData; import org.cmc.music.metadata.MusicMetadata; import org.cmc.music.metadata.MusicMetadataSet; import org.cmc.music.myid3.MyID3; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.URL; import java.net.URLConnection; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Random; import static com.arthenica.mobileffmpeg.FFmpeg.RETURN_CODE_CANCEL; import static com.arthenica.mobileffmpeg.FFmpeg.RETURN_CODE_SUCCESS; public class IntentDownloadService extends IntentService { private static final String TAG = "IntentDownloadService"; private boolean isDownloaded = false; private PowerManager.WakeLock wakeLock; NotificationManagerCompat notificationManagerCompat; private Context context; private String CHANNEL_ID = "channel_02"; Bitmap icon; private final int FOREGROUND_ID = 109; Notification notification; PendingIntent contentIntent, cancelIntent; private long oldbytes; boolean useFFMPEGmuxer=true; private Handler handler; /** * Some static Declarations */ public static YTConfig currentModel; public static int progress; public static long totalsize; public static ArrayList<YTConfig> pendingJobs; public static long currentsize; // public static Process process; // FFmpeg ffmpeg; public IntentDownloadService() { super("IntentDownloadService"); setIntentRedelivery(true); } @Override public void onCreate() { context = getApplicationContext(); pendingJobs = new ArrayList<>(); SharedPreferences preferences = getSharedPreferences("appSettings",MODE_PRIVATE); useFFMPEGmuxer = preferences.getBoolean("pref_muxer",true); PRDownloader.initialize(context); /** Create notification channel if not present */ if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.O) { CharSequence name = context.getString(R.string.channel_name); String description = context.getString(R.string.channel_description); int importance = NotificationManager.IMPORTANCE_LOW; NotificationChannel notificationChannel = new NotificationChannel("channel_01", name, importance); notificationChannel.setDescription(description); NotificationManager notificationManager = context.getSystemService(NotificationManager.class); notificationManager.createNotificationChannel(notificationChannel); NotificationChannel channel = new NotificationChannel(CHANNEL_ID,"Download",importance); notificationManager.createNotificationChannel(channel); } /** Setting Power Manager and Wakelock */ PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "app:Wakelock"); wakeLock.acquire(); Intent notificationIntent = new Intent(context, DownloadActivity.class); contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0); Intent newintent = new Intent(context, SongBroadCast.class); newintent.setAction("com.kpstv.youtube.STOP_SERVICE"); cancelIntent = PendingIntent.getBroadcast(context, 5, newintent, 0); setUpdateNotificationTask(); super.onCreate(); } @Override public int onStartCommand(@Nullable Intent intent, int flags, int startId) { YTConfig config = (YTConfig) intent.getSerializableExtra("addJob"); pendingJobs.add(config); /** Setting Notification */ notificationManagerCompat = NotificationManagerCompat.from(context); notification = new NotificationCompat.Builder(context, CHANNEL_ID) .setContentTitle("Download") .addAction(R.mipmap.ic_launcher, "Cancel", cancelIntent) .setContentText(pendingJobs.size() + " files downloading") .setSmallIcon(android.R.drawable.stat_sys_download) .setContentIntent(contentIntent) .setOngoing(true) .setPriority(Notification.PRIORITY_LOW) .build(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForeground(FOREGROUND_ID, notification); } else { notificationManagerCompat.notify(FOREGROUND_ID, notification); } return super.onStartCommand(intent, flags, startId); } @Override protected void onHandleIntent(@NonNull Intent intent) { isDownloaded=false; currentModel = (YTConfig) intent.getSerializableExtra("addJob"); if (pendingJobs.size() > 0) pendingJobs.remove(0); switch (currentModel.getTaskExtra()) { case "autoTask": autoTask(); break; case "mp3Task": mp3Task(); break; case "mergeTask": mergeTask(); break; } } /*@Override protected void onHandleIntent(@Nullable Intent intent) { *//** Set notification update handler*//* currentModel = (YTConfig) intent.getSerializableExtra("addJob"); if (pendingJobs.size() > 0) pendingJobs.remove(0); switch (currentModel.getTaskExtra()) { case "mp3Task": mp3Task(); break; case "mergeTask": mergeTask(); break; } }*/ public void setFinalNotification(File dst) { Notification not; String contentText = currentModel.getTitle() + " - " + currentModel.getChannelTitle(); if (dst.exists()) { Intent openSong = new Intent(context, SongBroadCast.class); openSong.setAction("com.kpstv.youtube.OPEN_SONG"); openSong.setData(Uri.fromFile(dst)); Intent openShare = new Intent(context, SongBroadCast.class); openShare.setAction("com.kpstv.youtube.OPEN_SHARE_SONG"); openShare.setData(Uri.fromFile(dst)); PendingIntent opensongService = PendingIntent.getBroadcast(context, 6, openSong, 0); PendingIntent openshareService = PendingIntent.getBroadcast(context, 7, openShare, 0); not = new NotificationCompat.Builder(context, CHANNEL_ID) .setContentTitle("Download Complete") .setContentText(contentText) .setSmallIcon(R.drawable.ic_check) .setContentIntent(opensongService) .setAutoCancel(true) .setPriority(Notification.PRIORITY_LOW) .addAction(R.mipmap.ic_launcher, "Share", openshareService) .build(); } else { not = new NotificationCompat.Builder(context, CHANNEL_ID) .setDefaults(Notification.DEFAULT_ALL) .setSmallIcon(R.drawable.ic_error_outline) .setContentTitle("Download Failed") .setContentText(contentText) .setAutoCancel(true) .setPriority(Notification.PRIORITY_LOW) .build(); } int nos = new Random().nextInt(400) + 150; notificationManagerCompat.notify(nos, not); } public static void LOG(String message) { Log.e(TAG, message); } public void setUpdateNotificationTask() { if (handler != null) handler.removeCallbacks(updateNotificationTask); handler = new Handler(); handler.post(updateNotificationTask); } private void addFiletoLocalDevice(File f, YTConfig model) { if (f.exists()) { /** Generate file name */ File fileList = new File(getFilesDir(), "fileList.csv"); String fileName = f.getParent().replace("/", "_") + ".csv"; File file = new File(getFilesDir(), "locals/" + fileName); Log.e(TAG, "FileList: " + fileList.getPath()); Log.e(TAG, "File: " + file.getPath()); if (fileList.exists()) { String author = model.getChannelTitle(); String album = "Unknown album"; String durationStr = "0"; String lastModified = YTutils.getDate(new Date(f.lastModified())); try { MediaMetadataRetriever mmr = new MediaMetadataRetriever(); mmr.setDataSource(context, Uri.fromFile(f)); durationStr = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); String album_local = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM); if (album_local != null) album = album_local; } catch (Exception ignored) { } int s = Integer.parseInt(durationStr) / 1000; String line = f.getPath() + "|" + author + "|" + album + "|" + s + "|" + lastModified; if (!file.exists()) { YTutils.writeContent(context, file.getPath(), line); String data = YTutils.readContent(context, "fileList.csv"); String fileContent = data + "\n" + f.getParent() + "|1|" + s; YTutils.writeContent(context, fileList.getPath(), "\n" + fileContent); } else { /** Check if file already exist in main File...*/ String mainData = YTutils.readContent(context,file.getPath()); if (mainData != null && mainData.contains(f.getPath()+"|")) return; /** Modify fileList */ String fileData = YTutils.readContent(context, "fileList.csv"); if (fileData != null && !fileData.isEmpty()) { String[] items = fileData.split("\n|\r"); StringBuilder builder = new StringBuilder(); LOG("Parent: " + f.getParent()); for (int i = 0; i < items.length; i++) { String l = items[i]; if (l.isEmpty()) continue; LOG("Line: " + l); if (l.contains(f.getParent() + "|")) { LOG("I came here..."); String[] childs = l.split("\\|"); int numberOfSong = Integer.parseInt(childs[1]) + 1; int duration = Integer.parseInt(childs[2]) + s; builder.append("\n").append(f.getParent()).append("|").append(numberOfSong).append("|") .append(duration); } else builder.append("\n").append(l); } YTutils.writeContent(context, "fileList.csv", builder.toString()); } String data = YTutils.readContent(context, file.getPath()) + "\n" + line; YTutils.writeContent(context, file.getPath(), data); } } } } public synchronized void setProgress(int progress, boolean indeterminate) { if (indeterminate) IntentDownloadService.progress = -1; else IntentDownloadService.progress = progress; } private void autoTask() { /** Generate a download link */ if (currentModel.getVideoID().contains("soundcloud.com")) { SoundCloud soundCloud = new SoundCloud(currentModel.getVideoID()); if (soundCloud.getModel()!=null) { if (currentModel.getTitle().equals("auto-generate")) { String title = soundCloud.getModel().getTitle(); String author = soundCloud.getModel().getAuthorName(); String imgUrl = soundCloud.getModel().getImageUrl(); currentModel.setExt("mp3"); currentModel.setTitle(title); currentModel.setChannelTitle(author); currentModel.setImageUrl(imgUrl); currentModel.setTargetName(YTutils.getTargetName(currentModel)); Log.e(TAG, "autoTask: CurrentModel: " +currentModel.getTitle()); } currentModel.setExt("mp3"); currentModel.setUrl(soundCloud.getModel().getStreamUrl()); }else Toast.makeText(context, "Failed to extract soundcloud link!", Toast.LENGTH_SHORT).show(); }else { isDownloaded=false; new YoutubeStreamExtractor(new YoutubeStreamExtractor.ExtractorListner() { @Override public void onExtractionGoesWrong(ExtractorException e) { LOG("Stream Error"); isDownloaded = true; } @Override public void onExtractionDone(List<YTMedia> adativeStream, List<YTMedia> muxedStream, YoutubeMeta meta) { /** Find the download link for suitable format */ if (adativeStream.isEmpty()) { LOG("AdativeStream Empty"); setFinalNotification(new File("null")); isDownloaded = true; return; } Log.e(TAG, "onExtractionDone: Current Ext: "+currentModel.getExt() ); switch (currentModel.getExt()) { case "mp3": currentModel.setExt("mp3"); currentModel.setUrl(getAudioStream(adativeStream)); break; case "m4a": currentModel.setExt("m4a"); currentModel.setUrl(getAudioStream(adativeStream)); break; case "1080p": currentModel.setExt("mp4"); currentModel.setAudioUrl(getAudioStream(adativeStream)); currentModel.setUrl(getVideoStream(adativeStream, 1080)); break; case "720p": currentModel.setExt("mp4"); currentModel.setAudioUrl(getAudioStream(adativeStream)); currentModel.setUrl(getVideoStream(adativeStream, 720)); break; case "480p": currentModel.setExt("mp4"); currentModel.setAudioUrl(getAudioStream(adativeStream)); currentModel.setUrl(getVideoStream(adativeStream, 480)); break; } isDownloaded = true; } }).useDefaultLogin().Extract(currentModel.getVideoID()); do { LOG("isDownloaded="+isDownloaded); }while (!isDownloaded); LOG("Normal Url: "+currentModel.getUrl()); LOG("Audio Url: "+currentModel.getAudioUrl()); } isDownloaded=false; if (currentModel.getUrl()!=null && !currentModel.getUrl().equals("auto-generate")) { if (currentModel.getExt().equals("mp3")||currentModel.getExt().equals("m4a")) { mp3Task(); }else if (currentModel.getExt().equals("mp4")) { mergeTask(); } }else LOG("Failed autoTask for: "+currentModel.getTargetName()); } private String getAudioStream(List<YTMedia> adativeStream) { for (YTMedia media : adativeStream) { if (media.getMimeType().contains("audio/mp4")) { return media.getUrl(); } } return null; } private String getVideoStream(List<YTMedia> adativeStream,int quality) { String backupUri=null; boolean setBackupUri=false; for (YTMedia media : adativeStream) { if (media.getAudioSampleRate() == 0) { if (!setBackupUri) { backupUri = media.getUrl(); setBackupUri=true; } if (media.getHeight()==quality) { return media.getUrl(); } } } return backupUri; } @SuppressLint("StaticFieldLeak") private void mp3Task() { try { LOG("Task detected MP3"); /** Set Image Url first and save to BitMap ICON... */ String imageUri; if (currentModel.getVideoID().contains("soundcloud.com")) imageUri = currentModel.getImageUrl(); else imageUri = YTutils.getImageUrlID_MAX(currentModel.getVideoID()); setProgress(0, true); icon = YTutils.getBitmapFromURL(imageUri); if (icon==null && !currentModel.getVideoID().contains("soundcloud.com")) { String refreshedUri = YTutils.getImageUrlID_MQ(YTutils.getVideoID_ImageUri(imageUri)); Log.e(TAG, "mp3Task: Error Image, Refreshing... "+refreshedUri ); icon = YTutils.getBitmapFromURL(refreshedUri); if (icon!=null) { imageUri = refreshedUri; } } setProgress(0, false); /*Glide.with(context).asBitmap().load(imageUri).into(new CustomTarget<Bitmap>() { @Override public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) { icon = resource; setProgress(0, true); } @Override public void onLoadCleared(@Nullable Drawable placeholder) { } });*/ /** Set Prefix for path... */ String prefixName = (currentModel.getTitle().trim() + "_" + currentModel.getChannelTitle().trim()) .replace(" ", "_").replace("]", "").replace("[", "") .replace("{", "").replace("}", "").replace("/", ""); /** This is our download stream... */ String download_Uri = currentModel.getUrl(); isDownloaded = false; /** Setting some file info... */ File f = YTutils.getFile("YTPlayer/" + prefixName + ".file"); if (f.exists()) f.delete(); File mp3 = YTutils.getFile("YTPlayer/" + prefixName + "." +currentModel.getExt()); if (mp3.exists()) mp3.delete(); File dst = YTutils.getFile(Environment.DIRECTORY_DOWNLOADS + "/" + currentModel.getTargetName() + "." + currentModel.getExt()); if (dst.exists()) dst.delete(); /** Actually downloading it... */ PRDownloader.download(download_Uri, YTutils.getFile("YTPlayer").getPath(), f.getName()) .build() .setOnProgressListener(progress1 -> { if (currentModel==null) { return; } if (currentModel.getExt().equals("mp3")||currentModel.getExt().equals("m4a")) totalsize = progress1.totalBytes * 2; else { totalsize = progress1.totalBytes; } currentsize = progress1.currentBytes; setProgress((int) (currentsize * 100 / totalsize), false); }) .start(new OnDownloadListener() { @Override public void onDownloadComplete() { isDownloaded = true; Log.e(TAG, "onDownloadComplete: Completed"); } @Override public void onError(Error error) { isDownloaded = true; } }); /** Wait till download is complete... */ Log.e(TAG, "mp3Task: Download Running"); do { LOG("isDownloaded="+isDownloaded);} while (!isDownloaded); LOG("Download Completed"); YTMeta ytMeta = new YTMeta(context, currentModel.getVideoID()); Log.e(TAG, "mp3Task: Extension="+currentModel.getExt()); switch (currentModel.getExt()) { case "m4a": processM4A(ytMeta,f,dst); /*dst = YTutils.getFile(Environment.DIRECTORY_DOWNLOADS + "/" + target); try { YTutils.moveFile(f, YTutils.getFile(Environment.DIRECTORY_DOWNLOADS + "/" + target)); } catch (IOException e) { e.printStackTrace(); }*/ break; case "mp3": /** Run ffmpeg... */ LOG("Executing FFMPEG"); isDownloaded = false; new AsyncTask<File,Void,Void>() { @Override protected void onPostExecute(Void aVoid) { isDownloaded=true; super.onPostExecute(aVoid); } @Override protected Void doInBackground(File... files) { LOG("Running FFMPEG now..."); String[] cmd = new String[]{"-i", files[0].getPath(), "-y" ,files[1].getPath()}; int rc = FFmpeg.execute(cmd); processRC(rc); return null; } }.execute(f,mp3); try { /** Wait for ffmpeg ffmpeg... */ do { if (mp3.exists()) { try { Thread.sleep(550); } catch (Exception e) { } currentsize = mp3.length() + f.length(); setProgress((int) (currentsize * 100 / totalsize), false); } } while (!isDownloaded); } catch (Exception e) { LOG("Error: " + e.getMessage()); e.printStackTrace(); } LOG("FFMPEG Run complete"); /** Set mp3 tags... */ setID3Tags(ytMeta,mp3,dst,imageUri); if (!dst.exists()) { LOG("Overriding defaults"); File sf = new File(dst.getPath().replace(".mp3", ".m4a")); dst = sf; f.renameTo(sf); Toast.makeText(context, "Failed to convert to mp3, overriding defaults!", Toast.LENGTH_SHORT).show(); } addFiletoLocalDevice(dst, currentModel); break; } /** Show notification... */ setFinalNotification(dst); if (f.exists()) f.delete(); if (mp3.exists()) mp3.delete(); } catch (Exception e) { e.printStackTrace(); LOG("Error: " + e.getMessage()); } } private void setID3Tags(YTMeta ytMeta,File mp3, File dst, String imageUri) { MusicMetadataSet src_set = null; try { src_set = new MyID3().read(mp3); } catch (IOException e1) { e1.printStackTrace(); } if (src_set == null) { Log.i("NULL", "NULL"); mp3.renameTo(dst); } else { URL uri = null; ImageData imageData = null; try { Log.e(TAG, "doInBackground: ImageUri: " + imageUri); uri = new URL(imageUri); Bitmap bitmap = BitmapFactory.decodeStream(uri.openConnection().getInputStream()); ByteArrayOutputStream stream = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream); byte[] bitmapdata = stream.toByteArray(); imageData = new ImageData(bitmapdata, "image/jpeg", "background", 1); } catch (Exception e) { e.printStackTrace(); } MusicMetadata meta = new MusicMetadata(YTutils.getVideoTitle(currentModel.getTitle())); if (imageData != null) { meta.addPicture(imageData); } meta.setAlbum(ytMeta.getVideMeta().getAuthor()); meta.setArtist(YTutils.getChannelTitle(currentModel.getTitle(), currentModel.getChannelTitle())); try { new MyID3().write(mp3, dst, src_set, meta); LOG("MP3 Saved!"); mp3.delete(); } catch (Exception e) { e.printStackTrace(); } } } @SuppressLint("StaticFieldLeak") private void processM4A(YTMeta ytMeta, File f, File dst) { /** Add meta data tags to m4a.. */ Log.e(TAG, "processM4A: Processing: "+f.getPath()+", Dest: "+dst.getPath() ); isDownloaded=false; new AsyncTask<File,Void,Void>() { @Override protected void onPostExecute(Void aVoid) { isDownloaded=true; super.onPostExecute(aVoid); } @Override protected Void doInBackground(File... files) { String album = ytMeta.getVideMeta().getAuthor(); String artist = YTutils.getChannelTitle(currentModel.getTitle(), currentModel.getChannelTitle()); String[] command = new String[] {"-i",files[0].getPath(),"-metadata","artist="+artist, "-metadata","album="+album,"-y",files[1].getPath()}; int rc = FFmpeg.execute(command); processRC(rc); return null; } }.execute(f,dst); try { /** Wait for ffmpeg ffmpeg... */ do { if (dst.exists()) { try { Thread.sleep(550); } catch (Exception e) { } currentsize = dst.length() + f.length(); setProgress((int) (currentsize * 100 / totalsize), false); } } while (!isDownloaded); } catch (Exception e) { LOG("Error: " + e.getMessage()); e.printStackTrace(); } addFiletoLocalDevice(dst,currentModel); //do { LOG("isDownload="+isDownloaded); }while (!isDownloaded); } private void processRC(int rc) { if (rc == RETURN_CODE_SUCCESS) { Log.e(Config.TAG, "Command execution completed successfully."); } else if (rc == RETURN_CODE_CANCEL) { Log.e(Config.TAG, "Command execution cancelled by user."); } else { Log.e(Config.TAG, String.format("Command execution failed with rc=%d and the output below.", rc)); } } @Override public void onTaskRemoved(Intent rootIntent) { LOG("Task removed"); super.onTaskRemoved(rootIntent); } @Override public void onDestroy() { LOG("ON Destroyed"); FFmpeg.cancel(); PRDownloader.cancelAll(); PRDownloader.shutDown(); isDownloaded=true; pendingJobs.clear(); totalsize=0; currentsize=0; progress=0; currentModel = null; handler.removeCallbacks(updateNotificationTask); notificationManagerCompat.cancel(FOREGROUND_ID); wakeLock.release(); super.onDestroy(); } private Runnable updateNotificationTask = new Runnable() { @Override public void run() { LOG("Running... " + progress); if (currentModel == null) return; boolean indeterminate = false; if (progress == -1) indeterminate = true; notification = new NotificationCompat.Builder(context, CHANNEL_ID) .setContentTitle("Download - " + currentModel.getTitle()) .addAction(R.mipmap.ic_launcher, "Cancel", cancelIntent) .setContentText(pendingJobs.size() + 1 + " files downloading") .setSmallIcon(android.R.drawable.stat_sys_download) .setLargeIcon(icon) .setContentIntent(contentIntent) .setProgress(100, progress, indeterminate) .setOngoing(true) .setPriority(Notification.PRIORITY_LOW) .build(); notificationManagerCompat.notify(FOREGROUND_ID, notification); handler.postDelayed(this, 1000); } }; private void mergeTask() { File audio = YTutils.getFile("/YTPlayer/audio.m4a"); if (audio.exists()) audio.delete(); File video = YTutils.getFile("/YTPlayer/video.mp4"); if (video.exists()) video.delete(); try { isDownloaded=false; String imageUri; if (currentModel.getVideoID().contains("soundcloud.com")) imageUri = currentModel.getImageUrl(); else imageUri = YTutils.getImageUrlID_MAX(currentModel.getVideoID()); Glide.with(context.getApplicationContext()).asBitmap().load(imageUri).into(new CustomTarget<Bitmap>() { @Override public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) { icon = resource; setProgress(0, true); } @Override public void onLoadCleared(@Nullable Drawable placeholder) { } }); String audioUrl = currentModel.getAudioUrl(); String videoUrl = currentModel.getUrl(); /** Calculate total file size... */ URL url = new URL(videoUrl); URLConnection connection = url.openConnection(); connection.connect(); long fileLength = connection.getContentLength(); /** Download audio file first... */ url = new URL(audioUrl); connection = url.openConnection(); connection.connect(); fileLength += connection.getContentLength(); totalsize = fileLength; PRDownloader.download(videoUrl, YTutils.getFile("YTPlayer").getPath(), "video.mp4") .build() .setOnProgressListener(progress -> { currentsize = progress.currentBytes; if (totalsize == 0) return; setProgress((int) (progress.currentBytes * 100 / totalsize), false); oldbytes = currentsize; }) .start(new OnDownloadListener() { @Override public void onDownloadComplete() { Log.e(TAG, "onDownloadComplete: Audio Download Complete"); /** Download video file now... */ PRDownloader.download(audioUrl, YTutils.getFile("YTPlayer").getPath(), "audio.m4a") .build() .setOnProgressListener(progress1 -> { currentsize = oldbytes + progress1.currentBytes; setProgress((int) ((progress1.currentBytes + oldbytes) * 100 / totalsize), false); }) .start(new OnDownloadListener() { @Override public void onDownloadComplete() { Log.e(TAG, "onDownloadComplete: Video Download Complete"); /* muxing(YTutils.getFile("YTPlayer/video.mp4").getPath(), YTutils.getFile("YTPlayer/audio.m4a").getPath(), save.getPath());*/ /** Show notification... */ isDownloaded = true; } @Override public void onError(Error error) { isDownloaded = true; } }); } @Override public void onError(Error error) { isDownloaded = true; } }); do { LOG("isDownload="+isDownloaded); } while (!isDownloaded); File save = YTutils.getFile(Environment.DIRECTORY_DOWNLOADS + "/" + currentModel.getTargetName() + "." + currentModel.getExt()); if (save.exists()) save.delete(); if (useFFMPEGmuxer) { mux_ffmpeg(YTutils.getFile("YTPlayer/video.mp4").getPath(), YTutils.getFile("YTPlayer/audio.m4a").getPath(), save.getPath()); }else { mux(YTutils.getFile("YTPlayer/video.mp4").getPath(), YTutils.getFile("YTPlayer/audio.m4a").getPath(), save.getPath()); } setFinalNotification(save); } catch (Exception e) { e.printStackTrace(); Log.e(TAG, "Error: " + e.getMessage()); } IntentDownloadService.currentsize=0; IntentDownloadService.totalsize=0; IntentDownloadService.progress=0; IntentDownloadService.currentModel = null; isDownloaded=false; if (video.exists()) video.delete(); if (audio.exists()) audio.delete(); Log.e(TAG, "doInBackground: Task Finished"); } private void muxing(String videoFile, String audioFile, String outFile) { String outputFile = ""; try { File file = new File(outFile); file.createNewFile(); outputFile = file.getAbsolutePath(); MediaExtractor videoExtractor = new MediaExtractor(); videoExtractor.setDataSource(videoFile); MediaExtractor audioExtractor = new MediaExtractor(); audioExtractor.setDataSource(audioFile); MediaMuxer muxer = new MediaMuxer(outputFile, 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 frameCount = 0; 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(); frameCount++; } } // Toast.makeText(getApplicationContext() , , Toast.LENGTH_SHORT).show(); LOG("frame:" + frameCount); boolean sawEOS2 = false; int frameCount2 = 0; while (!sawEOS2) { frameCount2++; audioBufferInfo.offset = offset; audioBufferInfo.size = audioExtractor.readSampleData(audioBuf, offset); if (videoBufferInfo.size < 0 || audioBufferInfo.size < 0) { // Log.d(TAG, "saw input EOS."); sawEOS2 = true; audioBufferInfo.size = 0; } else { audioBufferInfo.presentationTimeUs = audioExtractor.getSampleTime(); audioBufferInfo.flags = audioExtractor.getSampleFlags(); muxer.writeSampleData(audioTrack, audioBuf, audioBufferInfo); audioExtractor.advance(); } } LOG("frame:" + frameCount2); muxer.stop(); muxer.release(); } catch (IOException e) { Log.d(TAG, "Mixer Error 1 " + e.getMessage()); } catch (Exception e) { Log.d(TAG, "Mixer Error 2 " + e.getMessage()); } } @SuppressLint("StaticFieldLeak") public boolean mux_ffmpeg(String videoFile, String audioFile, String outputFile) { isDownloaded = false; File video = new File(videoFile); File audio = new File(audioFile); File outFile = new File(outputFile); new AsyncTask<File,Void,Void>() { @Override protected void onPostExecute(Void aVoid) { isDownloaded=true; super.onPostExecute(aVoid); } @Override protected Void doInBackground(File... files) { // ffmpeg -i audio.acc -i video.h264 -c:v copy -c:a copy -f mp4 -y out.mp4 String[] cmd = new String[]{"-i", files[0].getPath(),"-i" ,files[1].getPath(),"-c:v","copy","-c:a","copy","-f","mp4","-y",files[2].getPath()}; int rc = FFmpeg.execute(cmd); processRC(rc); return null; } }.execute(video,audio,outFile); do { Log.e(TAG, "isDownloaded="+isDownloaded ); } while (!isDownloaded); return outFile.exists(); } public boolean mux(String videoFile, String audioFile, String outputFile) { try { if (!new File(videoFile).exists()) return false; if (!new File(audioFile).exists()) return false; }catch (Exception e) { return false; } Movie video; try { video = MovieCreator.build(videoFile); } catch (RuntimeException | IOException e) { e.printStackTrace(); return false; } Movie audio; try { audio = MovieCreator.build(audioFile); } catch (IOException e) { LOG("IO Exception"); e.printStackTrace(); return false; } catch (NullPointerException e) { LOG("Null Exception"); e.printStackTrace(); return false; } Track audioTrack = audio.getTracks().get(0); video.addTrack(audioTrack); Container out = new DefaultMp4Builder().build(video); FileOutputStream fos; try { fos = new FileOutputStream(outputFile); } catch (FileNotFoundException e) { e.printStackTrace(); return false; } BufferedWritableFileByteChannel byteBufferByteChannel = new BufferedWritableFileByteChannel(fos); try { out.writeContainer(byteBufferByteChannel); byteBufferByteChannel.close(); fos.close(); } catch (IOException e) { e.printStackTrace(); return false; } return true; } class BufferedWritableFileByteChannel implements WritableByteChannel { // private static final int BUFFER_CAPACITY = 1000000; private static final int BUFFER_CAPACITY = 10000000; private boolean isOpen = true; private final OutputStream outputStream; private final ByteBuffer byteBuffer; private final byte[] rawBuffer = new byte[BUFFER_CAPACITY]; private void dumpToFile() { try { outputStream.write(rawBuffer, 0, byteBuffer.position()); } catch (IOException e) { throw new RuntimeException(e); } } private BufferedWritableFileByteChannel(OutputStream outputStream) { this.outputStream = outputStream; this.byteBuffer = ByteBuffer.wrap(rawBuffer); } @Override public int write(ByteBuffer inputBuffer) { int inputBytes = inputBuffer.remaining(); if (inputBytes > byteBuffer.remaining()) { dumpToFile(); byteBuffer.clear(); if (inputBytes > byteBuffer.remaining()) { throw new BufferOverflowException(); } } byteBuffer.put(inputBuffer); return inputBytes; } @Override public boolean isOpen() { return isOpen; } @Override public void close() throws IOException { dumpToFile(); isOpen = false; } } }