Merge branch 'dev' into unhook-save-restore
This commit is contained in:
commit
4b8474b0ac
67 changed files with 432 additions and 282 deletions
|
|
@ -1,20 +1,25 @@
|
|||
package org.schabi.newpipe;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import androidx.core.app.NavUtils;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/*
|
||||
* Created by beneth <bmauduit@beneth.fr> on 06.12.16.
|
||||
*
|
||||
|
|
@ -37,48 +42,46 @@ import android.webkit.WebViewClient;
|
|||
public class ReCaptchaActivity extends AppCompatActivity {
|
||||
public static final int RECAPTCHA_REQUEST = 10;
|
||||
public static final String RECAPTCHA_URL_EXTRA = "recaptcha_url_extra";
|
||||
|
||||
public static final String TAG = ReCaptchaActivity.class.toString();
|
||||
public static final String YT_URL = "https://www.youtube.com";
|
||||
|
||||
private String url;
|
||||
private WebView webView;
|
||||
private String foundCookies = "";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
ThemeHelper.setTheme(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_recaptcha);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
url = getIntent().getStringExtra(RECAPTCHA_URL_EXTRA);
|
||||
String url = getIntent().getStringExtra(RECAPTCHA_URL_EXTRA);
|
||||
if (url == null || url.isEmpty()) {
|
||||
url = YT_URL;
|
||||
}
|
||||
|
||||
|
||||
// Set return to Cancel by default
|
||||
// set return to Cancel by default
|
||||
setResult(RESULT_CANCELED);
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
actionBar.setTitle(R.string.reCaptcha_title);
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
}
|
||||
webView = findViewById(R.id.reCaptchaWebView);
|
||||
|
||||
WebView myWebView = findViewById(R.id.reCaptchaWebView);
|
||||
|
||||
// Enable Javascript
|
||||
WebSettings webSettings = myWebView.getSettings();
|
||||
// enable Javascript
|
||||
WebSettings webSettings = webView.getSettings();
|
||||
webSettings.setJavaScriptEnabled(true);
|
||||
|
||||
ReCaptchaWebViewClient webClient = new ReCaptchaWebViewClient(this);
|
||||
myWebView.setWebViewClient(webClient);
|
||||
webView.setWebViewClient(new WebViewClient() {
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
super.onPageFinished(view, url);
|
||||
handleCookies(url);
|
||||
}
|
||||
});
|
||||
|
||||
// Cleaning cache, history and cookies from webView
|
||||
myWebView.clearCache(true);
|
||||
myWebView.clearHistory();
|
||||
// cleaning cache, history and cookies from webView
|
||||
webView.clearCache(true);
|
||||
webView.clearHistory();
|
||||
android.webkit.CookieManager cookieManager = CookieManager.getInstance();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
cookieManager.removeAllCookies(aBoolean -> {});
|
||||
|
|
@ -86,77 +89,82 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
|||
cookieManager.removeAllCookie();
|
||||
}
|
||||
|
||||
myWebView.loadUrl(url);
|
||||
webView.loadUrl(url);
|
||||
}
|
||||
|
||||
private class ReCaptchaWebViewClient extends WebViewClient {
|
||||
private final Activity context;
|
||||
private String mCookies;
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.menu_recaptcha, menu);
|
||||
|
||||
ReCaptchaWebViewClient(Activity ctx) {
|
||||
context = ctx;
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setDisplayHomeAsUpEnabled(false);
|
||||
actionBar.setTitle(R.string.title_activity_recaptcha);
|
||||
actionBar.setSubtitle(R.string.subtitle_activity_recaptcha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
||||
// TODO: Start Loader
|
||||
super.onPageStarted(view, url, favicon);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
String cookies = CookieManager.getInstance().getCookie(url);
|
||||
|
||||
// TODO: Stop Loader
|
||||
|
||||
// find cookies : s_gl & goojf and Add cookies to Downloader
|
||||
if (find_access_cookies(cookies)) {
|
||||
// Give cookies to Downloader class
|
||||
DownloaderImpl.getInstance().setCookies(mCookies);
|
||||
|
||||
// Closing activity and return to parent
|
||||
setResult(RESULT_OK);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean find_access_cookies(String cookies) {
|
||||
boolean ret = false;
|
||||
String c_s_gl = "";
|
||||
String c_goojf = "";
|
||||
|
||||
String[] parts = cookies.split("; ");
|
||||
for (String part : parts) {
|
||||
if (part.trim().startsWith("s_gl")) {
|
||||
c_s_gl = part.trim();
|
||||
}
|
||||
if (part.trim().startsWith("goojf")) {
|
||||
c_goojf = part.trim();
|
||||
}
|
||||
}
|
||||
if (c_s_gl.length() > 0 && c_goojf.length() > 0) {
|
||||
ret = true;
|
||||
//mCookies = c_s_gl + "; " + c_goojf;
|
||||
// Youtube seems to also need the other cookies:
|
||||
mCookies = cookies;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
saveCookiesAndFinish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
switch (id) {
|
||||
case android.R.id.home: {
|
||||
Intent intent = new Intent(this, org.schabi.newpipe.MainActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
NavUtils.navigateUpTo(this, intent);
|
||||
case R.id.menu_item_done:
|
||||
saveCookiesAndFinish();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void saveCookiesAndFinish() {
|
||||
handleCookies(webView.getUrl()); // try to get cookies of unclosed page
|
||||
if (!foundCookies.isEmpty()) {
|
||||
// give cookies to Downloader class
|
||||
DownloaderImpl.getInstance().setCookies(foundCookies);
|
||||
setResult(RESULT_OK);
|
||||
}
|
||||
|
||||
Intent intent = new Intent(this, org.schabi.newpipe.MainActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
NavUtils.navigateUpTo(this, intent);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void handleCookies(String url) {
|
||||
String cookies = CookieManager.getInstance().getCookie(url);
|
||||
if (MainActivity.DEBUG) Log.d(TAG, "handleCookies: url=" + url + "; cookies=" + (cookies == null ? "null" : cookies));
|
||||
if (cookies == null) return;
|
||||
|
||||
addYoutubeCookies(cookies);
|
||||
// add other methods to extract cookies here
|
||||
}
|
||||
|
||||
private void addYoutubeCookies(@Nonnull String cookies) {
|
||||
if (cookies.contains("s_gl=") || cookies.contains("goojf=") || cookies.contains("VISITOR_INFO1_LIVE=")) {
|
||||
// youtube seems to also need the other cookies:
|
||||
addCookie(cookies);
|
||||
}
|
||||
}
|
||||
|
||||
private void addCookie(String cookie) {
|
||||
if (foundCookies.contains(cookie)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (foundCookies.isEmpty() || foundCookies.endsWith("; ")) {
|
||||
foundCookies += cookie;
|
||||
} else if (foundCookies.endsWith(";")) {
|
||||
foundCookies += " " + cookie;
|
||||
} else {
|
||||
foundCookies += "; " + cookie;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,18 +32,20 @@ public class AboutActivity extends AppCompatActivity {
|
|||
* List of all software components
|
||||
*/
|
||||
private static final SoftwareComponent[] SOFTWARE_COMPONENTS = new SoftwareComponent[]{
|
||||
new SoftwareComponent("Giga Get", "2014", "Peter Cai", "https://github.com/PaperAirplane-Dev-Team/GigaGet", StandardLicenses.GPL2),
|
||||
new SoftwareComponent("NewPipe Extractor", "2017", "Christian Schabesberger", "https://github.com/TeamNewPipe/NewPipeExtractor", StandardLicenses.GPL3),
|
||||
new SoftwareComponent("Giga Get", "2014 - 2015", "Peter Cai", "https://github.com/PaperAirplane-Dev-Team/GigaGet", StandardLicenses.GPL2),
|
||||
new SoftwareComponent("NewPipe Extractor", "2017 - 2020", "Christian Schabesberger", "https://github.com/TeamNewPipe/NewPipeExtractor", StandardLicenses.GPL3),
|
||||
new SoftwareComponent("Jsoup", "2017", "Jonathan Hedley", "https://github.com/jhy/jsoup", StandardLicenses.MIT),
|
||||
new SoftwareComponent("Rhino", "2015", "Mozilla", "https://www.mozilla.org/rhino/", StandardLicenses.MPL2),
|
||||
new SoftwareComponent("ACRA", "2013", "Kevin Gaudin", "http://www.acra.ch", StandardLicenses.APACHE2),
|
||||
new SoftwareComponent("Universal Image Loader", "2011 - 2015", "Sergey Tarasevich", "https://github.com/nostra13/Android-Universal-Image-Loader", StandardLicenses.APACHE2),
|
||||
new SoftwareComponent("CircleImageView", "2014 - 2017", "Henning Dodenhof", "https://github.com/hdodenhof/CircleImageView", StandardLicenses.APACHE2),
|
||||
new SoftwareComponent("CircleImageView", "2014 - 2020", "Henning Dodenhof", "https://github.com/hdodenhof/CircleImageView", StandardLicenses.APACHE2),
|
||||
new SoftwareComponent("NoNonsense-FilePicker", "2016", "Jonas Kalderstam", "https://github.com/spacecowboy/NoNonsense-FilePicker", StandardLicenses.MPL2),
|
||||
new SoftwareComponent("ExoPlayer", "2014-2017", "Google Inc", "https://github.com/google/ExoPlayer", StandardLicenses.APACHE2),
|
||||
new SoftwareComponent("RxAndroid", "2015", "The RxAndroid authors", "https://github.com/ReactiveX/RxAndroid", StandardLicenses.APACHE2),
|
||||
new SoftwareComponent("RxJava", "2016-present", "RxJava Contributors", "https://github.com/ReactiveX/RxJava", StandardLicenses.APACHE2),
|
||||
new SoftwareComponent("RxBinding", "2015", "Jake Wharton", "https://github.com/JakeWharton/RxBinding", StandardLicenses.APACHE2)
|
||||
new SoftwareComponent("ExoPlayer", "2014 - 2020", "Google Inc", "https://github.com/google/ExoPlayer", StandardLicenses.APACHE2),
|
||||
new SoftwareComponent("RxAndroid", "2015 - 2018", "The RxAndroid authors", "https://github.com/ReactiveX/RxAndroid", StandardLicenses.APACHE2),
|
||||
new SoftwareComponent("RxJava", "2016 - 2020", "RxJava Contributors", "https://github.com/ReactiveX/RxJava", StandardLicenses.APACHE2),
|
||||
new SoftwareComponent("RxBinding", "2015 - 2018", "Jake Wharton", "https://github.com/JakeWharton/RxBinding", StandardLicenses.APACHE2),
|
||||
new SoftwareComponent("PrettyTime", "2012 - 2020", "Lincoln Baxter, III", "https://github.com/ocpsoft/prettytime", StandardLicenses.APACHE2),
|
||||
new SoftwareComponent("Markwon", "2017 - 2020", "Noties", "https://github.com/noties/Markwon", StandardLicenses.APACHE2)
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package org.schabi.newpipe.fragments.detail;
|
|||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
|
|
@ -18,7 +17,6 @@ import androidx.fragment.app.Fragment;
|
|||
import androidx.core.content.ContextCompat;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.text.Html;
|
||||
import android.text.Spanned;
|
||||
|
|
@ -58,6 +56,7 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
|||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.Description;
|
||||
import org.schabi.newpipe.extractor.stream.Stream;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
|
|
@ -96,6 +95,8 @@ import java.util.List;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import icepick.State;
|
||||
import io.noties.markwon.Markwon;
|
||||
import io.noties.markwon.linkify.LinkifyPlugin;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
|
|
@ -483,7 +484,6 @@ public class VideoDetailFragment
|
|||
videoUploadDateView = rootView.findViewById(R.id.detail_upload_date_view);
|
||||
videoDescriptionView = rootView.findViewById(R.id.detail_description_view);
|
||||
videoDescriptionView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
videoDescriptionView.setAutoLinkMask(Linkify.WEB_URLS);
|
||||
|
||||
thumbsUpTextView = rootView.findViewById(R.id.detail_thumbs_up_count_view);
|
||||
thumbsUpImageView = rootView.findViewById(R.id.detail_thumbs_up_img_view);
|
||||
|
|
@ -919,28 +919,41 @@ public class VideoDetailFragment
|
|||
return sortedVideoStreams != null ? sortedVideoStreams.get(selectedVideoStreamIndex) : null;
|
||||
}
|
||||
|
||||
private void prepareDescription(final String descriptionHtml) {
|
||||
if (TextUtils.isEmpty(descriptionHtml)) {
|
||||
private void prepareDescription(Description description) {
|
||||
if (TextUtils.isEmpty(description.getContent()) || description == Description.emptyDescription) {
|
||||
return;
|
||||
}
|
||||
|
||||
disposables.add(Single.just(descriptionHtml)
|
||||
.map((@io.reactivex.annotations.NonNull String description) -> {
|
||||
Spanned parsedDescription;
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
parsedDescription = Html.fromHtml(description, 0);
|
||||
} else {
|
||||
//noinspection deprecation
|
||||
parsedDescription = Html.fromHtml(description);
|
||||
}
|
||||
return parsedDescription;
|
||||
})
|
||||
.subscribeOn(Schedulers.computation())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe((@io.reactivex.annotations.NonNull Spanned spanned) -> {
|
||||
videoDescriptionView.setText(spanned);
|
||||
videoDescriptionView.setVisibility(View.VISIBLE);
|
||||
}));
|
||||
if (description.getType() == Description.HTML) {
|
||||
disposables.add(Single.just(description.getContent())
|
||||
.map((@io.reactivex.annotations.NonNull String descriptionText) -> {
|
||||
Spanned parsedDescription;
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
parsedDescription = Html.fromHtml(descriptionText, 0);
|
||||
} else {
|
||||
//noinspection deprecation
|
||||
parsedDescription = Html.fromHtml(descriptionText);
|
||||
}
|
||||
return parsedDescription;
|
||||
})
|
||||
.subscribeOn(Schedulers.computation())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe((@io.reactivex.annotations.NonNull Spanned spanned) -> {
|
||||
videoDescriptionView.setText(spanned);
|
||||
videoDescriptionView.setVisibility(View.VISIBLE);
|
||||
}));
|
||||
} else if (description.getType() == Description.MARKDOWN) {
|
||||
final Markwon markwon = Markwon.builder(getContext())
|
||||
.usePlugin(LinkifyPlugin.create())
|
||||
.build();
|
||||
markwon.setMarkdown(videoDescriptionView, description.getContent());
|
||||
videoDescriptionView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
//== Description.PLAIN_TEXT
|
||||
videoDescriptionView.setAutoLinkMask(Linkify.WEB_URLS);
|
||||
videoDescriptionView.setText(description.getContent(), TextView.BufferType.SPANNABLE);
|
||||
videoDescriptionView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void setHeightThumbnail() {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
package org.schabi.newpipe.info_list.holder;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.text.util.Linkify;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import org.jsoup.helper.StringUtil;
|
||||
import org.schabi.newpipe.R;
|
||||
|
|
@ -120,6 +126,21 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
|||
itemBuilder.getOnCommentsSelectedListener().selected(item);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
itemView.setOnLongClickListener(new View.OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View view) {
|
||||
|
||||
ClipboardManager clipboardManager = (ClipboardManager) itemBuilder.getContext()
|
||||
.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
clipboardManager.setPrimaryClip(ClipData.newPlainText(null,commentText));
|
||||
Toast.makeText(itemBuilder.getContext(), R.string.msg_copied, Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void ellipsize() {
|
||||
|
|
|
|||
|
|
@ -137,6 +137,7 @@ public class DataReader {
|
|||
|
||||
position = 0;
|
||||
readOffset = readBuffer.length;
|
||||
readCount = 0;
|
||||
}
|
||||
|
||||
public boolean canRewind() {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import org.schabi.newpipe.streams.io.SharpStream;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* @author kapodamy
|
||||
|
|
@ -23,7 +24,6 @@ public class Mp4FromDashWriter {
|
|||
private final static byte SAMPLES_PER_CHUNK = 6;// ffmpeg uses 2, basic uses 1 (with 60fps uses 21 or 22). NewPipe will use 6
|
||||
private final static long THRESHOLD_FOR_CO64 = 0xFFFEFFFFL;// near 3.999 GiB
|
||||
private final static int THRESHOLD_MOOV_LENGTH = (256 * 1024) + (2048 * 1024); // 2.2 MiB enough for: 1080p 60fps 00h35m00s
|
||||
private final static short SINGLE_CHUNK_SAMPLE_BUFFER = 256;
|
||||
|
||||
private final long time;
|
||||
|
||||
|
|
@ -46,6 +46,8 @@ public class Mp4FromDashWriter {
|
|||
|
||||
private int overrideMainBrand = 0x00;
|
||||
|
||||
private ArrayList<Integer> compatibleBrands = new ArrayList<>(5);
|
||||
|
||||
public Mp4FromDashWriter(SharpStream... sources) throws IOException {
|
||||
for (SharpStream src : sources) {
|
||||
if (!src.canRewind() && !src.canRead()) {
|
||||
|
|
@ -57,6 +59,10 @@ public class Mp4FromDashWriter {
|
|||
readers = new Mp4DashReader[sourceTracks.length];
|
||||
readersChunks = new Mp4DashChunk[readers.length];
|
||||
time = (System.currentTimeMillis() / 1000L) + EPOCH_OFFSET;
|
||||
|
||||
compatibleBrands.add(0x6D703431);// mp41
|
||||
compatibleBrands.add(0x69736F6D);// isom
|
||||
compatibleBrands.add(0x69736F32);// iso2
|
||||
}
|
||||
|
||||
public Mp4Track[] getTracksFromSource(int sourceIndex) throws IllegalStateException {
|
||||
|
|
@ -104,8 +110,8 @@ public class Mp4FromDashWriter {
|
|||
}
|
||||
}
|
||||
|
||||
public void setMainBrand(int brandId) {
|
||||
overrideMainBrand = brandId;
|
||||
public void setMainBrand(int brand) {
|
||||
overrideMainBrand = brand;
|
||||
}
|
||||
|
||||
public boolean isDone() {
|
||||
|
|
@ -159,7 +165,13 @@ public class Mp4FromDashWriter {
|
|||
tablesInfo[i] = new TablesInfo();
|
||||
}
|
||||
|
||||
boolean singleChunk = tracks.length == 1 && tracks[0].kind == TrackKind.Audio;
|
||||
int single_sample_buffer;
|
||||
if (tracks.length == 1 && tracks[0].kind == TrackKind.Audio) {
|
||||
// near 1 second of audio data per chunk, avoid split the audio stream in large chunks
|
||||
single_sample_buffer = tracks[0].trak.mdia.mdhd_timeScale / 1000;
|
||||
} else {
|
||||
single_sample_buffer = -1;
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < readers.length; i++) {
|
||||
|
|
@ -210,31 +222,10 @@ public class Mp4FromDashWriter {
|
|||
|
||||
readers[i].rewind();
|
||||
|
||||
int tmp = tablesInfo[i].stsz - SAMPLES_PER_CHUNK_INIT;
|
||||
tablesInfo[i].stco = (tmp / SAMPLES_PER_CHUNK) + 1;// +1 for samples in first chunk
|
||||
|
||||
tmp = tmp % SAMPLES_PER_CHUNK;
|
||||
if (singleChunk) {
|
||||
// avoid split audio streams in chunks
|
||||
tablesInfo[i].stsc = 1;
|
||||
tablesInfo[i].stsc_bEntries = new int[]{
|
||||
1, tablesInfo[i].stsz, 1
|
||||
};
|
||||
tablesInfo[i].stco = 1;
|
||||
} else if (tmp == 0) {
|
||||
tablesInfo[i].stsc = 2;// first chunk (init) and succesive chunks
|
||||
tablesInfo[i].stsc_bEntries = new int[]{
|
||||
1, SAMPLES_PER_CHUNK_INIT, 1,
|
||||
2, SAMPLES_PER_CHUNK, 1
|
||||
};
|
||||
if (single_sample_buffer > 0) {
|
||||
initChunkTables(tablesInfo[i], single_sample_buffer, single_sample_buffer);
|
||||
} else {
|
||||
tablesInfo[i].stsc = 3;// first chunk (init) and successive chunks and remain chunk
|
||||
tablesInfo[i].stsc_bEntries = new int[]{
|
||||
1, SAMPLES_PER_CHUNK_INIT, 1,
|
||||
2, SAMPLES_PER_CHUNK, 1,
|
||||
tablesInfo[i].stco + 1, tmp, 1
|
||||
};
|
||||
tablesInfo[i].stco++;
|
||||
initChunkTables(tablesInfo[i], SAMPLES_PER_CHUNK_INIT, SAMPLES_PER_CHUNK);
|
||||
}
|
||||
|
||||
sampleCount[i] = tablesInfo[i].stsz;
|
||||
|
|
@ -259,7 +250,7 @@ public class Mp4FromDashWriter {
|
|||
|
||||
boolean is64 = read > THRESHOLD_FOR_CO64;
|
||||
|
||||
// calculate the moov size;
|
||||
// calculate the moov size
|
||||
int auxSize = make_moov(defaultMediaTime, tablesInfo, is64);
|
||||
|
||||
if (auxSize < THRESHOLD_MOOV_LENGTH) {
|
||||
|
|
@ -272,11 +263,6 @@ public class Mp4FromDashWriter {
|
|||
final int ftyp_size = make_ftyp();
|
||||
|
||||
// reserve moov space in the output stream
|
||||
/*if (outStream.canSetLength()) {
|
||||
long length = writeOffset + auxSize;
|
||||
outStream.setLength(length);
|
||||
outSeek(length);
|
||||
} else {*/
|
||||
if (auxSize > 0) {
|
||||
int length = auxSize;
|
||||
byte[] buffer = new byte[64 * 1024];// 64 KiB
|
||||
|
|
@ -292,10 +278,10 @@ public class Mp4FromDashWriter {
|
|||
}
|
||||
|
||||
// tablesInfo contains row counts
|
||||
// and after returning from make_moov() will contain table offsets
|
||||
// and after returning from make_moov() will contain those table offsets
|
||||
make_moov(defaultMediaTime, tablesInfo, is64);
|
||||
|
||||
// write tables: stts stsc
|
||||
// write tables: stts stsc sbgp
|
||||
// reset for ctts table: sampleCount sampleExtra
|
||||
for (int i = 0; i < readers.length; i++) {
|
||||
writeEntryArray(tablesInfo[i].stts, 2, sampleCount[i], defaultSampleDuration[i]);
|
||||
|
|
@ -305,6 +291,7 @@ public class Mp4FromDashWriter {
|
|||
sampleCount[i] = 1;// the index is not base zero
|
||||
sampleExtra[i] = -1;
|
||||
}
|
||||
writeEntryArray(tablesInfo[i].sbgp, 1, sampleCount[i]);
|
||||
}
|
||||
|
||||
if (auxBuffer == null) {
|
||||
|
|
@ -314,8 +301,8 @@ public class Mp4FromDashWriter {
|
|||
outWrite(make_mdat(totalSampleSize, is64));
|
||||
|
||||
int[] sampleIndex = new int[readers.length];
|
||||
int[] sizes = new int[singleChunk ? SINGLE_CHUNK_SAMPLE_BUFFER : SAMPLES_PER_CHUNK];
|
||||
int[] sync = new int[singleChunk ? SINGLE_CHUNK_SAMPLE_BUFFER : SAMPLES_PER_CHUNK];
|
||||
int[] sizes = new int[single_sample_buffer > 0 ? single_sample_buffer : SAMPLES_PER_CHUNK];
|
||||
int[] sync = new int[single_sample_buffer > 0 ? single_sample_buffer : SAMPLES_PER_CHUNK];
|
||||
|
||||
int written = readers.length;
|
||||
while (written > 0) {
|
||||
|
|
@ -329,8 +316,8 @@ public class Mp4FromDashWriter {
|
|||
long chunkOffset = writeOffset;
|
||||
int syncCount = 0;
|
||||
int limit;
|
||||
if (singleChunk) {
|
||||
limit = SINGLE_CHUNK_SAMPLE_BUFFER;
|
||||
if (single_sample_buffer > 0) {
|
||||
limit = single_sample_buffer;
|
||||
} else {
|
||||
limit = sampleIndex[i] == 0 ? SAMPLES_PER_CHUNK_INIT : SAMPLES_PER_CHUNK;
|
||||
}
|
||||
|
|
@ -342,6 +329,7 @@ public class Mp4FromDashWriter {
|
|||
if (sample == null) {
|
||||
if (tablesInfo[i].ctts > 0 && sampleExtra[i] >= 0) {
|
||||
writeEntryArray(tablesInfo[i].ctts, 1, sampleCount[i], sampleExtra[i]);// flush last entries
|
||||
outRestore();
|
||||
}
|
||||
sampleIndex[i] = -1;
|
||||
break;
|
||||
|
|
@ -390,10 +378,6 @@ public class Mp4FromDashWriter {
|
|||
} else {
|
||||
tablesInfo[i].stco = writeEntryArray(tablesInfo[i].stco, 1, (int) chunkOffset);
|
||||
}
|
||||
|
||||
if (singleChunk) {
|
||||
tablesInfo[i].stco = -1;
|
||||
}
|
||||
}
|
||||
|
||||
outRestore();
|
||||
|
|
@ -470,7 +454,42 @@ public class Mp4FromDashWriter {
|
|||
}
|
||||
}
|
||||
|
||||
private void initChunkTables(TablesInfo tables, int firstCount, int succesiveCount) {
|
||||
// tables.stsz holds amount of samples of the track (total)
|
||||
int totalSamples = (tables.stsz - firstCount);
|
||||
float chunkAmount = totalSamples / (float) succesiveCount;
|
||||
int remainChunkOffset = (int) Math.ceil(chunkAmount);
|
||||
boolean remain = remainChunkOffset != (int) chunkAmount;
|
||||
int index = 0;
|
||||
|
||||
tables.stsc = 1;
|
||||
if (firstCount != succesiveCount) {
|
||||
tables.stsc++;
|
||||
}
|
||||
if (remain) {
|
||||
tables.stsc++;
|
||||
}
|
||||
|
||||
// stsc_table_entry = [first_chunk, samples_per_chunk, sample_description_index]
|
||||
tables.stsc_bEntries = new int[tables.stsc * 3];
|
||||
tables.stco = remainChunkOffset + 1;// total entrys in chunk offset box
|
||||
|
||||
tables.stsc_bEntries[index++] = 1;
|
||||
tables.stsc_bEntries[index++] = firstCount;
|
||||
tables.stsc_bEntries[index++] = 1;
|
||||
|
||||
if (firstCount != succesiveCount) {
|
||||
tables.stsc_bEntries[index++] = 2;
|
||||
tables.stsc_bEntries[index++] = succesiveCount;
|
||||
tables.stsc_bEntries[index++] = 1;
|
||||
}
|
||||
|
||||
if (remain) {
|
||||
tables.stsc_bEntries[index++] = remainChunkOffset + 1;
|
||||
tables.stsc_bEntries[index++] = totalSamples % succesiveCount;
|
||||
tables.stsc_bEntries[index] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
private void outWrite(byte[] buffer) throws IOException {
|
||||
outWrite(buffer, buffer.length);
|
||||
|
|
@ -585,19 +604,29 @@ public class Mp4FromDashWriter {
|
|||
|
||||
|
||||
private int make_ftyp() throws IOException {
|
||||
byte[] buffer = new byte[]{
|
||||
0x00, 0x00, 0x00, 0x1C, 0x66, 0x74, 0x79, 0x70,// ftyp
|
||||
0x6D, 0x70, 0x34, 0x32,// mayor brand (mp42)
|
||||
0x00, 0x00, 0x02, 0x00,// default minor version (512)
|
||||
0x6D, 0x70, 0x34, 0x31, 0x69, 0x73, 0x6F, 0x6D, 0x69, 0x73, 0x6F, 0x32// compatible brands: mp41 isom iso2
|
||||
};
|
||||
int size = 16 + (compatibleBrands.size() * 4);
|
||||
if (overrideMainBrand != 0) size += 4;
|
||||
|
||||
if (overrideMainBrand != 0)
|
||||
ByteBuffer.wrap(buffer).putInt(8, overrideMainBrand);
|
||||
ByteBuffer buffer = ByteBuffer.allocate(size);
|
||||
buffer.putInt(size);
|
||||
buffer.putInt(0x66747970);// "ftyp"
|
||||
|
||||
outWrite(buffer);
|
||||
if (overrideMainBrand == 0) {
|
||||
buffer.putInt(0x6D703432);// mayor brand "mp42"
|
||||
buffer.putInt(512);// default minor version
|
||||
} else {
|
||||
buffer.putInt(overrideMainBrand);
|
||||
buffer.putInt(0);
|
||||
buffer.putInt(0x6D703432);// "mp42" compatible brand
|
||||
}
|
||||
|
||||
return buffer.length;
|
||||
for (Integer brand : compatibleBrands) {
|
||||
buffer.putInt(brand);// compatible brand
|
||||
}
|
||||
|
||||
outWrite(buffer.array());
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
private byte[] make_mdat(long refSize, boolean is64) {
|
||||
|
|
@ -740,13 +769,12 @@ public class Mp4FromDashWriter {
|
|||
.array()
|
||||
);
|
||||
|
||||
make_mdia(tracks[index].trak.mdia, tables, is64);
|
||||
make_mdia(tracks[index].trak.mdia, tables, is64, tracks[index].kind == TrackKind.Audio);
|
||||
|
||||
lengthFor(start);
|
||||
}
|
||||
|
||||
private void make_mdia(Mdia mdia, TablesInfo tablesInfo, boolean is64) throws IOException {
|
||||
|
||||
private void make_mdia(Mdia mdia, TablesInfo tablesInfo, boolean is64, boolean isAudio) throws IOException {
|
||||
int start_mdia = auxOffset();
|
||||
auxWrite(new byte[]{0x00, 0x00, 0x00, 0x00, 0x6D, 0x64, 0x69, 0x61});// mdia
|
||||
auxWrite(mdia.mdhd);
|
||||
|
|
@ -766,7 +794,7 @@ public class Mp4FromDashWriter {
|
|||
// And stsz can be empty if has a default sample size
|
||||
//
|
||||
if (moovSimulation) {
|
||||
make(0x73747473, -1, 2, 1);
|
||||
make(0x73747473, -1, 2, 1);// stts
|
||||
if (tablesInfo.stss > 0) {
|
||||
make(0x73747373, -1, 1, tablesInfo.stss);
|
||||
}
|
||||
|
|
@ -789,6 +817,11 @@ public class Mp4FromDashWriter {
|
|||
tablesInfo.stco = make(is64 ? 0x636F3634 : 0x7374636F, -1, is64 ? 2 : 1, tablesInfo.stco);
|
||||
}
|
||||
|
||||
if (isAudio) {
|
||||
auxWrite(make_sgpd());
|
||||
tablesInfo.sbgp = make_sbgp();// during simulation the returned offset is ignored
|
||||
}
|
||||
|
||||
lengthFor(start_stbl);
|
||||
lengthFor(start_minf);
|
||||
lengthFor(start_mdia);
|
||||
|
|
@ -816,6 +849,48 @@ public class Mp4FromDashWriter {
|
|||
return buffer.array();
|
||||
}
|
||||
|
||||
private int make_sbgp() throws IOException {
|
||||
int offset = auxOffset();
|
||||
|
||||
auxWrite(new byte[] {
|
||||
0x00, 0x00, 0x00, 0x1C,// box size
|
||||
0x73, 0x62, 0x67, 0x70,// "sbpg"
|
||||
0x00, 0x00, 0x00, 0x00,// default box flags
|
||||
0x72, 0x6F, 0x6C, 0x6C,// group type "roll"
|
||||
0x00, 0x00, 0x00, 0x01,// group table size
|
||||
0x00, 0x00, 0x00, 0x00,// group[0] total samples (to be set later)
|
||||
0x00, 0x00, 0x00, 0x01// group[0] description index
|
||||
});
|
||||
|
||||
return offset + 0x14;
|
||||
}
|
||||
|
||||
private byte[] make_sgpd() {
|
||||
/*
|
||||
* Sample Group Description Box
|
||||
*
|
||||
* ¿whats does?
|
||||
* the table inside of this box gives information about the
|
||||
* characteristics of sample groups. The descriptive information is any other
|
||||
* information needed to define or characterize the sample group.
|
||||
*
|
||||
* ¿is replicabled this box?
|
||||
* NO due lacks of documentation about this box but...
|
||||
* most of m4a encoders and ffmpeg uses this box with dummy values (same values)
|
||||
*/
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.wrap(new byte[] {
|
||||
0x00, 0x00, 0x00, 0x1A,// box size
|
||||
0x73, 0x67, 0x70, 0x64,// "sgpd"
|
||||
0x01, 0x00, 0x00, 0x00,// box flags (unknown flag sets)
|
||||
0x72, 0x6F, 0x6C, 0x6C, // ¿¿group type??
|
||||
0x00, 0x00, 0x00, 0x02,// ¿¿??
|
||||
0x00, 0x00, 0x00, 0x01,// ¿¿??
|
||||
(byte)0xFF, (byte)0xFF// ¿¿??
|
||||
});
|
||||
|
||||
return buffer.array();
|
||||
}
|
||||
|
||||
class TablesInfo {
|
||||
|
||||
|
|
@ -827,5 +902,6 @@ public class Mp4FromDashWriter {
|
|||
int stsz_default;
|
||||
int stss;
|
||||
int stco;
|
||||
int sbgp;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue