Merge branch 'dev' into dev
This commit is contained in:
commit
e94981e6f7
18 changed files with 241 additions and 443 deletions
|
@ -13,7 +13,7 @@
|
||||||
</p>
|
</p>
|
||||||
<hr>
|
<hr>
|
||||||
<p align="center"><a href="#screenshots">Screenshots</a> • <a href="#description">Description</a> • <a href="#features">Features</a> • <a href="#updates">Updates</a> • <a href="#contribution">Contribution</a> • <a href="#donate">Donate</a> • <a href="#license">License</a></p>
|
<p align="center"><a href="#screenshots">Screenshots</a> • <a href="#description">Description</a> • <a href="#features">Features</a> • <a href="#updates">Updates</a> • <a href="#contribution">Contribution</a> • <a href="#donate">Donate</a> • <a href="#license">License</a></p>
|
||||||
<p align="center"><a href="https://newpipe.schabi.org">Website</a> • <a href="https://newpipe.schabi.org/blog/">Blog</a> • <a href="https://newpipe.schabi.org/press/">Press</a></p>
|
<p align="center"><a href="https://newpipe.schabi.org">Website</a> • <a href="https://newpipe.schabi.org/blog/">Blog</a> • <a href="https://newpipe.schabi.org/FAQ/">FAQ</a> • <a href="https://newpipe.schabi.org/press/">Press</a></p>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<b>WARNING: THIS IS A BETA VERSION, THEREFORE YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE VIA OUR GITHUB REPOSITORY.</b>
|
<b>WARNING: THIS IS A BETA VERSION, THEREFORE YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE VIA OUR GITHUB REPOSITORY.</b>
|
||||||
|
|
|
@ -832,7 +832,6 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
psArgs = new String[]{
|
psArgs = new String[]{
|
||||||
selectedStream.getFormat().getSuffix(),
|
selectedStream.getFormat().getSuffix(),
|
||||||
"false",// ignore empty frames
|
"false",// ignore empty frames
|
||||||
"false",// detect youtube duplicate lines
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -79,6 +79,7 @@ import org.schabi.newpipe.util.Constants;
|
||||||
import org.schabi.newpipe.util.ExtractorHelper;
|
import org.schabi.newpipe.util.ExtractorHelper;
|
||||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||||
import org.schabi.newpipe.util.InfoCache;
|
import org.schabi.newpipe.util.InfoCache;
|
||||||
|
import org.schabi.newpipe.util.KoreUtil;
|
||||||
import org.schabi.newpipe.util.ListHelper;
|
import org.schabi.newpipe.util.ListHelper;
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
|
@ -624,7 +625,7 @@ public class VideoDetailFragment
|
||||||
url.replace("https", "http")));
|
url.replace("https", "http")));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (DEBUG) Log.i(TAG, "Failed to start kore", e);
|
if (DEBUG) Log.i(TAG, "Failed to start kore", e);
|
||||||
showInstallKoreDialog(activity);
|
KoreUtil.showInstallKoreDialog(activity);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
|
@ -632,16 +633,6 @@ public class VideoDetailFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void showInstallKoreDialog(final Context context) {
|
|
||||||
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
|
||||||
builder.setMessage(R.string.kore_not_found)
|
|
||||||
.setPositiveButton(R.string.install, (DialogInterface dialog, int which) ->
|
|
||||||
NavigationHelper.installKore(context))
|
|
||||||
.setNegativeButton(R.string.cancel, (DialogInterface dialog, int which) -> {
|
|
||||||
});
|
|
||||||
builder.create().show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupActionBarOnError(final String url) {
|
private void setupActionBarOnError(final String url) {
|
||||||
if (DEBUG) Log.d(TAG, "setupActionBarHandlerOnError() called with: url = [" + url + "]");
|
if (DEBUG) Log.d(TAG, "setupActionBarHandlerOnError() called with: url = [" + url + "]");
|
||||||
Log.e("-----", "missing code");
|
Log.e("-----", "missing code");
|
||||||
|
|
|
@ -55,10 +55,7 @@ public final class BackgroundPlayerActivity extends ServicePlayerActivity {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.player.setRecovery();
|
return switchTo(PopupVideoPlayer.class);
|
||||||
getApplicationContext().sendBroadcast(getPlayerShutdownIntent());
|
|
||||||
getApplicationContext().startService(getSwitchIntent(PopupVideoPlayer.class));
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,6 +150,8 @@ public abstract class BasePlayer implements
|
||||||
@NonNull
|
@NonNull
|
||||||
public static final String RESUME_PLAYBACK = "resume_playback";
|
public static final String RESUME_PLAYBACK = "resume_playback";
|
||||||
@NonNull
|
@NonNull
|
||||||
|
public static final String START_PAUSED = "start_paused";
|
||||||
|
@NonNull
|
||||||
public static final String SELECT_ON_APPEND = "select_on_append";
|
public static final String SELECT_ON_APPEND = "select_on_append";
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -304,7 +306,7 @@ public abstract class BasePlayer implements
|
||||||
}
|
}
|
||||||
// Good to go...
|
// Good to go...
|
||||||
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch, playbackSkipSilence,
|
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch, playbackSkipSilence,
|
||||||
/*playOnInit=*/true);
|
/*playOnInit=*/!intent.getBooleanExtra(START_PAUSED, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void initPlayback(@NonNull final PlayQueue queue,
|
protected void initPlayback(@NonNull final PlayQueue queue,
|
||||||
|
@ -944,10 +946,10 @@ public abstract class BasePlayer implements
|
||||||
public void onPlayPause() {
|
public void onPlayPause() {
|
||||||
if (DEBUG) Log.d(TAG, "onPlayPause() called");
|
if (DEBUG) Log.d(TAG, "onPlayPause() called");
|
||||||
|
|
||||||
if (!isPlaying()) {
|
if (isPlaying()) {
|
||||||
onPlay();
|
|
||||||
} else {
|
|
||||||
onPause();
|
onPause();
|
||||||
|
} else {
|
||||||
|
onPlay();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ import android.database.ContentObserver;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.drawable.ColorDrawable;
|
import android.graphics.drawable.ColorDrawable;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
@ -75,6 +76,7 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback;
|
||||||
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
||||||
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
|
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
|
||||||
import org.schabi.newpipe.util.AnimationUtils;
|
import org.schabi.newpipe.util.AnimationUtils;
|
||||||
|
import org.schabi.newpipe.util.KoreUtil;
|
||||||
import org.schabi.newpipe.util.ListHelper;
|
import org.schabi.newpipe.util.ListHelper;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.PermissionHelper;
|
import org.schabi.newpipe.util.PermissionHelper;
|
||||||
|
@ -440,6 +442,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||||
private boolean queueVisible;
|
private boolean queueVisible;
|
||||||
|
|
||||||
private ImageButton moreOptionsButton;
|
private ImageButton moreOptionsButton;
|
||||||
|
private ImageButton kodiButton;
|
||||||
private ImageButton shareButton;
|
private ImageButton shareButton;
|
||||||
private ImageButton toggleOrientationButton;
|
private ImageButton toggleOrientationButton;
|
||||||
private ImageButton switchPopupButton;
|
private ImageButton switchPopupButton;
|
||||||
|
@ -476,6 +479,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||||
|
|
||||||
this.moreOptionsButton = rootView.findViewById(R.id.moreOptionsButton);
|
this.moreOptionsButton = rootView.findViewById(R.id.moreOptionsButton);
|
||||||
this.secondaryControls = rootView.findViewById(R.id.secondaryControls);
|
this.secondaryControls = rootView.findViewById(R.id.secondaryControls);
|
||||||
|
this.kodiButton = rootView.findViewById(R.id.kodi);
|
||||||
this.shareButton = rootView.findViewById(R.id.share);
|
this.shareButton = rootView.findViewById(R.id.share);
|
||||||
this.toggleOrientationButton = rootView.findViewById(R.id.toggleOrientation);
|
this.toggleOrientationButton = rootView.findViewById(R.id.toggleOrientation);
|
||||||
this.switchBackgroundButton = rootView.findViewById(R.id.switchBackground);
|
this.switchBackgroundButton = rootView.findViewById(R.id.switchBackground);
|
||||||
|
@ -487,6 +491,9 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||||
|
|
||||||
titleTextView.setSelected(true);
|
titleTextView.setSelected(true);
|
||||||
channelTextView.setSelected(true);
|
channelTextView.setSelected(true);
|
||||||
|
boolean showKodiButton = PreferenceManager.getDefaultSharedPreferences(this.context).getBoolean(
|
||||||
|
this.context.getString(R.string.show_play_with_kodi_key), false);
|
||||||
|
kodiButton.setVisibility(showKodiButton ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
getRootView().setKeepScreenOn(true);
|
getRootView().setKeepScreenOn(true);
|
||||||
}
|
}
|
||||||
|
@ -523,6 +530,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||||
closeButton.setOnClickListener(this);
|
closeButton.setOnClickListener(this);
|
||||||
|
|
||||||
moreOptionsButton.setOnClickListener(this);
|
moreOptionsButton.setOnClickListener(this);
|
||||||
|
kodiButton.setOnClickListener(this);
|
||||||
shareButton.setOnClickListener(this);
|
shareButton.setOnClickListener(this);
|
||||||
toggleOrientationButton.setOnClickListener(this);
|
toggleOrientationButton.setOnClickListener(this);
|
||||||
switchBackgroundButton.setOnClickListener(this);
|
switchBackgroundButton.setOnClickListener(this);
|
||||||
|
@ -593,6 +601,17 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onKodiShare() {
|
||||||
|
onPause();
|
||||||
|
try {
|
||||||
|
NavigationHelper.playWithKore(this.context, Uri.parse(
|
||||||
|
playerImpl.getVideoUrl().replace("https", "http")));
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (DEBUG) Log.i(TAG, "Failed to start kore", e);
|
||||||
|
KoreUtil.showInstallKoreDialog(this.context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Player Overrides
|
// Player Overrides
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -619,7 +638,8 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||||
this.getPlaybackPitch(),
|
this.getPlaybackPitch(),
|
||||||
this.getPlaybackSkipSilence(),
|
this.getPlaybackSkipSilence(),
|
||||||
this.getPlaybackQuality(),
|
this.getPlaybackQuality(),
|
||||||
false
|
false,
|
||||||
|
!isPlaying()
|
||||||
);
|
);
|
||||||
context.startService(intent);
|
context.startService(intent);
|
||||||
|
|
||||||
|
@ -642,7 +662,8 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||||
this.getPlaybackPitch(),
|
this.getPlaybackPitch(),
|
||||||
this.getPlaybackSkipSilence(),
|
this.getPlaybackSkipSilence(),
|
||||||
this.getPlaybackQuality(),
|
this.getPlaybackQuality(),
|
||||||
false
|
false,
|
||||||
|
!isPlaying()
|
||||||
);
|
);
|
||||||
context.startService(intent);
|
context.startService(intent);
|
||||||
|
|
||||||
|
@ -691,6 +712,8 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||||
} else if (v.getId() == closeButton.getId()) {
|
} else if (v.getId() == closeButton.getId()) {
|
||||||
onPlaybackShutdown();
|
onPlaybackShutdown();
|
||||||
return;
|
return;
|
||||||
|
} else if (v.getId() == kodiButton.getId()) {
|
||||||
|
onKodiShare();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getCurrentState() != STATE_COMPLETED) {
|
if (getCurrentState() != STATE_COMPLETED) {
|
||||||
|
|
|
@ -571,7 +571,8 @@ public final class PopupVideoPlayer extends Service {
|
||||||
this.getPlaybackPitch(),
|
this.getPlaybackPitch(),
|
||||||
this.getPlaybackSkipSilence(),
|
this.getPlaybackSkipSilence(),
|
||||||
this.getPlaybackQuality(),
|
this.getPlaybackQuality(),
|
||||||
false
|
false,
|
||||||
|
!isPlaying()
|
||||||
);
|
);
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
context.startActivity(intent);
|
context.startActivity(intent);
|
||||||
|
|
|
@ -48,10 +48,7 @@ public final class PopupVideoPlayerActivity extends ServicePlayerActivity {
|
||||||
@Override
|
@Override
|
||||||
public boolean onPlayerOptionSelected(MenuItem item) {
|
public boolean onPlayerOptionSelected(MenuItem item) {
|
||||||
if (item.getItemId() == R.id.action_switch_background) {
|
if (item.getItemId() == R.id.action_switch_background) {
|
||||||
this.player.setRecovery();
|
return switchTo(BackgroundPlayer.class);
|
||||||
getApplicationContext().sendBroadcast(getPlayerShutdownIntent());
|
|
||||||
getApplicationContext().startService(getSwitchIntent(BackgroundPlayer.class));
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -164,10 +164,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
||||||
startActivity(new Intent(Settings.ACTION_SOUND_SETTINGS));
|
startActivity(new Intent(Settings.ACTION_SOUND_SETTINGS));
|
||||||
return true;
|
return true;
|
||||||
case R.id.action_switch_main:
|
case R.id.action_switch_main:
|
||||||
this.player.setRecovery();
|
return switchTo(MainVideoPlayer.class);
|
||||||
getApplicationContext().sendBroadcast(getPlayerShutdownIntent());
|
|
||||||
getApplicationContext().startActivity(getSwitchIntent(MainVideoPlayer.class));
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return onPlayerOptionSelected(item) || super.onOptionsItemSelected(item);
|
return onPlayerOptionSelected(item) || super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
@ -188,8 +185,17 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
||||||
this.player.getPlaybackPitch(),
|
this.player.getPlaybackPitch(),
|
||||||
this.player.getPlaybackSkipSilence(),
|
this.player.getPlaybackSkipSilence(),
|
||||||
null,
|
null,
|
||||||
|
false,
|
||||||
false
|
false
|
||||||
).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
.putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean switchTo(final Class clazz) {
|
||||||
|
this.player.setRecovery();
|
||||||
|
getApplicationContext().sendBroadcast(getPlayerShutdownIntent());
|
||||||
|
getApplicationContext().startActivity(getSwitchIntent(clazz));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
package org.schabi.newpipe.streams;
|
||||||
|
|
||||||
|
import org.jsoup.Jsoup;
|
||||||
|
import org.jsoup.nodes.Document;
|
||||||
|
import org.jsoup.nodes.Element;
|
||||||
|
import org.jsoup.nodes.Node;
|
||||||
|
import org.jsoup.nodes.TextNode;
|
||||||
|
import org.jsoup.parser.Parser;
|
||||||
|
import org.jsoup.select.Elements;
|
||||||
|
import org.schabi.newpipe.streams.io.SharpStream;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kapodamy
|
||||||
|
*/
|
||||||
|
public class SrtFromTtmlWriter {
|
||||||
|
private static final String NEW_LINE = "\r\n";
|
||||||
|
|
||||||
|
private SharpStream out;
|
||||||
|
private boolean ignoreEmptyFrames;
|
||||||
|
private final Charset charset = StandardCharsets.UTF_8;
|
||||||
|
|
||||||
|
private int frameIndex = 0;
|
||||||
|
|
||||||
|
public SrtFromTtmlWriter(SharpStream out, boolean ignoreEmptyFrames) {
|
||||||
|
this.out = out;
|
||||||
|
this.ignoreEmptyFrames = ignoreEmptyFrames;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getTimestamp(Element frame, String attr) {
|
||||||
|
return frame
|
||||||
|
.attr(attr)
|
||||||
|
.replace('.', ',');// SRT subtitles uses comma as decimal separator
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeFrame(String begin, String end, StringBuilder text) throws IOException {
|
||||||
|
writeString(String.valueOf(frameIndex++));
|
||||||
|
writeString(NEW_LINE);
|
||||||
|
writeString(begin);
|
||||||
|
writeString(" --> ");
|
||||||
|
writeString(end);
|
||||||
|
writeString(NEW_LINE);
|
||||||
|
writeString(text.toString());
|
||||||
|
writeString(NEW_LINE);
|
||||||
|
writeString(NEW_LINE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeString(String text) throws IOException {
|
||||||
|
out.write(text.getBytes(charset));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void build(SharpStream ttml) throws IOException {
|
||||||
|
/*
|
||||||
|
* TTML parser with BASIC support
|
||||||
|
* multiple CUE is not supported
|
||||||
|
* styling is not supported
|
||||||
|
* tag timestamps (in auto-generated subtitles) are not supported, maybe in the future
|
||||||
|
* also TimestampTagOption enum is not applicable
|
||||||
|
* Language parsing is not supported
|
||||||
|
*/
|
||||||
|
|
||||||
|
// parse XML
|
||||||
|
byte[] buffer = new byte[(int) ttml.available()];
|
||||||
|
ttml.read(buffer);
|
||||||
|
Document doc = Jsoup.parse(new ByteArrayInputStream(buffer), "UTF-8", "", Parser.xmlParser());
|
||||||
|
|
||||||
|
StringBuilder text = new StringBuilder(128);
|
||||||
|
Elements paragraph_list = doc.select("body > div > p");
|
||||||
|
|
||||||
|
// check if has frames
|
||||||
|
if (paragraph_list.size() < 1) return;
|
||||||
|
|
||||||
|
for (Element paragraph : paragraph_list) {
|
||||||
|
text.setLength(0);
|
||||||
|
|
||||||
|
for (Node children : paragraph.childNodes()) {
|
||||||
|
if (children instanceof TextNode)
|
||||||
|
text.append(((TextNode) children).text());
|
||||||
|
else if (children instanceof Element && ((Element) children).tagName().equalsIgnoreCase("br"))
|
||||||
|
text.append(NEW_LINE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ignoreEmptyFrames && text.length() < 1) continue;
|
||||||
|
|
||||||
|
String begin = getTimestamp(paragraph, "begin");
|
||||||
|
String end = getTimestamp(paragraph, "end");
|
||||||
|
|
||||||
|
writeFrame(begin, end, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,369 +0,0 @@
|
||||||
package org.schabi.newpipe.streams;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.streams.io.SharpStream;
|
|
||||||
import org.w3c.dom.Document;
|
|
||||||
import org.w3c.dom.Element;
|
|
||||||
import org.w3c.dom.Node;
|
|
||||||
import org.w3c.dom.NodeList;
|
|
||||||
import org.xml.sax.SAXException;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.text.ParseException;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
|
||||||
import javax.xml.xpath.XPathExpressionException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author kapodamy
|
|
||||||
*/
|
|
||||||
public class SubtitleConverter {
|
|
||||||
private static final String NEW_LINE = "\r\n";
|
|
||||||
|
|
||||||
public void dumpTTML(SharpStream in, final SharpStream out, final boolean ignoreEmptyFrames, final boolean detectYoutubeDuplicateLines
|
|
||||||
) throws IOException, ParseException, SAXException, ParserConfigurationException, XPathExpressionException {
|
|
||||||
|
|
||||||
final FrameWriter callback = new FrameWriter() {
|
|
||||||
int frameIndex = 0;
|
|
||||||
final Charset charset = Charset.forName("utf-8");
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void yield(SubtitleFrame frame) throws IOException {
|
|
||||||
if (ignoreEmptyFrames && frame.isEmptyText()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
out.write(String.valueOf(frameIndex++).getBytes(charset));
|
|
||||||
out.write(NEW_LINE.getBytes(charset));
|
|
||||||
out.write(getTime(frame.start, true).getBytes(charset));
|
|
||||||
out.write(" --> ".getBytes(charset));
|
|
||||||
out.write(getTime(frame.end, true).getBytes(charset));
|
|
||||||
out.write(NEW_LINE.getBytes(charset));
|
|
||||||
out.write(frame.text.getBytes(charset));
|
|
||||||
out.write(NEW_LINE.getBytes(charset));
|
|
||||||
out.write(NEW_LINE.getBytes(charset));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
read_xml_based(in, callback, detectYoutubeDuplicateLines,
|
|
||||||
"tt", "xmlns", "http://www.w3.org/ns/ttml",
|
|
||||||
new String[]{"timedtext", "head", "wp"},
|
|
||||||
new String[]{"body", "div", "p"},
|
|
||||||
"begin", "end", true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void read_xml_based(SharpStream source, FrameWriter callback, boolean detectYoutubeDuplicateLines,
|
|
||||||
String root, String formatAttr, String formatVersion, String[] cuePath, String[] framePath,
|
|
||||||
String timeAttr, String durationAttr, boolean hasTimestamp
|
|
||||||
) throws IOException, ParseException, SAXException, ParserConfigurationException, XPathExpressionException {
|
|
||||||
/*
|
|
||||||
* XML based subtitles parser with BASIC support
|
|
||||||
* multiple CUE is not supported
|
|
||||||
* styling is not supported
|
|
||||||
* tag timestamps (in auto-generated subtitles) are not supported, maybe in the future
|
|
||||||
* also TimestampTagOption enum is not applicable
|
|
||||||
* Language parsing is not supported
|
|
||||||
*/
|
|
||||||
|
|
||||||
byte[] buffer = new byte[(int) source.available()];
|
|
||||||
source.read(buffer);
|
|
||||||
|
|
||||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
|
||||||
factory.setNamespaceAware(true);
|
|
||||||
DocumentBuilder builder = factory.newDocumentBuilder();
|
|
||||||
Document xml = builder.parse(new ByteArrayInputStream(buffer));
|
|
||||||
|
|
||||||
String attr;
|
|
||||||
|
|
||||||
// get the format version or namespace
|
|
||||||
Element node = xml.getDocumentElement();
|
|
||||||
|
|
||||||
if (node == null) {
|
|
||||||
throw new ParseException("Can't get the format version. ¿wrong namespace?", -1);
|
|
||||||
} else if (!node.getNodeName().equals(root)) {
|
|
||||||
throw new ParseException("Invalid root", -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (formatAttr.equals("xmlns")) {
|
|
||||||
if (!node.getNamespaceURI().equals(formatVersion)) {
|
|
||||||
throw new UnsupportedOperationException("Expected xml namespace: " + formatVersion);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
attr = node.getAttributeNS(formatVersion, formatAttr);
|
|
||||||
if (attr == null) {
|
|
||||||
throw new ParseException("Can't get the format attribute", -1);
|
|
||||||
}
|
|
||||||
if (!attr.equals(formatVersion)) {
|
|
||||||
throw new ParseException("Invalid format version : " + attr, -1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NodeList node_list;
|
|
||||||
|
|
||||||
int line_break = 0;// Maximum characters per line if present (valid for TranScript v3)
|
|
||||||
|
|
||||||
if (!hasTimestamp) {
|
|
||||||
node_list = selectNodes(xml, cuePath, formatVersion);
|
|
||||||
|
|
||||||
if (node_list != null) {
|
|
||||||
// if the subtitle has multiple CUEs, use the highest value
|
|
||||||
for (int i = 0; i < node_list.getLength(); i++) {
|
|
||||||
try {
|
|
||||||
int tmp = Integer.parseInt(((Element) node_list.item(i)).getAttributeNS(formatVersion, "ah"));
|
|
||||||
if (tmp > line_break) {
|
|
||||||
line_break = tmp;
|
|
||||||
}
|
|
||||||
} catch (Exception err) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse every frame
|
|
||||||
node_list = selectNodes(xml, framePath, formatVersion);
|
|
||||||
|
|
||||||
if (node_list == null) {
|
|
||||||
return;// no frames detected
|
|
||||||
}
|
|
||||||
|
|
||||||
int fs_ff = -1;// first timestamp of first frame
|
|
||||||
boolean limit_lines = false;
|
|
||||||
|
|
||||||
for (int i = 0; i < node_list.getLength(); i++) {
|
|
||||||
Element elem = (Element) node_list.item(i);
|
|
||||||
SubtitleFrame obj = new SubtitleFrame();
|
|
||||||
obj.text = elem.getTextContent();
|
|
||||||
|
|
||||||
attr = elem.getAttribute(timeAttr);// ¡this cant be null!
|
|
||||||
obj.start = hasTimestamp ? parseTimestamp(attr) : Integer.parseInt(attr);
|
|
||||||
|
|
||||||
attr = elem.getAttribute(durationAttr);
|
|
||||||
if (obj.text == null || attr == null) {
|
|
||||||
continue;// normally is a blank line (on auto-generated subtitles) ignore
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasTimestamp) {
|
|
||||||
obj.end = parseTimestamp(attr);
|
|
||||||
|
|
||||||
if (detectYoutubeDuplicateLines) {
|
|
||||||
if (limit_lines) {
|
|
||||||
int swap = obj.end;
|
|
||||||
obj.end = fs_ff;
|
|
||||||
fs_ff = swap;
|
|
||||||
} else {
|
|
||||||
if (fs_ff < 0) {
|
|
||||||
fs_ff = obj.end;
|
|
||||||
} else {
|
|
||||||
if (fs_ff < obj.start) {
|
|
||||||
limit_lines = true;// the subtitles has duplicated lines
|
|
||||||
} else {
|
|
||||||
detectYoutubeDuplicateLines = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
obj.end = obj.start + Integer.parseInt(attr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (/*node.getAttribute("w").equals("1") &&*/line_break > 1 && obj.text.length() > line_break) {
|
|
||||||
|
|
||||||
// implement auto line breaking (once)
|
|
||||||
StringBuilder text = new StringBuilder(obj.text);
|
|
||||||
obj.text = null;
|
|
||||||
|
|
||||||
switch (text.charAt(line_break)) {
|
|
||||||
case ' ':
|
|
||||||
case '\t':
|
|
||||||
putBreakAt(line_break, text);
|
|
||||||
break;
|
|
||||||
default:// find the word start position
|
|
||||||
for (int j = line_break - 1; j > 0; j--) {
|
|
||||||
switch (text.charAt(j)) {
|
|
||||||
case ' ':
|
|
||||||
case '\t':
|
|
||||||
putBreakAt(j, text);
|
|
||||||
j = -1;
|
|
||||||
break;
|
|
||||||
case '\r':
|
|
||||||
case '\n':
|
|
||||||
j = -1;// long word, just ignore
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
obj.text = text.toString();// set the processed text
|
|
||||||
}
|
|
||||||
|
|
||||||
callback.yield(obj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NodeList selectNodes(Document xml, String[] path, String namespaceUri) {
|
|
||||||
Element ref = xml.getDocumentElement();
|
|
||||||
|
|
||||||
for (int i = 0; i < path.length - 1; i++) {
|
|
||||||
NodeList nodes = ref.getChildNodes();
|
|
||||||
if (nodes.getLength() < 1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Element elem;
|
|
||||||
for (int j = 0; j < nodes.getLength(); j++) {
|
|
||||||
if (nodes.item(j).getNodeType() == Node.ELEMENT_NODE) {
|
|
||||||
elem = (Element) nodes.item(j);
|
|
||||||
if (elem.getNodeName().equals(path[i]) && elem.getNamespaceURI().equals(namespaceUri)) {
|
|
||||||
ref = elem;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ref.getElementsByTagNameNS(namespaceUri, path[path.length - 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int parseTimestamp(String multiImpl) throws NumberFormatException, ParseException {
|
|
||||||
if (multiImpl.length() < 1) {
|
|
||||||
return 0;
|
|
||||||
} else if (multiImpl.length() == 1) {
|
|
||||||
return Integer.parseInt(multiImpl) * 1000;// ¡this must be a number in seconds!
|
|
||||||
}
|
|
||||||
|
|
||||||
// detect wallclock-time
|
|
||||||
if (multiImpl.startsWith("wallclock(")) {
|
|
||||||
throw new UnsupportedOperationException("Parsing wallclock timestamp is not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
// detect offset-time
|
|
||||||
if (multiImpl.indexOf(':') < 0) {
|
|
||||||
int multiplier = 1000;
|
|
||||||
char metric = multiImpl.charAt(multiImpl.length() - 1);
|
|
||||||
switch (metric) {
|
|
||||||
case 'h':
|
|
||||||
multiplier *= 3600000;
|
|
||||||
break;
|
|
||||||
case 'm':
|
|
||||||
multiplier *= 60000;
|
|
||||||
break;
|
|
||||||
case 's':
|
|
||||||
if (multiImpl.charAt(multiImpl.length() - 2) == 'm') {
|
|
||||||
multiplier = 1;// ms
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (!Character.isDigit(metric)) {
|
|
||||||
throw new NumberFormatException("Invalid metric suffix found on : " + multiImpl);
|
|
||||||
}
|
|
||||||
metric = '\0';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
String offset_time = multiImpl;
|
|
||||||
|
|
||||||
if (multiplier == 1) {
|
|
||||||
offset_time = offset_time.substring(0, offset_time.length() - 2);
|
|
||||||
} else if (metric != '\0') {
|
|
||||||
offset_time = offset_time.substring(0, offset_time.length() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
double time_metric_based = Double.parseDouble(offset_time);
|
|
||||||
if (Math.abs(time_metric_based) <= Double.MAX_VALUE) {
|
|
||||||
return (int) (time_metric_based * multiplier);
|
|
||||||
}
|
|
||||||
} catch (Exception err) {
|
|
||||||
throw new UnsupportedOperationException("Invalid or not implemented timestamp on: " + multiImpl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// detect clock-time
|
|
||||||
int time = 0;
|
|
||||||
String[] units = multiImpl.split(":");
|
|
||||||
|
|
||||||
if (units.length < 3) {
|
|
||||||
throw new ParseException("Invalid clock-time timestamp", -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
time += Integer.parseInt(units[0]) * 3600000;// hours
|
|
||||||
time += Integer.parseInt(units[1]) * 60000;//minutes
|
|
||||||
time += Float.parseFloat(units[2]) * 1000f;// seconds and milliseconds (if present)
|
|
||||||
|
|
||||||
// frames and sub-frames are ignored (not implemented)
|
|
||||||
// time += units[3] * fps;
|
|
||||||
return time;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void putBreakAt(int idx, StringBuilder str) {
|
|
||||||
// this should be optimized at compile time
|
|
||||||
|
|
||||||
if (NEW_LINE.length() > 1) {
|
|
||||||
str.delete(idx, idx + 1);// remove after replace
|
|
||||||
str.insert(idx, NEW_LINE);
|
|
||||||
} else {
|
|
||||||
str.setCharAt(idx, NEW_LINE.charAt(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getTime(int time, boolean comma) {
|
|
||||||
// cast every value to integer to avoid auto-round in ToString("00").
|
|
||||||
StringBuilder str = new StringBuilder(12);
|
|
||||||
str.append(numberToString(time / 1000 / 3600, 2));// hours
|
|
||||||
str.append(':');
|
|
||||||
str.append(numberToString(time / 1000 / 60 % 60, 2));// minutes
|
|
||||||
str.append(':');
|
|
||||||
str.append(numberToString(time / 1000 % 60, 2));// seconds
|
|
||||||
str.append(comma ? ',' : '.');
|
|
||||||
str.append(numberToString(time % 1000, 3));// miliseconds
|
|
||||||
|
|
||||||
return str.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String numberToString(int nro, int pad) {
|
|
||||||
return String.format(Locale.ENGLISH, "%0".concat(String.valueOf(pad)).concat("d"), nro);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/******************
|
|
||||||
* helper classes *
|
|
||||||
******************/
|
|
||||||
|
|
||||||
private interface FrameWriter {
|
|
||||||
|
|
||||||
void yield(SubtitleFrame frame) throws IOException;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class SubtitleFrame {
|
|
||||||
//Java no support unsigned int
|
|
||||||
|
|
||||||
public int end;
|
|
||||||
public int start;
|
|
||||||
public String text = "";
|
|
||||||
|
|
||||||
private boolean isEmptyText() {
|
|
||||||
if (text == null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < text.length(); i++) {
|
|
||||||
switch (text.charAt(i)) {
|
|
||||||
case ' ':
|
|
||||||
case '\t':
|
|
||||||
case '\r':
|
|
||||||
case '\n':
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
23
app/src/main/java/org/schabi/newpipe/util/KoreUtil.java
Normal file
23
app/src/main/java/org/schabi/newpipe/util/KoreUtil.java
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package org.schabi.newpipe.util;
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
|
||||||
|
|
||||||
|
public class KoreUtil {
|
||||||
|
private KoreUtil() { }
|
||||||
|
|
||||||
|
public static void showInstallKoreDialog(final Context context) {
|
||||||
|
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||||
|
builder.setMessage(R.string.kore_not_found)
|
||||||
|
.setPositiveButton(R.string.install,
|
||||||
|
(DialogInterface dialog, int which) -> NavigationHelper.installKore(context))
|
||||||
|
.setNegativeButton(R.string.cancel, (DialogInterface dialog, int which) -> {
|
||||||
|
});
|
||||||
|
builder.create().show();
|
||||||
|
}
|
||||||
|
}
|
|
@ -109,12 +109,14 @@ public class NavigationHelper {
|
||||||
final float playbackPitch,
|
final float playbackPitch,
|
||||||
final boolean playbackSkipSilence,
|
final boolean playbackSkipSilence,
|
||||||
@Nullable final String playbackQuality,
|
@Nullable final String playbackQuality,
|
||||||
final boolean resumePlayback) {
|
final boolean resumePlayback,
|
||||||
|
final boolean startPaused) {
|
||||||
return getPlayerIntent(context, targetClazz, playQueue, playbackQuality, resumePlayback)
|
return getPlayerIntent(context, targetClazz, playQueue, playbackQuality, resumePlayback)
|
||||||
.putExtra(BasePlayer.REPEAT_MODE, repeatMode)
|
.putExtra(BasePlayer.REPEAT_MODE, repeatMode)
|
||||||
.putExtra(BasePlayer.PLAYBACK_SPEED, playbackSpeed)
|
.putExtra(BasePlayer.PLAYBACK_SPEED, playbackSpeed)
|
||||||
.putExtra(BasePlayer.PLAYBACK_PITCH, playbackPitch)
|
.putExtra(BasePlayer.PLAYBACK_PITCH, playbackPitch)
|
||||||
.putExtra(BasePlayer.PLAYBACK_SKIP_SILENCE, playbackSkipSilence);
|
.putExtra(BasePlayer.PLAYBACK_SKIP_SILENCE, playbackSkipSilence)
|
||||||
|
.putExtra(BasePlayer.START_PAUSED, startPaused);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void playOnMainPlayer(final Context context, final PlayQueue queue, final boolean resumePlayback) {
|
public static void playOnMainPlayer(final Context context, final PlayQueue queue, final boolean resumePlayback) {
|
||||||
|
|
|
@ -80,7 +80,7 @@ public abstract class Postprocessing implements Serializable {
|
||||||
|
|
||||||
private transient DownloadMission mission;
|
private transient DownloadMission mission;
|
||||||
|
|
||||||
private File tempFile;
|
private transient File tempFile;
|
||||||
|
|
||||||
Postprocessing(boolean reserveSpace, boolean worksOnSameFile, String algorithmName) {
|
Postprocessing(boolean reserveSpace, boolean worksOnSameFile, String algorithmName) {
|
||||||
this.reserveSpace = reserveSpace;
|
this.reserveSpace = reserveSpace;
|
||||||
|
@ -95,8 +95,12 @@ public abstract class Postprocessing implements Serializable {
|
||||||
|
|
||||||
public void cleanupTemporalDir() {
|
public void cleanupTemporalDir() {
|
||||||
if (tempFile != null && tempFile.exists()) {
|
if (tempFile != null && tempFile.exists()) {
|
||||||
|
try {
|
||||||
//noinspection ResultOfMethodCallIgnored
|
//noinspection ResultOfMethodCallIgnored
|
||||||
tempFile.delete();
|
tempFile.delete();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// nothing to do
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,15 +2,10 @@ package us.shandian.giga.postprocessing;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.schabi.newpipe.streams.SubtitleConverter;
|
import org.schabi.newpipe.streams.SrtFromTtmlWriter;
|
||||||
import org.schabi.newpipe.streams.io.SharpStream;
|
import org.schabi.newpipe.streams.io.SharpStream;
|
||||||
import org.xml.sax.SAXException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.text.ParseException;
|
|
||||||
|
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
|
||||||
import javax.xml.xpath.XPathExpressionException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author kapodamy
|
* @author kapodamy
|
||||||
|
@ -27,33 +22,16 @@ class TtmlConverter extends Postprocessing {
|
||||||
int process(SharpStream out, SharpStream... sources) throws IOException {
|
int process(SharpStream out, SharpStream... sources) throws IOException {
|
||||||
// check if the subtitle is already in srt and copy, this should never happen
|
// check if the subtitle is already in srt and copy, this should never happen
|
||||||
String format = getArgumentAt(0, null);
|
String format = getArgumentAt(0, null);
|
||||||
|
boolean ignoreEmptyFrames = getArgumentAt(1, "true").equals("true");
|
||||||
|
|
||||||
if (format == null || format.equals("ttml")) {
|
if (format == null || format.equals("ttml")) {
|
||||||
SubtitleConverter ttmlDumper = new SubtitleConverter();
|
SrtFromTtmlWriter writer = new SrtFromTtmlWriter(out, ignoreEmptyFrames);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ttmlDumper.dumpTTML(
|
writer.build(sources[0]);
|
||||||
sources[0],
|
|
||||||
out,
|
|
||||||
getArgumentAt(1, "true").equals("true"),
|
|
||||||
getArgumentAt(2, "true").equals("true")
|
|
||||||
);
|
|
||||||
} catch (Exception err) {
|
} catch (Exception err) {
|
||||||
Log.e(TAG, "subtitle parse failed", err);
|
Log.e(TAG, "subtitle parse failed", err);
|
||||||
|
return err instanceof IOException ? 1 : 8;
|
||||||
if (err instanceof IOException) {
|
|
||||||
return 1;
|
|
||||||
} else if (err instanceof ParseException) {
|
|
||||||
return 2;
|
|
||||||
} else if (err instanceof SAXException) {
|
|
||||||
return 3;
|
|
||||||
} else if (err instanceof ParserConfigurationException) {
|
|
||||||
return 4;
|
|
||||||
} else if (err instanceof XPathExpressionException) {
|
|
||||||
return 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 8;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return OK_RESULT;
|
return OK_RESULT;
|
||||||
|
|
|
@ -139,6 +139,9 @@ public class DownloadManager {
|
||||||
Log.d(TAG, "Loading pending downloads from directory: " + mPendingMissionsDir.getAbsolutePath());
|
Log.d(TAG, "Loading pending downloads from directory: " + mPendingMissionsDir.getAbsolutePath());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
File tempDir = pickAvailableTemporalDir(ctx);
|
||||||
|
Log.i(TAG, "using '" + tempDir + "' as temporal directory");
|
||||||
|
|
||||||
for (File sub : subs) {
|
for (File sub : subs) {
|
||||||
if (!sub.isFile()) continue;
|
if (!sub.isFile()) continue;
|
||||||
if (sub.getName().equals(".tmp")) continue;
|
if (sub.getName().equals(".tmp")) continue;
|
||||||
|
@ -184,7 +187,7 @@ public class DownloadManager {
|
||||||
|
|
||||||
if (mis.psAlgorithm != null) {
|
if (mis.psAlgorithm != null) {
|
||||||
mis.psAlgorithm.cleanupTemporalDir();
|
mis.psAlgorithm.cleanupTemporalDir();
|
||||||
mis.psAlgorithm.setTemporalDir(pickAvailableTemporalDir(ctx));
|
mis.psAlgorithm.setTemporalDir(tempDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
mis.metadata = sub;
|
mis.metadata = sub;
|
||||||
|
@ -513,13 +516,21 @@ public class DownloadManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
static File pickAvailableTemporalDir(@NonNull Context ctx) {
|
static File pickAvailableTemporalDir(@NonNull Context ctx) {
|
||||||
if (isDirectoryAvailable(ctx.getExternalFilesDir(null)))
|
File dir = ctx.getExternalFilesDir(null);
|
||||||
return ctx.getExternalFilesDir(null);
|
if (isDirectoryAvailable(dir)) return dir;
|
||||||
else if (isDirectoryAvailable(ctx.getFilesDir()))
|
|
||||||
return ctx.getFilesDir();
|
dir = ctx.getFilesDir();
|
||||||
|
if (isDirectoryAvailable(dir)) return dir;
|
||||||
|
|
||||||
// this never should happen
|
// this never should happen
|
||||||
return ctx.getDir("tmp", Context.MODE_PRIVATE);
|
dir = ctx.getDir("muxing_tmp", Context.MODE_PRIVATE);
|
||||||
|
if (isDirectoryAvailable(dir)) return dir;
|
||||||
|
|
||||||
|
// fallback to cache dir
|
||||||
|
dir = ctx.getCacheDir();
|
||||||
|
if (isDirectoryAvailable(dir)) return dir;
|
||||||
|
|
||||||
|
throw new RuntimeException("Not temporal directories are available");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|
|
@ -305,7 +305,7 @@
|
||||||
tools:text="English" />
|
tools:text="English" />
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/share"
|
android:id="@+id/kodi"
|
||||||
android:layout_width="30dp"
|
android:layout_width="30dp"
|
||||||
android:layout_height="30dp"
|
android:layout_height="30dp"
|
||||||
android:layout_marginLeft="4dp"
|
android:layout_marginLeft="4dp"
|
||||||
|
@ -316,6 +316,25 @@
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:padding="5dp"
|
android:padding="5dp"
|
||||||
android:scaleType="fitXY"
|
android:scaleType="fitXY"
|
||||||
|
android:src="@drawable/ic_cast_white_24dp"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:contentDescription="@string/play_with_kodi_title"
|
||||||
|
tools:ignore="RtlHardcoded"
|
||||||
|
android:visibility="visible"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/share"
|
||||||
|
android:layout_width="30dp"
|
||||||
|
android:layout_height="30dp"
|
||||||
|
android:layout_marginLeft="4dp"
|
||||||
|
android:layout_marginRight="2dp"
|
||||||
|
android:layout_toLeftOf="@id/kodi"
|
||||||
|
android:layout_alignWithParentIfMissing="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:padding="5dp"
|
||||||
|
android:scaleType="fitXY"
|
||||||
android:src="@drawable/ic_share_white_24dp"
|
android:src="@drawable/ic_share_white_24dp"
|
||||||
android:background="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:contentDescription="@string/share"
|
android:contentDescription="@string/share"
|
||||||
|
|
|
@ -303,7 +303,7 @@
|
||||||
tools:text="English" />
|
tools:text="English" />
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/share"
|
android:id="@+id/kodi"
|
||||||
android:layout_width="30dp"
|
android:layout_width="30dp"
|
||||||
android:layout_height="30dp"
|
android:layout_height="30dp"
|
||||||
android:layout_marginLeft="4dp"
|
android:layout_marginLeft="4dp"
|
||||||
|
@ -314,6 +314,25 @@
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:padding="5dp"
|
android:padding="5dp"
|
||||||
android:scaleType="fitXY"
|
android:scaleType="fitXY"
|
||||||
|
android:src="@drawable/ic_cast_white_24dp"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:contentDescription="@string/play_with_kodi_title"
|
||||||
|
tools:ignore="RtlHardcoded"
|
||||||
|
android:visibility="visible"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/share"
|
||||||
|
android:layout_width="30dp"
|
||||||
|
android:layout_height="30dp"
|
||||||
|
android:layout_marginLeft="4dp"
|
||||||
|
android:layout_marginRight="2dp"
|
||||||
|
android:layout_toLeftOf="@id/kodi"
|
||||||
|
android:layout_alignWithParentIfMissing="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:padding="5dp"
|
||||||
|
android:scaleType="fitXY"
|
||||||
android:src="@drawable/ic_share_white_24dp"
|
android:src="@drawable/ic_share_white_24dp"
|
||||||
android:background="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:contentDescription="@string/share"
|
android:contentDescription="@string/share"
|
||||||
|
|
Loading…
Reference in a new issue