diff --git a/app/build.gradle b/app/build.gradle index 2ffb788d6..87215b385 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,8 +13,8 @@ android { resValue "string", "app_name", "NewPipe" minSdkVersion 19 targetSdkVersion 29 - versionCode 959 - versionName "0.20.5" + versionCode 960 + versionName "0.20.6" multiDexEnabled true @@ -175,7 +175,7 @@ dependencies { // NewPipe dependencies // You can use a local version by uncommenting a few lines in settings.gradle - implementation 'com.github.TeamNewPipe:NewPipeExtractor:175df679e05b24b6094570d719cc11f8dfc17c68' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:b3835bd616ab28b861c83dcefd56e1754c6d20be' implementation "com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751" implementation "org.jsoup:jsoup:1.13.1" diff --git a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java index 4dfd1bdce..916630ae6 100644 --- a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java +++ b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java @@ -51,10 +51,10 @@ public final class CheckForNewAppVersion { private static final String NEWPIPE_API_URL = "https://newpipe.schabi.org/api/data.json"; /** - * Method to get the apk's SHA1 key. See https://stackoverflow.com/questions/9293019/#22506133. + * Method to get the APK's SHA1 key. See https://stackoverflow.com/questions/9293019/#22506133. * * @param application The application - * @return String with the apk's SHA1 fingeprint in hexadecimal + * @return String with the APK's SHA1 fingerprint in hexadecimal */ @NonNull private static String getCertificateSHA1Fingerprint(@NonNull final Application application) { diff --git a/app/src/main/java/org/schabi/newpipe/ktx/OffsetDateTime.kt b/app/src/main/java/org/schabi/newpipe/ktx/OffsetDateTime.kt index b3df83c25..0d1a534b9 100644 --- a/app/src/main/java/org/schabi/newpipe/ktx/OffsetDateTime.kt +++ b/app/src/main/java/org/schabi/newpipe/ktx/OffsetDateTime.kt @@ -1,10 +1,29 @@ package org.schabi.newpipe.ktx import java.time.OffsetDateTime -import java.time.ZoneId +import java.time.ZoneOffset +import java.time.temporal.ChronoField import java.util.Calendar +import java.util.Date import java.util.GregorianCalendar +import java.util.TimeZone -fun OffsetDateTime.toCalendar(zoneId: ZoneId = ZoneId.systemDefault()): Calendar { - return GregorianCalendar.from(if (zoneId != offset) atZoneSameInstant(zoneId) else toZonedDateTime()) +// This method is a modified version of GregorianCalendar.from(ZonedDateTime). +// Math.addExact() and Math.multiplyExact() are desugared even though lint displays a warning. +@SuppressWarnings("NewApi") +fun OffsetDateTime.toCalendar(): Calendar { + val cal = GregorianCalendar(TimeZone.getTimeZone("UTC")) + val offsetDateTimeUTC = withOffsetSameInstant(ZoneOffset.UTC) + cal.gregorianChange = Date(Long.MIN_VALUE) + cal.firstDayOfWeek = Calendar.MONDAY + cal.minimalDaysInFirstWeek = 4 + try { + cal.timeInMillis = Math.addExact( + Math.multiplyExact(offsetDateTimeUTC.toEpochSecond(), 1000), + offsetDateTimeUTC[ChronoField.MILLI_OF_SECOND].toLong() + ) + } catch (ex: ArithmeticException) { + throw IllegalArgumentException(ex) + } + return cal } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/services/ImportExportEventListener.java b/app/src/main/java/org/schabi/newpipe/local/subscription/services/ImportExportEventListener.java index 34bd68f5e..7352d1f12 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/services/ImportExportEventListener.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/services/ImportExportEventListener.java @@ -9,7 +9,7 @@ public interface ImportExportEventListener { void onSizeReceived(int size); /** - * Called everytime an item has been parsed/resolved. + * Called every time an item has been parsed/resolved. * * @param itemName the name of the subscription item */ diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java index 94ef64581..745319c98 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -698,7 +698,7 @@ public abstract class BasePlayer implements public void onMuteUnmuteButtonClicked() { if (DEBUG) { - Log.d(TAG, "onMuteUnmuteButtonClicled() called"); + Log.d(TAG, "onMuteUnmuteButtonClicked() called"); } simpleExoPlayer.setVolume(isMuted() ? 1 : 0); } diff --git a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java index ad4c603cd..d1c2be014 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java @@ -618,7 +618,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity progressLiveSync.setClickable(!player.isLiveEdge()); } - // this will make shure progressCurrentTime has the same width as progressEndTime + // this will make sure progressCurrentTime has the same width as progressEndTime final ViewGroup.LayoutParams endTimeParams = progressEndTime.getLayoutParams(); final ViewGroup.LayoutParams currentTimeParams = progressCurrentTime.getLayoutParams(); currentTimeParams.width = progressEndTime.getWidth(); diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java b/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java index d123a263b..0604e6ae8 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java @@ -26,12 +26,12 @@ public class LoadController implements LoadControl { } private LoadController(final int initialPlaybackBufferMs, - final int minimumPlaybackbufferMs, + final int minimumPlaybackBufferMs, final int optimalPlaybackBufferMs) { this.initialPlaybackBufferUs = initialPlaybackBufferMs * 1000; final DefaultLoadControl.Builder builder = new DefaultLoadControl.Builder(); - builder.setBufferDurationsMs(minimumPlaybackbufferMs, optimalPlaybackBufferMs, + builder.setBufferDurationsMs(minimumPlaybackBufferMs, optimalPlaybackBufferMs, initialPlaybackBufferMs, initialPlaybackBufferMs); internalLoadControl = builder.createDefaultLoadControl(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java index 17bb6f4c4..07c8d9f90 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java @@ -128,9 +128,9 @@ abstract class AbstractInfoPlayQueue ext fetchReactor = null; } - private static List extractListItems(final List infos) { + private static List extractListItems(final List infoItems) { final List result = new ArrayList<>(); - for (final InfoItem stream : infos) { + for (final InfoItem stream : infoItems) { if (stream instanceof StreamInfoItem) { result.add(new PlayQueueItem((StreamInfoItem) stream)); } diff --git a/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java b/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java index 3b3c74e3a..266cec24a 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java +++ b/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java @@ -1,416 +1,416 @@ -package org.schabi.newpipe.streams; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.schabi.newpipe.streams.WebMReader.Cluster; -import org.schabi.newpipe.streams.WebMReader.Segment; -import org.schabi.newpipe.streams.WebMReader.SimpleBlock; -import org.schabi.newpipe.streams.WebMReader.WebMTrack; -import org.schabi.newpipe.streams.io.SharpStream; - -import java.io.Closeable; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -/** - * @author kapodamy - */ -public class OggFromWebMWriter implements Closeable { - private static final byte FLAG_UNSET = 0x00; - //private static final byte FLAG_CONTINUED = 0x01; - private static final byte FLAG_FIRST = 0x02; - private static final byte FLAG_LAST = 0x04; - - private static final byte HEADER_CHECKSUM_OFFSET = 22; - private static final byte HEADER_SIZE = 27; - - private static final int TIME_SCALE_NS = 1000000000; - - private boolean done = false; - private boolean parsed = false; - - private final SharpStream source; - private final SharpStream output; - - private int sequenceCount = 0; - private final int streamId; - private byte packetFlag = FLAG_FIRST; - - private WebMReader webm = null; - private WebMTrack webmTrack = null; - private Segment webmSegment = null; - private Cluster webmCluster = null; - private SimpleBlock webmBlock = null; - - private long webmBlockLastTimecode = 0; - private long webmBlockNearDuration = 0; - - private short segmentTableSize = 0; - private final byte[] segmentTable = new byte[255]; - private long segmentTableNextTimestamp = TIME_SCALE_NS; - - private final int[] crc32Table = new int[256]; - - public OggFromWebMWriter(@NonNull final SharpStream source, @NonNull final SharpStream target) { - if (!source.canRead() || !source.canRewind()) { - throw new IllegalArgumentException("source stream must be readable and allows seeking"); - } - if (!target.canWrite() || !target.canRewind()) { - throw new IllegalArgumentException("output stream must be writable and allows seeking"); - } - - this.source = source; - this.output = target; - - this.streamId = (int) System.currentTimeMillis(); - - populateCrc32Table(); - } - - public boolean isDone() { - return done; - } - - public boolean isParsed() { - return parsed; - } - - public WebMTrack[] getTracksFromSource() throws IllegalStateException { - if (!parsed) { - throw new IllegalStateException("source must be parsed first"); - } - - return webm.getAvailableTracks(); - } - - public void parseSource() throws IOException, IllegalStateException { - if (done) { - throw new IllegalStateException("already done"); - } - if (parsed) { - throw new IllegalStateException("already parsed"); - } - - try { - webm = new WebMReader(source); - webm.parse(); - webmSegment = webm.getNextSegment(); - } finally { - parsed = true; - } - } - - public void selectTrack(final int trackIndex) throws IOException { - if (!parsed) { - throw new IllegalStateException("source must be parsed first"); - } - if (done) { - throw new IOException("already done"); - } - if (webmTrack != null) { - throw new IOException("tracks already selected"); - } - - switch (webm.getAvailableTracks()[trackIndex].kind) { - case Audio: - case Video: - break; - default: - throw new UnsupportedOperationException("the track must an audio or video stream"); - } - - try { - webmTrack = webm.selectTrack(trackIndex); - } finally { - parsed = true; - } - } - - @Override - public void close() throws IOException { - done = true; - parsed = true; - - webmTrack = null; - webm = null; - - if (!output.isClosed()) { - output.flush(); - } - - source.close(); - output.close(); - } - - public void build() throws IOException { - final float resolution; - SimpleBlock bloq; - final ByteBuffer header = ByteBuffer.allocate(27 + (255 * 255)); - final ByteBuffer page = ByteBuffer.allocate(64 * 1024); - - header.order(ByteOrder.LITTLE_ENDIAN); - - /* step 1: get the amount of frames per seconds */ - switch (webmTrack.kind) { - case Audio: - resolution = getSampleFrequencyFromTrack(webmTrack.bMetadata); - if (resolution == 0f) { - throw new RuntimeException("cannot get the audio sample rate"); - } - break; - case Video: - // WARNING: untested - if (webmTrack.defaultDuration == 0) { - throw new RuntimeException("missing default frame time"); - } - resolution = 1000f / ((float) webmTrack.defaultDuration - / webmSegment.info.timecodeScale); - break; - default: - throw new RuntimeException("not implemented"); - } - - /* step 2: create packet with code init data */ - if (webmTrack.codecPrivate != null) { - addPacketSegment(webmTrack.codecPrivate.length); - makePacketheader(0x00, header, webmTrack.codecPrivate); - write(header); - output.write(webmTrack.codecPrivate); - } - - /* step 3: create packet with metadata */ - final byte[] buffer = makeMetadata(); - if (buffer != null) { - addPacketSegment(buffer.length); - makePacketheader(0x00, header, buffer); - write(header); - output.write(buffer); - } - - /* step 4: calculate amount of packets */ - while (webmSegment != null) { - bloq = getNextBlock(); - - if (bloq != null && addPacketSegment(bloq)) { - final int pos = page.position(); - //noinspection ResultOfMethodCallIgnored - bloq.data.read(page.array(), pos, bloq.dataSize); - page.position(pos + bloq.dataSize); - continue; - } - - // calculate the current packet duration using the next block - double elapsedNs = webmTrack.codecDelay; - - if (bloq == null) { - packetFlag = FLAG_LAST; // note: if the flag is FLAG_CONTINUED, is changed - elapsedNs += webmBlockLastTimecode; - - if (webmTrack.defaultDuration > 0) { - elapsedNs += webmTrack.defaultDuration; - } else { - // hardcoded way, guess the sample duration - elapsedNs += webmBlockNearDuration; - } - } else { - elapsedNs += bloq.absoluteTimeCodeNs; - } - - // get the sample count in the page - elapsedNs = elapsedNs / TIME_SCALE_NS; - elapsedNs = Math.ceil(elapsedNs * resolution); - - // create header and calculate page checksum - int checksum = makePacketheader((long) elapsedNs, header, null); - checksum = calcCrc32(checksum, page.array(), page.position()); - - header.putInt(HEADER_CHECKSUM_OFFSET, checksum); - - // dump data - write(header); - write(page); - - webmBlock = bloq; - } - } - - private int makePacketheader(final long granPos, @NonNull final ByteBuffer buffer, - final byte[] immediatePage) { - short length = HEADER_SIZE; - - buffer.putInt(0x5367674f); // "OggS" binary string in little-endian - buffer.put((byte) 0x00); // version - buffer.put(packetFlag); // type - - buffer.putLong(granPos); // granulate position - - buffer.putInt(streamId); // bitstream serial number - buffer.putInt(sequenceCount++); // page sequence number - - buffer.putInt(0x00); // page checksum - - buffer.put((byte) segmentTableSize); // segment table - buffer.put(segmentTable, 0, segmentTableSize); // segment size - - length += segmentTableSize; - - clearSegmentTable(); // clear segment table for next header - - int checksumCrc32 = calcCrc32(0x00, buffer.array(), length); - - if (immediatePage != null) { - checksumCrc32 = calcCrc32(checksumCrc32, immediatePage, immediatePage.length); - buffer.putInt(HEADER_CHECKSUM_OFFSET, checksumCrc32); - segmentTableNextTimestamp -= TIME_SCALE_NS; - } - - return checksumCrc32; - } - - @Nullable - private byte[] makeMetadata() { - if ("A_OPUS".equals(webmTrack.codecId)) { - return new byte[]{ - 0x4F, 0x70, 0x75, 0x73, 0x54, 0x61, 0x67, 0x73, // "OpusTags" binary string - 0x00, 0x00, 0x00, 0x00, // writing application string size (not present) - 0x00, 0x00, 0x00, 0x00 // additional tags count (zero means no tags) - }; - } else if ("A_VORBIS".equals(webmTrack.codecId)) { - return new byte[]{ - 0x03, // ¿¿¿??? - 0x76, 0x6f, 0x72, 0x62, 0x69, 0x73, // "vorbis" binary string - 0x00, 0x00, 0x00, 0x00, // writing application string size (not present) - 0x00, 0x00, 0x00, 0x00 // additional tags count (zero means no tags) - }; - } - - // not implemented for the desired codec - return null; - } - - private void write(final ByteBuffer buffer) throws IOException { - output.write(buffer.array(), 0, buffer.position()); - buffer.position(0); - } - - @Nullable - private SimpleBlock getNextBlock() throws IOException { - SimpleBlock res; - - if (webmBlock != null) { - res = webmBlock; - webmBlock = null; - return res; - } - - if (webmSegment == null) { - webmSegment = webm.getNextSegment(); - if (webmSegment == null) { - return null; // no more blocks in the selected track - } - } - - if (webmCluster == null) { - webmCluster = webmSegment.getNextCluster(); - if (webmCluster == null) { - webmSegment = null; - return getNextBlock(); - } - } - - res = webmCluster.getNextSimpleBlock(); - if (res == null) { - webmCluster = null; - return getNextBlock(); - } - - webmBlockNearDuration = res.absoluteTimeCodeNs - webmBlockLastTimecode; - webmBlockLastTimecode = res.absoluteTimeCodeNs; - - return res; - } - - private float getSampleFrequencyFromTrack(final byte[] bMetadata) { - // hardcoded way - final ByteBuffer buffer = ByteBuffer.wrap(bMetadata); - - while (buffer.remaining() >= 6) { - final int id = buffer.getShort() & 0xFFFF; - if (id == 0x0000B584) { - return buffer.getFloat(); - } - } - - return 0.0f; - } - - private void clearSegmentTable() { - segmentTableNextTimestamp += TIME_SCALE_NS; - packetFlag = FLAG_UNSET; - segmentTableSize = 0; - } - - private boolean addPacketSegment(final SimpleBlock block) { - final long timestamp = block.absoluteTimeCodeNs + webmTrack.codecDelay; - - if (timestamp >= segmentTableNextTimestamp) { - return false; - } - - return addPacketSegment(block.dataSize); - } - - private boolean addPacketSegment(final int size) { - if (size > 65025) { - throw new UnsupportedOperationException("page size cannot be larger than 65025"); - } - - int available = (segmentTable.length - segmentTableSize) * 255; - final boolean extra = (size % 255) == 0; - - if (extra) { - // add a zero byte entry in the table - // required to indicate the sample size is multiple of 255 - available -= 255; - } - - // check if possible add the segment, without overflow the table - if (available < size) { - return false; // not enough space on the page - } - - for (int seg = size; seg > 0; seg -= 255) { - segmentTable[segmentTableSize++] = (byte) Math.min(seg, 255); - } - - if (extra) { - segmentTable[segmentTableSize++] = 0x00; - } - - return true; - } - - private void populateCrc32Table() { - for (int i = 0; i < 0x100; i++) { - int crc = i << 24; - for (int j = 0; j < 8; j++) { - final long b = crc >>> 31; - crc <<= 1; - crc ^= (int) (0x100000000L - b) & 0x04c11db7; - } - crc32Table[i] = crc; - } - } - - private int calcCrc32(final int initialCrc, final byte[] buffer, final int size) { - int crc = initialCrc; - for (int i = 0; i < size; i++) { - final int reg = (crc >>> 24) & 0xff; - crc = (crc << 8) ^ crc32Table[reg ^ (buffer[i] & 0xff)]; - } - - return crc; - } -} +package org.schabi.newpipe.streams; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.schabi.newpipe.streams.WebMReader.Cluster; +import org.schabi.newpipe.streams.WebMReader.Segment; +import org.schabi.newpipe.streams.WebMReader.SimpleBlock; +import org.schabi.newpipe.streams.WebMReader.WebMTrack; +import org.schabi.newpipe.streams.io.SharpStream; + +import java.io.Closeable; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * @author kapodamy + */ +public class OggFromWebMWriter implements Closeable { + private static final byte FLAG_UNSET = 0x00; + //private static final byte FLAG_CONTINUED = 0x01; + private static final byte FLAG_FIRST = 0x02; + private static final byte FLAG_LAST = 0x04; + + private static final byte HEADER_CHECKSUM_OFFSET = 22; + private static final byte HEADER_SIZE = 27; + + private static final int TIME_SCALE_NS = 1000000000; + + private boolean done = false; + private boolean parsed = false; + + private final SharpStream source; + private final SharpStream output; + + private int sequenceCount = 0; + private final int streamId; + private byte packetFlag = FLAG_FIRST; + + private WebMReader webm = null; + private WebMTrack webmTrack = null; + private Segment webmSegment = null; + private Cluster webmCluster = null; + private SimpleBlock webmBlock = null; + + private long webmBlockLastTimecode = 0; + private long webmBlockNearDuration = 0; + + private short segmentTableSize = 0; + private final byte[] segmentTable = new byte[255]; + private long segmentTableNextTimestamp = TIME_SCALE_NS; + + private final int[] crc32Table = new int[256]; + + public OggFromWebMWriter(@NonNull final SharpStream source, @NonNull final SharpStream target) { + if (!source.canRead() || !source.canRewind()) { + throw new IllegalArgumentException("source stream must be readable and allows seeking"); + } + if (!target.canWrite() || !target.canRewind()) { + throw new IllegalArgumentException("output stream must be writable and allows seeking"); + } + + this.source = source; + this.output = target; + + this.streamId = (int) System.currentTimeMillis(); + + populateCrc32Table(); + } + + public boolean isDone() { + return done; + } + + public boolean isParsed() { + return parsed; + } + + public WebMTrack[] getTracksFromSource() throws IllegalStateException { + if (!parsed) { + throw new IllegalStateException("source must be parsed first"); + } + + return webm.getAvailableTracks(); + } + + public void parseSource() throws IOException, IllegalStateException { + if (done) { + throw new IllegalStateException("already done"); + } + if (parsed) { + throw new IllegalStateException("already parsed"); + } + + try { + webm = new WebMReader(source); + webm.parse(); + webmSegment = webm.getNextSegment(); + } finally { + parsed = true; + } + } + + public void selectTrack(final int trackIndex) throws IOException { + if (!parsed) { + throw new IllegalStateException("source must be parsed first"); + } + if (done) { + throw new IOException("already done"); + } + if (webmTrack != null) { + throw new IOException("tracks already selected"); + } + + switch (webm.getAvailableTracks()[trackIndex].kind) { + case Audio: + case Video: + break; + default: + throw new UnsupportedOperationException("the track must an audio or video stream"); + } + + try { + webmTrack = webm.selectTrack(trackIndex); + } finally { + parsed = true; + } + } + + @Override + public void close() throws IOException { + done = true; + parsed = true; + + webmTrack = null; + webm = null; + + if (!output.isClosed()) { + output.flush(); + } + + source.close(); + output.close(); + } + + public void build() throws IOException { + final float resolution; + SimpleBlock bloq; + final ByteBuffer header = ByteBuffer.allocate(27 + (255 * 255)); + final ByteBuffer page = ByteBuffer.allocate(64 * 1024); + + header.order(ByteOrder.LITTLE_ENDIAN); + + /* step 1: get the amount of frames per seconds */ + switch (webmTrack.kind) { + case Audio: + resolution = getSampleFrequencyFromTrack(webmTrack.bMetadata); + if (resolution == 0f) { + throw new RuntimeException("cannot get the audio sample rate"); + } + break; + case Video: + // WARNING: untested + if (webmTrack.defaultDuration == 0) { + throw new RuntimeException("missing default frame time"); + } + resolution = 1000f / ((float) webmTrack.defaultDuration + / webmSegment.info.timecodeScale); + break; + default: + throw new RuntimeException("not implemented"); + } + + /* step 2: create packet with code init data */ + if (webmTrack.codecPrivate != null) { + addPacketSegment(webmTrack.codecPrivate.length); + makePacketheader(0x00, header, webmTrack.codecPrivate); + write(header); + output.write(webmTrack.codecPrivate); + } + + /* step 3: create packet with metadata */ + final byte[] buffer = makeMetadata(); + if (buffer != null) { + addPacketSegment(buffer.length); + makePacketheader(0x00, header, buffer); + write(header); + output.write(buffer); + } + + /* step 4: calculate amount of packets */ + while (webmSegment != null) { + bloq = getNextBlock(); + + if (bloq != null && addPacketSegment(bloq)) { + final int pos = page.position(); + //noinspection ResultOfMethodCallIgnored + bloq.data.read(page.array(), pos, bloq.dataSize); + page.position(pos + bloq.dataSize); + continue; + } + + // calculate the current packet duration using the next block + double elapsedNs = webmTrack.codecDelay; + + if (bloq == null) { + packetFlag = FLAG_LAST; // note: if the flag is FLAG_CONTINUED, is changed + elapsedNs += webmBlockLastTimecode; + + if (webmTrack.defaultDuration > 0) { + elapsedNs += webmTrack.defaultDuration; + } else { + // hardcoded way, guess the sample duration + elapsedNs += webmBlockNearDuration; + } + } else { + elapsedNs += bloq.absoluteTimeCodeNs; + } + + // get the sample count in the page + elapsedNs = elapsedNs / TIME_SCALE_NS; + elapsedNs = Math.ceil(elapsedNs * resolution); + + // create header and calculate page checksum + int checksum = makePacketheader((long) elapsedNs, header, null); + checksum = calcCrc32(checksum, page.array(), page.position()); + + header.putInt(HEADER_CHECKSUM_OFFSET, checksum); + + // dump data + write(header); + write(page); + + webmBlock = bloq; + } + } + + private int makePacketheader(final long granPos, @NonNull final ByteBuffer buffer, + final byte[] immediatePage) { + short length = HEADER_SIZE; + + buffer.putInt(0x5367674f); // "OggS" binary string in little-endian + buffer.put((byte) 0x00); // version + buffer.put(packetFlag); // type + + buffer.putLong(granPos); // granulate position + + buffer.putInt(streamId); // bitstream serial number + buffer.putInt(sequenceCount++); // page sequence number + + buffer.putInt(0x00); // page checksum + + buffer.put((byte) segmentTableSize); // segment table + buffer.put(segmentTable, 0, segmentTableSize); // segment size + + length += segmentTableSize; + + clearSegmentTable(); // clear segment table for next header + + int checksumCrc32 = calcCrc32(0x00, buffer.array(), length); + + if (immediatePage != null) { + checksumCrc32 = calcCrc32(checksumCrc32, immediatePage, immediatePage.length); + buffer.putInt(HEADER_CHECKSUM_OFFSET, checksumCrc32); + segmentTableNextTimestamp -= TIME_SCALE_NS; + } + + return checksumCrc32; + } + + @Nullable + private byte[] makeMetadata() { + if ("A_OPUS".equals(webmTrack.codecId)) { + return new byte[]{ + 0x4F, 0x70, 0x75, 0x73, 0x54, 0x61, 0x67, 0x73, // "OpusTags" binary string + 0x00, 0x00, 0x00, 0x00, // writing application string size (not present) + 0x00, 0x00, 0x00, 0x00 // additional tags count (zero means no tags) + }; + } else if ("A_VORBIS".equals(webmTrack.codecId)) { + return new byte[]{ + 0x03, // ¿¿¿??? + 0x76, 0x6f, 0x72, 0x62, 0x69, 0x73, // "vorbis" binary string + 0x00, 0x00, 0x00, 0x00, // writing application string size (not present) + 0x00, 0x00, 0x00, 0x00 // additional tags count (zero means no tags) + }; + } + + // not implemented for the desired codec + return null; + } + + private void write(final ByteBuffer buffer) throws IOException { + output.write(buffer.array(), 0, buffer.position()); + buffer.position(0); + } + + @Nullable + private SimpleBlock getNextBlock() throws IOException { + SimpleBlock res; + + if (webmBlock != null) { + res = webmBlock; + webmBlock = null; + return res; + } + + if (webmSegment == null) { + webmSegment = webm.getNextSegment(); + if (webmSegment == null) { + return null; // no more blocks in the selected track + } + } + + if (webmCluster == null) { + webmCluster = webmSegment.getNextCluster(); + if (webmCluster == null) { + webmSegment = null; + return getNextBlock(); + } + } + + res = webmCluster.getNextSimpleBlock(); + if (res == null) { + webmCluster = null; + return getNextBlock(); + } + + webmBlockNearDuration = res.absoluteTimeCodeNs - webmBlockLastTimecode; + webmBlockLastTimecode = res.absoluteTimeCodeNs; + + return res; + } + + private float getSampleFrequencyFromTrack(final byte[] bMetadata) { + // hardcoded way + final ByteBuffer buffer = ByteBuffer.wrap(bMetadata); + + while (buffer.remaining() >= 6) { + final int id = buffer.getShort() & 0xFFFF; + if (id == 0x0000B584) { + return buffer.getFloat(); + } + } + + return 0.0f; + } + + private void clearSegmentTable() { + segmentTableNextTimestamp += TIME_SCALE_NS; + packetFlag = FLAG_UNSET; + segmentTableSize = 0; + } + + private boolean addPacketSegment(final SimpleBlock block) { + final long timestamp = block.absoluteTimeCodeNs + webmTrack.codecDelay; + + if (timestamp >= segmentTableNextTimestamp) { + return false; + } + + return addPacketSegment(block.dataSize); + } + + private boolean addPacketSegment(final int size) { + if (size > 65025) { + throw new UnsupportedOperationException("page size cannot be larger than 65025"); + } + + int available = (segmentTable.length - segmentTableSize) * 255; + final boolean extra = (size % 255) == 0; + + if (extra) { + // add a zero byte entry in the table + // required to indicate the sample size is multiple of 255 + available -= 255; + } + + // check if possible add the segment, without overflow the table + if (available < size) { + return false; // not enough space on the page + } + + for (int seg = size; seg > 0; seg -= 255) { + segmentTable[segmentTableSize++] = (byte) Math.min(seg, 255); + } + + if (extra) { + segmentTable[segmentTableSize++] = 0x00; + } + + return true; + } + + private void populateCrc32Table() { + for (int i = 0; i < 0x100; i++) { + int crc = i << 24; + for (int j = 0; j < 8; j++) { + final long b = crc >>> 31; + crc <<= 1; + crc ^= (int) (0x100000000L - b) & 0x04c11db7; + } + crc32Table[i] = crc; + } + } + + private int calcCrc32(final int initialCrc, final byte[] buffer, final int size) { + int crc = initialCrc; + for (int i = 0; i < size; i++) { + final int reg = (crc >>> 24) & 0xff; + crc = (crc << 8) ^ crc32Table[reg ^ (buffer[i] & 0xff)]; + } + + return crc; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java b/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java index b676a1a88..ccd4d13fc 100644 --- a/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java +++ b/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java @@ -5,7 +5,7 @@ import android.content.Context; import org.schabi.newpipe.R; /** - * Created by Chrsitian Schabesberger on 28.09.17. + * Created by Christian Schabesberger on 28.09.17. * KioskTranslator.java is part of NewPipe. *

* NewPipe is free software: you can redistribute it and/or modify diff --git a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java index 0c840f8c3..5f8fb5898 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -141,7 +141,7 @@ public final class ListHelper { final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); - // Load the prefered resolution otherwise the best available + // Load the preferred resolution otherwise the best available String resolution = preferences != null ? preferences.getString(context.getString(key), context.getString(value)) : context.getString(R.string.best_resolution_key); @@ -161,7 +161,7 @@ public final class ListHelper { * * @param defaultResolution the default resolution to look for * @param bestResolutionKey key of the best resolution - * @param defaultFormat the default fomat to look for + * @param defaultFormat the default format to look for * @param videoStreams list of the video streams to check * @return index of the default resolution&format */ @@ -351,7 +351,7 @@ public final class ListHelper { * @param targetResolution the resolution to look for * @param targetFormat the format to look for * @param videoStreams the available video streams - * @return the index of the prefered video stream + * @return the index of the preferred video stream */ static int getVideoStreamIndex(final String targetResolution, final MediaFormat targetFormat, final List videoStreams) { @@ -413,7 +413,7 @@ public final class ListHelper { * @param context Android app context * @param defaultResolution the default resolution * @param videoStreams the list of video streams to check - * @return the index of the prefered video stream + * @return the index of the preferred video stream */ private static int getDefaultResolutionWithDefaultFormat(final Context context, final String defaultResolution, diff --git a/app/src/main/java/org/schabi/newpipe/util/Localization.java b/app/src/main/java/org/schabi/newpipe/util/Localization.java index afe2c0467..710827864 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Localization.java +++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java @@ -20,6 +20,7 @@ import org.ocpsoft.prettytime.units.Decade; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.localization.ContentCountry; +import org.schabi.newpipe.ktx.OffsetDateTimeKt; import java.math.BigDecimal; import java.math.RoundingMode; @@ -30,7 +31,6 @@ import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; import java.util.Arrays; import java.util.Calendar; -import java.util.GregorianCalendar; import java.util.List; import java.util.Locale; @@ -314,7 +314,7 @@ public final class Localization { } public static String relativeTime(final OffsetDateTime offsetDateTime) { - return relativeTime(GregorianCalendar.from(offsetDateTime.toZonedDateTime())); + return relativeTime(OffsetDateTimeKt.toCalendar(offsetDateTime)); } public static String relativeTime(final Calendar calendarTime) { diff --git a/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java b/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java index 0e9b9ff00..5b2858aa2 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java @@ -1,313 +1,313 @@ -package us.shandian.giga.get; - -import android.util.Log; - -import org.schabi.newpipe.extractor.NewPipe; -import org.schabi.newpipe.extractor.StreamingService; -import org.schabi.newpipe.extractor.exceptions.ExtractionException; -import org.schabi.newpipe.extractor.stream.AudioStream; -import org.schabi.newpipe.extractor.stream.StreamExtractor; -import org.schabi.newpipe.extractor.stream.SubtitlesStream; -import org.schabi.newpipe.extractor.stream.VideoStream; - -import java.io.IOException; -import java.io.InterruptedIOException; -import java.net.HttpURLConnection; -import java.nio.channels.ClosedByInterruptException; -import java.util.List; - -import us.shandian.giga.get.DownloadMission.HttpError; - -import static us.shandian.giga.get.DownloadMission.ERROR_RESOURCE_GONE; - -public class DownloadMissionRecover extends Thread { - private static final String TAG = "DownloadMissionRecover"; - static final int mID = -3; - - private final DownloadMission mMission; - private final boolean mNotInitialized; - - private final int mErrCode; - - private HttpURLConnection mConn; - private MissionRecoveryInfo mRecovery; - private StreamExtractor mExtractor; - - DownloadMissionRecover(DownloadMission mission, int errCode) { - mMission = mission; - mNotInitialized = mission.blocks == null && mission.current == 0; - mErrCode = errCode; - } - - @Override - public void run() { - if (mMission.source == null) { - mMission.notifyError(mErrCode, null); - return; - } - - Exception err = null; - int attempt = 0; - - while (attempt++ < mMission.maxRetry) { - try { - tryRecover(); - return; - } catch (InterruptedIOException | ClosedByInterruptException e) { - return; - } catch (Exception e) { - if (!mMission.running || super.isInterrupted()) return; - err = e; - } - } - - // give up - mMission.notifyError(mErrCode, err); - } - - private void tryRecover() throws ExtractionException, IOException, HttpError { - if (mExtractor == null) { - try { - StreamingService svr = NewPipe.getServiceByUrl(mMission.source); - mExtractor = svr.getStreamExtractor(mMission.source); - mExtractor.fetchPage(); - } catch (ExtractionException e) { - mExtractor = null; - throw e; - } - } - - // maybe the following check is redundant - if (!mMission.running || super.isInterrupted()) return; - - if (!mNotInitialized) { - // set the current download url to null in case if the recovery - // process is canceled. Next time start() method is called the - // recovery will be executed, saving time - mMission.urls[mMission.current] = null; - - mRecovery = mMission.recoveryInfo[mMission.current]; - resolveStream(); - return; - } - - Log.w(TAG, "mission is not fully initialized, this will take a while"); - - try { - for (; mMission.current < mMission.urls.length; mMission.current++) { - mRecovery = mMission.recoveryInfo[mMission.current]; - - if (test()) continue; - if (!mMission.running) return; - - resolveStream(); - if (!mMission.running) return; - - // before continue, check if the current stream was resolved - if (mMission.urls[mMission.current] == null) { - break; - } - } - } finally { - mMission.current = 0; - } - - mMission.writeThisToFile(); - - if (!mMission.running || super.isInterrupted()) return; - - mMission.running = false; - mMission.start(); - } - - private void resolveStream() throws IOException, ExtractionException, HttpError { - // FIXME: this getErrorMessage() always returns "video is unavailable" - /*if (mExtractor.getErrorMessage() != null) { - mMission.notifyError(mErrCode, new ExtractionException(mExtractor.getErrorMessage())); - return; - }*/ - - String url = null; - - switch (mRecovery.getKind()) { - case 'a': - for (AudioStream audio : mExtractor.getAudioStreams()) { - if (audio.average_bitrate == mRecovery.getDesiredBitrate() && audio.getFormat() == mRecovery.getFormat()) { - url = audio.getUrl(); - break; - } - } - break; - case 'v': - List videoStreams; - if (mRecovery.isDesired2()) - videoStreams = mExtractor.getVideoOnlyStreams(); - else - videoStreams = mExtractor.getVideoStreams(); - for (VideoStream video : videoStreams) { - if (video.resolution.equals(mRecovery.getDesired()) && video.getFormat() == mRecovery.getFormat()) { - url = video.getUrl(); - break; - } - } - break; - case 's': - for (SubtitlesStream subtitles : mExtractor.getSubtitles(mRecovery.getFormat())) { - String tag = subtitles.getLanguageTag(); - if (tag.equals(mRecovery.getDesired()) && subtitles.isAutoGenerated() == mRecovery.isDesired2()) { - url = subtitles.getUrl(); - break; - } - } - break; - default: - throw new RuntimeException("Unknown stream type"); - } - - resolve(url); - } - - private void resolve(String url) throws IOException, HttpError { - if (mRecovery.getValidateCondition() == null) { - Log.w(TAG, "validation condition not defined, the resource can be stale"); - } - - if (mMission.unknownLength || mRecovery.getValidateCondition() == null) { - recover(url, false); - return; - } - - /////////////////////////////////////////////////////////////////////// - ////// Validate the http resource doing a range request - ///////////////////// - try { - mConn = mMission.openConnection(url, true, mMission.length - 10, mMission.length); - mConn.setRequestProperty("If-Range", mRecovery.getValidateCondition()); - mMission.establishConnection(mID, mConn); - - int code = mConn.getResponseCode(); - - switch (code) { - case 200: - case 413: - // stale - recover(url, true); - return; - case 206: - // in case of validation using the Last-Modified date, check the resource length - long[] contentRange = parseContentRange(mConn.getHeaderField("Content-Range")); - boolean lengthMismatch = contentRange[2] != -1 && contentRange[2] != mMission.length; - - recover(url, lengthMismatch); - return; - } - - throw new HttpError(code); - } finally { - disconnect(); - } - } - - private void recover(String url, boolean stale) { - Log.i(TAG, - String.format("recover() name=%s isStale=%s url=%s", mMission.storage.getName(), stale, url) - ); - - mMission.urls[mMission.current] = url; - - if (url == null) { - mMission.urls = new String[0]; - mMission.notifyError(ERROR_RESOURCE_GONE, null); - return; - } - - if (mNotInitialized) return; - - if (stale) { - mMission.resetState(false, false, DownloadMission.ERROR_NOTHING); - } - - mMission.writeThisToFile(); - - if (!mMission.running || super.isInterrupted()) return; - - mMission.running = false; - mMission.start(); - } - - private long[] parseContentRange(String value) { - long[] range = new long[3]; - - if (value == null) { - // this never should happen - return range; - } - - try { - value = value.trim(); - - if (!value.startsWith("bytes")) { - return range;// unknown range type - } - - int space = value.lastIndexOf(' ') + 1; - int dash = value.indexOf('-', space) + 1; - int bar = value.indexOf('/', dash); - - // start - range[0] = Long.parseLong(value.substring(space, dash - 1)); - - // end - range[1] = Long.parseLong(value.substring(dash, bar)); - - // resource length - value = value.substring(bar + 1); - if (value.equals("*")) { - range[2] = -1;// unknown length received from the server but should be valid - } else { - range[2] = Long.parseLong(value); - } - } catch (Exception e) { - // nothing to do - } - - return range; - } - - private boolean test() { - if (mMission.urls[mMission.current] == null) return false; - - try { - mConn = mMission.openConnection(mMission.urls[mMission.current], true, -1, -1); - mMission.establishConnection(mID, mConn); - - if (mConn.getResponseCode() == 200) return true; - } catch (Exception e) { - // nothing to do - } finally { - disconnect(); - } - - return false; - } - - private void disconnect() { - try { - try { - mConn.getInputStream().close(); - } finally { - mConn.disconnect(); - } - } catch (Exception e) { - // nothing to do - } finally { - mConn = null; - } - } - - @Override - public void interrupt() { - super.interrupt(); - if (mConn != null) disconnect(); - } -} +package us.shandian.giga.get; + +import android.util.Log; + +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.StreamingService; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.stream.AudioStream; +import org.schabi.newpipe.extractor.stream.StreamExtractor; +import org.schabi.newpipe.extractor.stream.SubtitlesStream; +import org.schabi.newpipe.extractor.stream.VideoStream; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.HttpURLConnection; +import java.nio.channels.ClosedByInterruptException; +import java.util.List; + +import us.shandian.giga.get.DownloadMission.HttpError; + +import static us.shandian.giga.get.DownloadMission.ERROR_RESOURCE_GONE; + +public class DownloadMissionRecover extends Thread { + private static final String TAG = "DownloadMissionRecover"; + static final int mID = -3; + + private final DownloadMission mMission; + private final boolean mNotInitialized; + + private final int mErrCode; + + private HttpURLConnection mConn; + private MissionRecoveryInfo mRecovery; + private StreamExtractor mExtractor; + + DownloadMissionRecover(DownloadMission mission, int errCode) { + mMission = mission; + mNotInitialized = mission.blocks == null && mission.current == 0; + mErrCode = errCode; + } + + @Override + public void run() { + if (mMission.source == null) { + mMission.notifyError(mErrCode, null); + return; + } + + Exception err = null; + int attempt = 0; + + while (attempt++ < mMission.maxRetry) { + try { + tryRecover(); + return; + } catch (InterruptedIOException | ClosedByInterruptException e) { + return; + } catch (Exception e) { + if (!mMission.running || super.isInterrupted()) return; + err = e; + } + } + + // give up + mMission.notifyError(mErrCode, err); + } + + private void tryRecover() throws ExtractionException, IOException, HttpError { + if (mExtractor == null) { + try { + StreamingService svr = NewPipe.getServiceByUrl(mMission.source); + mExtractor = svr.getStreamExtractor(mMission.source); + mExtractor.fetchPage(); + } catch (ExtractionException e) { + mExtractor = null; + throw e; + } + } + + // maybe the following check is redundant + if (!mMission.running || super.isInterrupted()) return; + + if (!mNotInitialized) { + // set the current download url to null in case if the recovery + // process is canceled. Next time start() method is called the + // recovery will be executed, saving time + mMission.urls[mMission.current] = null; + + mRecovery = mMission.recoveryInfo[mMission.current]; + resolveStream(); + return; + } + + Log.w(TAG, "mission is not fully initialized, this will take a while"); + + try { + for (; mMission.current < mMission.urls.length; mMission.current++) { + mRecovery = mMission.recoveryInfo[mMission.current]; + + if (test()) continue; + if (!mMission.running) return; + + resolveStream(); + if (!mMission.running) return; + + // before continue, check if the current stream was resolved + if (mMission.urls[mMission.current] == null) { + break; + } + } + } finally { + mMission.current = 0; + } + + mMission.writeThisToFile(); + + if (!mMission.running || super.isInterrupted()) return; + + mMission.running = false; + mMission.start(); + } + + private void resolveStream() throws IOException, ExtractionException, HttpError { + // FIXME: this getErrorMessage() always returns "video is unavailable" + /*if (mExtractor.getErrorMessage() != null) { + mMission.notifyError(mErrCode, new ExtractionException(mExtractor.getErrorMessage())); + return; + }*/ + + String url = null; + + switch (mRecovery.getKind()) { + case 'a': + for (AudioStream audio : mExtractor.getAudioStreams()) { + if (audio.average_bitrate == mRecovery.getDesiredBitrate() && audio.getFormat() == mRecovery.getFormat()) { + url = audio.getUrl(); + break; + } + } + break; + case 'v': + List videoStreams; + if (mRecovery.isDesired2()) + videoStreams = mExtractor.getVideoOnlyStreams(); + else + videoStreams = mExtractor.getVideoStreams(); + for (VideoStream video : videoStreams) { + if (video.resolution.equals(mRecovery.getDesired()) && video.getFormat() == mRecovery.getFormat()) { + url = video.getUrl(); + break; + } + } + break; + case 's': + for (SubtitlesStream subtitles : mExtractor.getSubtitles(mRecovery.getFormat())) { + String tag = subtitles.getLanguageTag(); + if (tag.equals(mRecovery.getDesired()) && subtitles.isAutoGenerated() == mRecovery.isDesired2()) { + url = subtitles.getUrl(); + break; + } + } + break; + default: + throw new RuntimeException("Unknown stream type"); + } + + resolve(url); + } + + private void resolve(String url) throws IOException, HttpError { + if (mRecovery.getValidateCondition() == null) { + Log.w(TAG, "validation condition not defined, the resource can be stale"); + } + + if (mMission.unknownLength || mRecovery.getValidateCondition() == null) { + recover(url, false); + return; + } + + /////////////////////////////////////////////////////////////////////// + ////// Validate the http resource doing a range request + ///////////////////// + try { + mConn = mMission.openConnection(url, true, mMission.length - 10, mMission.length); + mConn.setRequestProperty("If-Range", mRecovery.getValidateCondition()); + mMission.establishConnection(mID, mConn); + + int code = mConn.getResponseCode(); + + switch (code) { + case 200: + case 413: + // stale + recover(url, true); + return; + case 206: + // in case of validation using the Last-Modified date, check the resource length + long[] contentRange = parseContentRange(mConn.getHeaderField("Content-Range")); + boolean lengthMismatch = contentRange[2] != -1 && contentRange[2] != mMission.length; + + recover(url, lengthMismatch); + return; + } + + throw new HttpError(code); + } finally { + disconnect(); + } + } + + private void recover(String url, boolean stale) { + Log.i(TAG, + String.format("recover() name=%s isStale=%s url=%s", mMission.storage.getName(), stale, url) + ); + + mMission.urls[mMission.current] = url; + + if (url == null) { + mMission.urls = new String[0]; + mMission.notifyError(ERROR_RESOURCE_GONE, null); + return; + } + + if (mNotInitialized) return; + + if (stale) { + mMission.resetState(false, false, DownloadMission.ERROR_NOTHING); + } + + mMission.writeThisToFile(); + + if (!mMission.running || super.isInterrupted()) return; + + mMission.running = false; + mMission.start(); + } + + private long[] parseContentRange(String value) { + long[] range = new long[3]; + + if (value == null) { + // this never should happen + return range; + } + + try { + value = value.trim(); + + if (!value.startsWith("bytes")) { + return range;// unknown range type + } + + int space = value.lastIndexOf(' ') + 1; + int dash = value.indexOf('-', space) + 1; + int bar = value.indexOf('/', dash); + + // start + range[0] = Long.parseLong(value.substring(space, dash - 1)); + + // end + range[1] = Long.parseLong(value.substring(dash, bar)); + + // resource length + value = value.substring(bar + 1); + if (value.equals("*")) { + range[2] = -1;// unknown length received from the server but should be valid + } else { + range[2] = Long.parseLong(value); + } + } catch (Exception e) { + // nothing to do + } + + return range; + } + + private boolean test() { + if (mMission.urls[mMission.current] == null) return false; + + try { + mConn = mMission.openConnection(mMission.urls[mMission.current], true, -1, -1); + mMission.establishConnection(mID, mConn); + + if (mConn.getResponseCode() == 200) return true; + } catch (Exception e) { + // nothing to do + } finally { + disconnect(); + } + + return false; + } + + private void disconnect() { + try { + try { + mConn.getInputStream().close(); + } finally { + mConn.disconnect(); + } + } catch (Exception e) { + // nothing to do + } finally { + mConn = null; + } + } + + @Override + public void interrupt() { + super.interrupt(); + if (mConn != null) disconnect(); + } +} diff --git a/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java b/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java index 9cb40cb32..eed5db463 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java @@ -21,7 +21,7 @@ import static us.shandian.giga.get.DownloadMission.ERROR_HTTP_FORBIDDEN; * Single-threaded fallback mode */ public class DownloadRunnableFallback extends Thread { - private static final String TAG = "DownloadRunnableFallbac"; + private static final String TAG = "DownloadRunnableFallback"; private final DownloadMission mMission; diff --git a/app/src/main/java/us/shandian/giga/get/FinishedMission.java b/app/src/main/java/us/shandian/giga/get/FinishedMission.java index 6bc5423b8..29f3c6296 100644 --- a/app/src/main/java/us/shandian/giga/get/FinishedMission.java +++ b/app/src/main/java/us/shandian/giga/get/FinishedMission.java @@ -1,18 +1,18 @@ -package us.shandian.giga.get; - -import androidx.annotation.NonNull; - -public class FinishedMission extends Mission { - - public FinishedMission() { - } - - public FinishedMission(@NonNull DownloadMission mission) { - source = mission.source; - length = mission.length; - timestamp = mission.timestamp; - kind = mission.kind; - storage = mission.storage; - } - -} +package us.shandian.giga.get; + +import androidx.annotation.NonNull; + +public class FinishedMission extends Mission { + + public FinishedMission() { + } + + public FinishedMission(@NonNull DownloadMission mission) { + source = mission.source; + length = mission.length; + timestamp = mission.timestamp; + kind = mission.kind; + storage = mission.storage; + } + +} diff --git a/app/src/main/java/us/shandian/giga/get/Mission.java b/app/src/main/java/us/shandian/giga/get/Mission.java index ff1319884..ecb0eaebd 100644 --- a/app/src/main/java/us/shandian/giga/get/Mission.java +++ b/app/src/main/java/us/shandian/giga/get/Mission.java @@ -1,64 +1,64 @@ -package us.shandian.giga.get; - -import androidx.annotation.NonNull; - -import java.io.Serializable; -import java.util.Calendar; - -import us.shandian.giga.io.StoredFileHelper; - -public abstract class Mission implements Serializable { - private static final long serialVersionUID = 1L;// last bump: 27 march 2019 - - /** - * Source url of the resource - */ - public String source; - - /** - * Length of the current resource - */ - public long length; - - /** - * creation timestamp (and maybe unique identifier) - */ - public long timestamp; - - /** - * pre-defined content type - */ - public char kind; - - /** - * The downloaded file - */ - public StoredFileHelper storage; - - public long getTimestamp() { - return timestamp; - } - - /** - * Delete the downloaded file - * - * @return {@code true] if and only if the file is successfully deleted, otherwise, {@code false} - */ - public boolean delete() { - if (storage != null) return storage.delete(); - return true; - } - - /** - * Indicate if this mission is deleted whatever is stored - */ - public transient boolean deleted = false; - - @NonNull - @Override - public String toString() { - Calendar calendar = Calendar.getInstance(); - calendar.setTimeInMillis(timestamp); - return "[" + calendar.getTime().toString() + "] " + (storage.isInvalid() ? storage.getName() : storage.getUri()); - } -} +package us.shandian.giga.get; + +import androidx.annotation.NonNull; + +import java.io.Serializable; +import java.util.Calendar; + +import us.shandian.giga.io.StoredFileHelper; + +public abstract class Mission implements Serializable { + private static final long serialVersionUID = 1L;// last bump: 27 march 2019 + + /** + * Source url of the resource + */ + public String source; + + /** + * Length of the current resource + */ + public long length; + + /** + * creation timestamp (and maybe unique identifier) + */ + public long timestamp; + + /** + * pre-defined content type + */ + public char kind; + + /** + * The downloaded file + */ + public StoredFileHelper storage; + + public long getTimestamp() { + return timestamp; + } + + /** + * Delete the downloaded file + * + * @return {@code true] if and only if the file is successfully deleted, otherwise, {@code false} + */ + public boolean delete() { + if (storage != null) return storage.delete(); + return true; + } + + /** + * Indicate if this mission is deleted whatever is stored + */ + public transient boolean deleted = false; + + @NonNull + @Override + public String toString() { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(timestamp); + return "[" + calendar.getTime().toString() + "] " + (storage.isInvalid() ? storage.getName() : storage.getUri()); + } +} diff --git a/app/src/main/java/us/shandian/giga/io/ProgressReport.java b/app/src/main/java/us/shandian/giga/io/ProgressReport.java index 14ae9ded9..e382747f6 100644 --- a/app/src/main/java/us/shandian/giga/io/ProgressReport.java +++ b/app/src/main/java/us/shandian/giga/io/ProgressReport.java @@ -1,11 +1,11 @@ -package us.shandian.giga.io; - -public interface ProgressReport { - - /** - * Report the size of the new file - * - * @param progress the new size - */ - void report(long progress); +package us.shandian.giga.io; + +public interface ProgressReport { + + /** + * Report the size of the new file + * + * @param progress the new size + */ + void report(long progress); } \ No newline at end of file diff --git a/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java b/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java index 04958c495..dc46ced5d 100644 --- a/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java +++ b/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java @@ -1,44 +1,44 @@ -package us.shandian.giga.postprocessing; - -import androidx.annotation.NonNull; - -import org.schabi.newpipe.streams.OggFromWebMWriter; -import org.schabi.newpipe.streams.io.SharpStream; - -import java.io.IOException; -import java.nio.ByteBuffer; - -class OggFromWebmDemuxer extends Postprocessing { - - OggFromWebmDemuxer() { - super(true, true, ALGORITHM_OGG_FROM_WEBM_DEMUXER); - } - - @Override - boolean test(SharpStream... sources) throws IOException { - ByteBuffer buffer = ByteBuffer.allocate(4); - sources[0].read(buffer.array()); - - // youtube uses WebM as container, but the file extension (format suffix) is "*.opus" - // check if the file is a webm/mkv file before proceed - - switch (buffer.getInt()) { - case 0x1a45dfa3: - return true;// webm/mkv - case 0x4F676753: - return false;// ogg - } - - throw new UnsupportedOperationException("file not recognized, failed to demux the audio stream"); - } - - @Override - int process(SharpStream out, @NonNull SharpStream... sources) throws IOException { - OggFromWebMWriter demuxer = new OggFromWebMWriter(sources[0], out); - demuxer.parseSource(); - demuxer.selectTrack(0); - demuxer.build(); - - return OK_RESULT; - } -} +package us.shandian.giga.postprocessing; + +import androidx.annotation.NonNull; + +import org.schabi.newpipe.streams.OggFromWebMWriter; +import org.schabi.newpipe.streams.io.SharpStream; + +import java.io.IOException; +import java.nio.ByteBuffer; + +class OggFromWebmDemuxer extends Postprocessing { + + OggFromWebmDemuxer() { + super(true, true, ALGORITHM_OGG_FROM_WEBM_DEMUXER); + } + + @Override + boolean test(SharpStream... sources) throws IOException { + ByteBuffer buffer = ByteBuffer.allocate(4); + sources[0].read(buffer.array()); + + // youtube uses WebM as container, but the file extension (format suffix) is "*.opus" + // check if the file is a webm/mkv file before proceed + + switch (buffer.getInt()) { + case 0x1a45dfa3: + return true;// webm/mkv + case 0x4F676753: + return false;// ogg + } + + throw new UnsupportedOperationException("file not recognized, failed to demux the audio stream"); + } + + @Override + int process(SharpStream out, @NonNull SharpStream... sources) throws IOException { + OggFromWebMWriter demuxer = new OggFromWebMWriter(sources[0], out); + demuxer.parseSource(); + demuxer.selectTrack(0); + demuxer.build(); + + return OK_RESULT; + } +} diff --git a/app/src/main/java/us/shandian/giga/ui/common/Deleter.java b/app/src/main/java/us/shandian/giga/ui/common/Deleter.java index 1d57605b9..b42ebbeb4 100644 --- a/app/src/main/java/us/shandian/giga/ui/common/Deleter.java +++ b/app/src/main/java/us/shandian/giga/ui/common/Deleter.java @@ -1,138 +1,138 @@ -package us.shandian.giga.ui.common; - -import android.content.Context; -import android.content.Intent; -import android.graphics.Color; -import android.os.Handler; -import android.view.View; - -import com.google.android.material.snackbar.Snackbar; - -import org.schabi.newpipe.R; - -import java.util.ArrayList; - -import us.shandian.giga.get.FinishedMission; -import us.shandian.giga.get.Mission; -import us.shandian.giga.service.DownloadManager; -import us.shandian.giga.service.DownloadManager.MissionIterator; -import us.shandian.giga.ui.adapter.MissionAdapter; - -public class Deleter { - private static final int TIMEOUT = 5000;// ms - private static final int DELAY = 350;// ms - private static final int DELAY_RESUME = 400;// ms - - private Snackbar snackbar; - private ArrayList items; - private boolean running = true; - - private final Context mContext; - private final MissionAdapter mAdapter; - private final DownloadManager mDownloadManager; - private final MissionIterator mIterator; - private final Handler mHandler; - private final View mView; - - private final Runnable rShow; - private final Runnable rNext; - private final Runnable rCommit; - - public Deleter(View v, Context c, MissionAdapter a, DownloadManager d, MissionIterator i, Handler h) { - mView = v; - mContext = c; - mAdapter = a; - mDownloadManager = d; - mIterator = i; - mHandler = h; - - // use variables to know the reference of the lambdas - rShow = this::show; - rNext = this::next; - rCommit = this::commit; - - items = new ArrayList<>(2); - } - - public void append(Mission item) { - mIterator.hide(item); - items.add(0, item); - - show(); - } - - private void forget() { - mIterator.unHide(items.remove(0)); - mAdapter.applyChanges(); - - show(); - } - - private void show() { - if (items.size() < 1) return; - - pause(); - running = true; - - mHandler.postDelayed(rNext, DELAY); - } - - private void next() { - if (items.size() < 1) return; - - String msg = mContext.getString(R.string.file_deleted).concat(":\n").concat(items.get(0).storage.getName()); - - snackbar = Snackbar.make(mView, msg, Snackbar.LENGTH_INDEFINITE); - snackbar.setAction(R.string.undo, s -> forget()); - snackbar.setActionTextColor(Color.YELLOW); - snackbar.show(); - - mHandler.postDelayed(rCommit, TIMEOUT); - } - - private void commit() { - if (items.size() < 1) return; - - while (items.size() > 0) { - Mission mission = items.remove(0); - if (mission.deleted) continue; - - mIterator.unHide(mission); - mDownloadManager.deleteMission(mission); - - if (mission instanceof FinishedMission) { - mContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, mission.storage.getUri())); - } - break; - } - - if (items.size() < 1) { - pause(); - return; - } - - show(); - } - - public void pause() { - running = false; - mHandler.removeCallbacks(rNext); - mHandler.removeCallbacks(rShow); - mHandler.removeCallbacks(rCommit); - if (snackbar != null) snackbar.dismiss(); - } - - public void resume() { - if (running) return; - mHandler.postDelayed(rShow, DELAY_RESUME); - } - - public void dispose() { - if (items.size() < 1) return; - - pause(); - - for (Mission mission : items) mDownloadManager.deleteMission(mission); - items = null; - } -} +package us.shandian.giga.ui.common; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.os.Handler; +import android.view.View; + +import com.google.android.material.snackbar.Snackbar; + +import org.schabi.newpipe.R; + +import java.util.ArrayList; + +import us.shandian.giga.get.FinishedMission; +import us.shandian.giga.get.Mission; +import us.shandian.giga.service.DownloadManager; +import us.shandian.giga.service.DownloadManager.MissionIterator; +import us.shandian.giga.ui.adapter.MissionAdapter; + +public class Deleter { + private static final int TIMEOUT = 5000;// ms + private static final int DELAY = 350;// ms + private static final int DELAY_RESUME = 400;// ms + + private Snackbar snackbar; + private ArrayList items; + private boolean running = true; + + private final Context mContext; + private final MissionAdapter mAdapter; + private final DownloadManager mDownloadManager; + private final MissionIterator mIterator; + private final Handler mHandler; + private final View mView; + + private final Runnable rShow; + private final Runnable rNext; + private final Runnable rCommit; + + public Deleter(View v, Context c, MissionAdapter a, DownloadManager d, MissionIterator i, Handler h) { + mView = v; + mContext = c; + mAdapter = a; + mDownloadManager = d; + mIterator = i; + mHandler = h; + + // use variables to know the reference of the lambdas + rShow = this::show; + rNext = this::next; + rCommit = this::commit; + + items = new ArrayList<>(2); + } + + public void append(Mission item) { + mIterator.hide(item); + items.add(0, item); + + show(); + } + + private void forget() { + mIterator.unHide(items.remove(0)); + mAdapter.applyChanges(); + + show(); + } + + private void show() { + if (items.size() < 1) return; + + pause(); + running = true; + + mHandler.postDelayed(rNext, DELAY); + } + + private void next() { + if (items.size() < 1) return; + + String msg = mContext.getString(R.string.file_deleted).concat(":\n").concat(items.get(0).storage.getName()); + + snackbar = Snackbar.make(mView, msg, Snackbar.LENGTH_INDEFINITE); + snackbar.setAction(R.string.undo, s -> forget()); + snackbar.setActionTextColor(Color.YELLOW); + snackbar.show(); + + mHandler.postDelayed(rCommit, TIMEOUT); + } + + private void commit() { + if (items.size() < 1) return; + + while (items.size() > 0) { + Mission mission = items.remove(0); + if (mission.deleted) continue; + + mIterator.unHide(mission); + mDownloadManager.deleteMission(mission); + + if (mission instanceof FinishedMission) { + mContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, mission.storage.getUri())); + } + break; + } + + if (items.size() < 1) { + pause(); + return; + } + + show(); + } + + public void pause() { + running = false; + mHandler.removeCallbacks(rNext); + mHandler.removeCallbacks(rShow); + mHandler.removeCallbacks(rCommit); + if (snackbar != null) snackbar.dismiss(); + } + + public void resume() { + if (running) return; + mHandler.postDelayed(rShow, DELAY_RESUME); + } + + public void dispose() { + if (items.size() < 1) return; + + pause(); + + for (Mission mission : items) mDownloadManager.deleteMission(mission); + items = null; + } +} diff --git a/app/src/main/res/layout/missions_header.xml b/app/src/main/res/layout/missions_header.xml index 2eb38c1fc..45c8b95b8 100644 --- a/app/src/main/res/layout/missions_header.xml +++ b/app/src/main/res/layout/missions_header.xml @@ -1,29 +1,29 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index fccf8cf1b..14bbb79a9 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -268,8 +268,8 @@ لم يتم العثور على مشغل بث (يمكنك تثبيت VLC لتشغيله). استيراد قاعدة البيانات تصدير قاعدة البيانات - يتجاوز السجل والاشتراكات الحالية - تصدير السجل، الإشتراكات وقوائم التشغيل + يلغي السجل الحالي والاشتراكات وقوائم التشغيل والإعدادات (اختياريًا) + تصدير قوائم تشغيل, الاشتراكات, والإعدادات عرض المعلومات إضافة إلى تحليل diff --git a/app/src/main/res/values-b+ast/strings.xml b/app/src/main/res/values-b+ast/strings.xml index faafa7538..e9a8c8fd7 100644 --- a/app/src/main/res/values-b+ast/strings.xml +++ b/app/src/main/res/values-b+ast/strings.xml @@ -1,20 +1,20 @@ - %1$s visualizaciones + %1$s vistes Espublizóse\'l %1$s Encaboxar - Abrir nun restolador web + Abrir nun restolador Compartir Axustes - ¿Quixesti dicir «%1$s»\? + ¿Quxesti dicir «%1$s»\? Usar un reproductor esternu de videu Usar un reproductor esternu d\'audiu Resolución predeterminada Reproducir en Kodi Amosar la opción «Reproducir en Kodi» - Amuesa una opción pa reproducir un videu pel centru multimedia Kodi + Amuesa una opción pa reproducir vídeos pel centru multimedia Kodi Audiu - Formatu predetermináu d\'audiu + Formatu d\'audiu predetermináu Estilu Escuridá Claridá @@ -26,16 +26,16 @@ En direuto Fallu Nun pudieron cargase toles miniatures - Nun pudo descifrase la robla de la URL del videu + Nun pudo sabese la robla de la URL del videu Nun pudo analizase\'l sitiu web - Entá nun se sofiten los fluxos en direuto - Nun pudo consiguise dengún fluxu - Perdona pero eso nun debió asoceder. + Los fluxos en direuto entá nun se sofiten + Nun pudo consiguise nengún fluxu + Buff... Esto nun debió asoceder. Perdona mas asocedió daqué malo. Información: Detalles: Préstames - Usar TOR + Usar Tor Creóse\'l direutoriu de descarga «%1$s» Videu Audiu @@ -48,73 +48,73 @@ Espera… Copióse al cartafueyu Tarrezmes - Formatu predetermináu de videu + Formatu de videu predetermináu Prietu Canal - Mil - Mill. - Mil mill. - Precísase esti permisu -\np\'abrir nel mou ventanu + mil + mill. + mil mill. + Precísase esti permisu p\'abrir +\nnel mou ventanu Retu de reCAPTCHA - Solicitóse\'l retu de reCAPTCHA + Solicitóse un retu de reCAPTCHA En segundu planu Ventanu Resolución predeterminada del ventanu Amosar resoluciones más altes - Namái dalgunos preseos puen reproducir vídeos en 2K/4K + Namás dalgunos preseos puen reproducir videos en 2K/4K Llimpiar Quita l\'audiu en dalgunes resoluciones - Usa xestos pa controlar el brilléu y volume del reproductor - Guetar suxerencies - Amuesa suxerencies al guetar + Usa xestos pa controlar el volume y brillu del reproductor + Suxerencies de busca + Amosar suxerencies al buscar Soscribise Nun pudo anovase la soscripción Soscripciones Novedaes - Historial de gueta - - Sigue cola reproducción dempués de les interrupciones (llamaes telefóniques, por exemplu) + Historial de busques + Siguir cola reproducción + Sigue cola reproducción dempués de les interrupciones (por exemplu, llamaes) Reproductor Comportamientu Historial y caché La meyor resolución - Avisu de NewPipe + Avisu permanente de NewPipe Nun hai resultaos Equí nun hai más que grillos %s soscriptor %s soscriptores - Nun hai visualizaciones + Nun hai vistes - %s visualización - %s visualizaciones + %s vista + %s vistes Descarga - Caráuteres almitíos nos nomes de ficheros + Caráuteres permitíos nos nomes de los ficheros Lletres y díxitos La mayoría de caráuteres especiales Tocante a NewPipe Axustes Tocante a Llicencies de terceros - © %1$s por %2$s so %3$s + © %1$s por %2$s baxo %3$s Nun pudo cargase la llicencia Tocante a Collaboradores Llicencies Ver en GitHub Llicencia de NewPipe - Si sabes traducir, quies encuriosar el códigu, amestar carauteríques o proponer cambeos nel diseñu, vamos agradecételo siempres. ¡Cuánto más, meyor! - Lleer la llicencia + Si tienes idees, quies traducir, facer dalgún cambéu nel diseñu, acuriosar poco o muncho\'l códigu... Agradecemos l\'ayuda. ¡Cuanto más se faiga, meyor! + Lleer Collaboración Historial Vióse Historial L\'historial ta baleru - ¿Quies desaniciar esti elementu del historial de gueta\? + ¿Quies desaniciar esti elementu del historial de busques\? Reproducir too Nun pudo reproducise esti fluxu Asocedió un fallu irrecuperable del reproductor @@ -123,14 +123,14 @@ Esbilla d\'un quioscu Quioscu Tendencies - Destácase + Los 50 destacaos Detalles - Novedaes + Novedaes destacaes [Desconozse] Reproducir en segundu planu - + Reproducir nun ventanu Donación - + NewPipe ta desendolcáu por xente voluntario que pasa\'l tiempu llibre ufriéndote la meyor esperiencia d\'usuariu. Devolvi\'l favor p\'ayudar a los desendolcadores p\'ameyorar NewPipe tovía más mentanto esfruten d\'una taza de café. Donar Sitiu web Visita\'l sitiu web de NewPipe pa más información y noticies. @@ -138,43 +138,43 @@ Siempres Importar una base de datos Esportar la base de datos - Anula l\'historial y les soscripciones actuales - Esporta l\'historial, les soscripciones y llistes de reproducción. - URL nun ye válida + Anula l\'historial, les soscripciones, les llistes de reproducción y (opcionalmente) los axustes actuales + Esporta l\'historial, les soscripciones, les llistes de reproducción y los axustes + La URL nun ye válida Esto va anular la configuración actual. Amosar la información - Llistes de reproducción + Llistes en marcadores Crear Escartar ¿De xuru que quies desaniciar tolos elementos del historial\? - Equí va apaecer dalgo ceo ;D - Llista nueva de repoducción + Equí va apaecer daqué pronto ;D + Llista de reproducción nueva Nome - Amestar a una llista de repoducción - ¿Desaniciar esta llista de reproducción\? + Amestar a una llista de reproducción + ¿Desanicair esta llista de reproducción\? Nun pudo desaniciase la llista de reproducción. Ensin sotítulos Axustar Rellenar Zoom - Ficheru - El ficheru nun esiste o falta\'l permisu d\'escritura o llectura + FIcheru + El ficheru nun esiste o a l\'aplicación fálta-y el permisu de llectura/escritura Asocedió un fallu: %1$s Importación/Esportación Importando… Esportando… Nun pudieron esportase les soscripciones - Aición preferida d\'apertura - ¿Quies importar los axustes tamién\? - Lleer la política de privacidá + Aición d\'apertura preferida + ¿Quies tamién importar los axustes\? + Lleer Canales Llistes de reproducción Pistes Usuarios Llingüeta nueva Usa xestos pa controlar el volume del reproductor - Usa xestos pa controlar el brilléu del reproductor - Reafitar + Usa xestos pa controlar el brillu del reproductor + Reafitamientu de valores El númberu de soscriptores nun ta disponible Esbilla Anovamientos @@ -184,10 +184,10 @@ Finó Falló la descarga Hai una descarga en cursu con esti nome - Nun pue crease la carpeta de destín + La carpeta de destín nun pue crease Nun pudo afitase una conexón segura - Nun pue coneutase col sirvidor - Reintentos máximos + Nun pues coneutate al sirvidor + Intentos máximos Eventos Conferencies Reproducción automática @@ -195,50 +195,52 @@ El ficheru movióse o desanicióse Yá esiste un ficheru con esti nome Yá esiste un ficheru baxáu con esti nome - nun pue sobrescribise\'l ficheru + nun pue sobrecribise\'l ficheru Hai una descarga pendiente con esti nome - Escosó l\'espaciu del preséu + Nun queda espaciu nel preséu Escosó\'l tiempu d\'espera de la conexón Nun pudieron importase les soscripciones Sotítulos Aceutar ¿Quies reafitar los valores\? - - El sirvidor nun aceuta descargues multifilu, volvi probar con @string/msg_threads = 1 + Nun pudo atopase\'l sirvidor + El sirvidor nun aceuta descargues multifilu, volvi tentalo con @string/msg_threads = 1 Nun hai comentarios Llimpieza de datos - Amosar comentarios + Amosar los comentarios Desactiva esta opción p\'anubrir los comentarios - Pa cumplir cola GDPR (Regulación Xeneral de Proteición de Datos) europea, pidímoste que revises la política de privacidá de NewPipe. Lléila con procuru. -\nHas aceutala unviándonos un informe de fallos. - - Aición al cambiar a otra aplicación dende\'l reproductor de vídeos principal — %s - El númberu máximu d\'intentos enantes d\'encaboxar la descarga + Pa cumplir cola Regulación Xeneral de Proteición de Datos (RGPD), pidímoste que mires la política de privacidá de NewPipe. Lleila con procuru. +\nHas aceutala pa unvianos informes de fallos. + L\'aición al cambiar a otra aplicación dende\'l reproductor de videu princpial — %s + El númberu máximu d\'intentos pa encaboxar la descarga ¿Quies llimpiar l\'historial de descargues o desaniciar tolos ficheros baxaos\? Esportación anterior - Importar el ficheru - Importa les soscripciones de YouTube baxando\'l ficheru d\'esportación: -\n -\n1.- Vete a esta URL: %1$s -\n2.- Anicia sesión cuando se te pida -\n3.- Debería aniciase una descarga (que ye\'l ficheru d\'esportación) - Importa un perfil de SoundCloud teclexando la URL o la ID de to: -\n1.- Activa\'l mou escritoriu nun restolador web (el sitiu nun ta disponible pa móviles) + Importación d\'un ficheru + Importa les soscripciones de YouTube dende Google Takeout: \n -\n3.- Anicia sesión cuando se te pida -\n2.- Vete a esta URL: %1$s -\n4.- Copia la URL del perfil al que se te redirixa. - LaToID, soundcloud.com/latoid - Cargar miniatures - Desactiva esta opción pa evitar la carga de miniatures y aforrar datos y usu de la memoria. Los cambeos van llimpiar la memoria y la caché d\'imáxenes. - Minimizar al cambiar a otra aplicación +\n1. Vete a esta URL: %1$s +\n2. Anicia sesión cuando se te pida +\n3. Calca en «Tolos datos incluyíos», darréu en «Deseleicionar too», dempués esbilla namás «soscripciones» y calca «Aceutar» +\n4. Calca en «Pasu siguiente» y darréu en «Crear una esportación» +\n5. Calca nel botón «Baxar» dempués de qu\'apaeza y +\n6. A partir del ficheru baxáu, estrái\'l ficheru .json (polo xeneral baxo «YouTube and YouTube Music/subscriptions/subscriptions.json») ya impórtalu equí. + Importa un perfil de SoundCloud teclexando la URL o la to ID: +\n +\n1. Activa\'l «mou d\'escritoriu» nun restolador web (el sitiu nun ta disponible pa móviles) +\n2. Vete a esta URL: %1$s +\n3. Anicia sesión cuando se te pida +\n4. Copia la URL del perfil al que te redirixeron. + soundcloud.com/LaToID + Cargar les miniatures + Desactiva esta opción pa eviar la carga de miniatures y aforrar datos y usu de memoria. Los cambeos llimpien la caché d\'imáxenes temporal y permanente. + Minimizar al cambiar d\'aplicación Minimizar al reproductor en segundu planu Minimizar al reproductor en ventanu - Desoscribise - Escoyeta d\'una llingüeta + Esborrase + Esbilla d\'una llingüeta Siguir cola reproducción Les llingüetes que s\'amuesen na páxina principal - Entrugar ánde baxar + Entrugar ónde baxar Descargues Descargues @@ -247,26 +249,26 @@ Control per xestos del reproductor Cargando\'l conteníu solicitáu - Política de Privacidá de NewPipe + Política de privacidá de NewPipe Control per xestos del volume - Control per xestos del brilléu + Control per xestos del brillu El ficheru nun pue crease El sirvidor nun unvia datos - La llingua va camudar namái que se reanicie l\'aplicación. - Guetar + La llingua va camudar namás que se reanicie l\'aplicación. + Buscar Compartir con - El ficheru yá esiste + Yá esiste\'l ficheru Soscribiéstite L\'historial ta desactiváu Nun hai vídeos - ¿Desaniciar tol historial de guetes\? - ¡Hai un anovamientu disponible pa NewPipe! - Toca pa baxar la versión + ¿Desaniciar tol historial de busques\? + ¡Hai un anovamientu pa NewPipe! + Toca pa baxalu Perdióse\'l progresu porque se desanició\'l ficheru Instancies de PeerTube La instancia yá esiste Llingua de l\'aplicación - La predeterminada del sistema + Lo predeterminao del sistema Vídeos %d segundu @@ -275,14 +277,14 @@ Tempu Tonu Refugar - La URL nun se sofita + La URL nun ta sofitada Reproduciendo en segundu planu Reproduciendo nel mou ventanu - Atroxa llocalmente les consultes de gueta - Los reproductores esternos nun so + Atroxa llocalmente les consultes de busca + Los reproductores esternos nun sofiten esti tipu d\'enllaces Esportóse Importóse - Alvertencia: Nun pudieron importase tolos ficheros. + Alvertencia: nun pudieron importase tolos ficheros. Anovamientos Finó la descarga Nun pudo validase la instancia @@ -299,10 +301,352 @@ %d díes ¿Quies desaniciar esti grupu\? - Llipióse la caché de metadatos - Desanicia los datos de les páxines web na caché + Llimpióse la caché de metadatos + Desanicia tolos datos na caché de les páxines web Llimpiar los metadatos de la caché Llimpióse la caché d\'imáxenes - ¿Instalar l\'aplicación Kore\? - Reproduz un videu cuando s\'invoca a NewPipe dende otra aplicación + ¿Instalar Kore\? + Reproduz un videu al llamar a NewPipe dende otra aplicación + Qué asocedió:\\nSolicitú:\\nLlingua del conteníu:\\nPaís del conteníu:\\nLlingua de l\'aplicación:\\nServiciu:\\nHora en GMT:\\nPaquete:\\nVersión de l\'aplicación:\\nVersión del SO: + Nun s\'atopó nengún reproductor de fluxos (pues instalar VLC pa reproducilos). + Amuesa una miniatura nel fondu de la pantalla de bloquéu y dientro de los avisos + Amosar una miniatura + Páxina d\'una llista de reproducción + Por %s + Canal creada por %s + Avatar de la canal + Esti conteníu entá nun ta sofitáu por NewPipe. +\n +\nQuiciabes nel futuru sí. + ¿Cuides que la carga del feed ye perlenta\? Si ye asina, activa la carga rápida (pues camudalo nos axustes o primiendo\'l botón d\'embaxo). +\n +\nNewPipe ufre dos estratexes de descarga de feeds: +\n• Dir en cata de tola canal de soscripciones, lo cual ye lento pero completo. +\n• Usar un serviciu final dedicáu, lo cual ye rápido mas xeneralmente incompleto. +\n +\nLa diferencia ente los dos ye qu\'al más rápidu, polo xeneral, fálta-y dalguna información como\'l tipu (nun pue estremar ente vídeos en direuto o normales) o la duración de los elementos (que devuelve menos). +\n +\nYouTube ye l\'exemplu d\'un serviciu qu\'ufre esti métodu rápidu col so feed RSS. +\n +\nPolo que tu escueyes según lo que prefieras, si velocidá o información precisa. + Desactivar el mou rápidu + Activar el mou rápidu + Disponible en dalgunos preseos, suel ser muncho más rápido mas podría devolver un númberu llendáu d\'elementos y davezu información completa (por exemplu, falta de la duración, el tipu d\'elementu o l\'estáu de la tresmisión). + Dir en cata de feeds dedicaos cuando seya posible + Anovar siempres + Tiempu que tarda (dende l\'últimu anovamientu) en considerase una soscripción ensin anovar — %s + Llende del anovamientu del feed + Feed + Amosar namás les soscripciones ensin agrupar + Nuevu + El nome del grupu ta baleru + + %d na esbilla + %d na esbilla + + Nun s\'esbilló nenguna soscripción + Esbillar soscripciones + Procesando\'l feed… + Cargando\'l feed… + Númberu d\'elementos que nun cargaron: %d + Últimu anovamientu del feed: %s + Grupos de canales + Pola mor de les torgues d\'ExoPlayer la duración afitóse en %d segundos + Sí, y tamién los vistos parcialmente + Van desaniciase los vídeos que se vieren enantes y dempués d\'amestase a la llista de reproducción. +\n¿De xuru\? ¡Esto nun pue desfacese! + ¿Desaniciar los vídeos vistos\? + Desaniciar lo visto + Escoyeta d\'una instancia + El «Storage Access Framework» permite les descargues nuna tarxeta SD esterna. +\nDalgunos preseos son incompatibles + Usar SAF + Va pidísete l\'allugamientu onde guardar les descargues con cauna. +\nEscueyi SAF si quies guardar les descargues na tarxeta SD esterna + Va pidísete l\'allugamientu onde guardar les descargues con cauna + Posar les descargues + Aniciar les descargues + Namás va executase una descarga al empar + Llendar la cola de descarga + Zarrar + Útil al cambiar a los datos móviles, magar que dalgunes descargues nun puedan suspendese + Torgar nes redes midíes + Parar + Desaniciáronse %1$d descargues + Desaniciar lo baxao + Llimpiar l\'historial de descargues + Esta descarga nun pue recuperase + NewPipe zarróse mentanto trabayaba nel ficheru + Falló\'l posprocesamientu + Nun s\'atopó + El sistema negó\'l permisu + Códigu + Amosar el fallu + Sobrescribir + Xenerar un nome únicu + Finaron %s descargues + El sistema negó l\'aición + Amestar a la cola + recuperando + posprocesando + na cola + posóse + Pendiente + Cambiar la vista + Auto + Mou de la vista de les llistes + Enxamás + Namás na Wi-Fi + Anicia automáticamente la reproducción — %s + Nada + Llendar la resolución al usar los datos móviles + Ensin llende + Reafitar + Pasu + Avance rápidu nos silencios + Separtar (pue causar distorsión) + Controles de la velocidá de reproducción + Ten en cuenta qu\'esta operación pue ser esixente cola rede. +\n +\n¿Quies siguir\? + Esportar a + Importar dende + Importar + Los testos orxinales de los servicios van amosase nos elementos de les tresmisiones + Amosar les marques de tiempu orixinales + Forcia l\'informe d\'esceiciones Rx que nun se puen entregar fuera del ciclu de vida d\'un fragmentu o actividá dempués de desanicialos + Informar de fallos fuera de ciclu + Amosar los escapes de memoria + La supervisión de los escapes de memoria pue facer que l\'aplicación nun respuenda al volquiar la pila + Modifica\'l testu de los sotítulos y el so fondu. Rique\'l reaniciu de l\'aplicación pa que faiga efeutu. + Xeneróse automáticamente + Xeneróse automáticamente (nun s\'atopó\'l xubidor) + Camudóse la miniatura de la llista de reproducción. + Metióse nuna llista de reproducción + Creóse la llista de reproducción + Desaniciar el marcador + Amestar la llista a marcadores + Afitar como la miniatura de la llista + Desactivar el soníu + Activar el soníu + Renomar + Desaniciar + Consiguiendo la información… + Entrugar siempres + Reproductor en ventanu + Reproductor en segundu planu + Reproductor de videu + L\'aición predeterminada al abrir conteníu — %s + Zarrar el caxón + Abrir el caxón + Reproducir equí + Amestóse a la cola + Amestar a la cola + Ten primío p\'amestar a la cola + Axustes del audiu + Desaniciar + Cola de reproducción + Lo que más prestó + Amestóse apocayá + Llocal + El ficheru ZIP nun ye válidu + Entá nun hai llistes en marcadores + Esbilla d\'una llista de reproducción + Entá nun hai soscripciones a canales + Esbilla d\'una canal + Páxina d\'una canal + Páxina del feed + Páxina de soscripciones + Quioscu predetermináu + Páxina de quioscu + Lo más reproducío + Lo último reproducío + ¿Quies desaniciar esti elementu del historial de vídeos vistos\? + Desanicióse l\'elementu + Llimpióse l\'historial + Buscóse + NewPipe ye software copyleft: pues usalu, estudialu, compartilu y ameyoralu como quieras. N\'especial, pues redistribuyilu y/o modificalu baxo los términos de la GNU General Public License según espublizó la Free Software Foundation, quier la versión 3 de la llicencia quier (na to opinión) cualesquier versión posterior. + El proyeutu de NewPipe toma mui en serio la privacidá. Poro, l\'aplicación nun recueye nengún datu ensin el to consentimientu. +\nLa política de privacidá de NewPipe desplica en detalle los datos que s\'unvien y atroxen cuando unvies un informe de casque. + Un aplicación llibre pa ver/sentir plataformes de tresmisión n\'Android. + Abrir el sitiu web + Nun hai nenguna aplicación pa reproducir esti ficheru + Caráuteres de troquéu + Los caráuteres que nun son válidos van trocase por esti valor + Fecho + Primi «Fecho» al resolvelu + Desanicióse 1 elementu. + Defini una capeta de descargues dempués, nos axustes de l\'aplicación + Toca pa los detalles + Descarga de NewPipe + La URL ta mal formada o internet nun ta disponible + El sirvidor nun ta sofitáu + Nome del ficheru + Renomar + Desaniciar too + Desaniciar un elementu + Reproducir + Posar + Aniciar + Vídeos ∞ + +100 vídeos + + %s oyente + %s oyentes + + Naide nun ta sintiendo + + %s espectador + %s espectadores + + Nun hai espectadores + Nun hai soscriptores + Alternar el serviciu, esbillóse: + Concedi l\'accesu al almacenamientu primero + Retentar + Nun pue crease\'l direutoriu de descarga «%1$s» + Arrastra pa reordenar + Informe d\'usuariu + Informar del fallu + (Esperimental) Forcia\'l tráficu de les descargues pente Tor pa más privacidá (la reproducción de vídeos entá nun se sofita). + Avatar del xubidor + Reproducción d\'un videu, duración: + Miniatura del videu + Un comentariu (n\'inglés): + Qué pasó: + Informar + Comprueba si yá esiste un problema qu\'alderique esti casque, por favor. Al crear informes duplicaos faes que perdamos el tiempu que podríemos dedicar a iguar el fallu. + Informar en GitHub + Copiar l\'informe con formatu + Informar d\'esti fallu per corréu + Da\'l permisu p\'amosar NewPipe penriba d\'otres aplicaciones + Nun pudieron lleese les llingüetes guardaes polo que van usase les predeterminaes + Nun hai fluxos disponibles pa baxar + El nome del ficheru nun pue tar baleru + El ficheru/orixe del conteníu nun esiste + La carpeta nun esiste + Nun s\'atoparon fluxos d\'audiu + Nun s\'atoparon fluxos de videu + L\'aplicación ta recuperándose d\'un fallu del reproductor + L\'aplicación/IU cascó + Nun pudo cargase la imaxe + Nun pudo configurase\'l menú de descarga + El conteníu nun ta disponible + Nun pudo analizase dafechu\'l sitiu web + Fallu de la rede + La descarga a la tarxeta SD nun ye posible. ¿Reafitar l\'allugamientu de la carpeta de descarga\? + L\'almacenamientu esternu nun ta disponible + Ayuda + Desanicióse l\'historial de busques. + Desanicia l\'historial de les pallabres clave de busca + Llimpiar l\'historial de busca + Desaniciáronse los puntos de reproducción. + ¿Desaniciar tolos puntos de reproducción\? + Desanicia tolos puntos de reproducción + Desaniciar los puntos de reproducción + Desanicióse l\'historial de vídeos vistos. + ¿Desaniciar tol historial de vídeos vistos\? + Desanicia l\'historial de fluxos reproducíos y el de puntos de reproducción + Llimpiar l\'historial de los vídeos vistos + Llimpia les cookies que NewPipe atrroxa cuando soluciones un reCAPTCHA + Llimpiáronse les cookies de reCAPTCHA + Llimpiar les cookies de reCAPTCHA + Cambiar al reproductor principal + Cambiar al reproductor en ventanu + Cambiar al reproductor en segundu planu + Alternar la orientación + Anovamientos pa NewPipe + Avisos d\'anovamientos + Avisos de los reproductores en segundu planu y en ventanu de NewPipe + Namás una vegada + Desanicióse\'l ficheru + Desfacer + Redimensionáu + Refrescar + Peñerar + Desactivóse + Dempués + Artistes + Álbumes + Canciones + Llista de reproducción + Too + Informe d\'un fallu + Tien una torga por edá. +\n +\nActiva «%1$s» nos axustes de l\'aplicación si quies velu. + YouTube forne\'l «Mou torgáu» qu\'anubre conteníu\'l que seya potencialmente p\'adultos + Activar el «Mou torgáu» de YouTube + Amuesa\'l conteníu que quiciabes nun seya afayadizu pa guaḥes porque tien una llende d\'edá (como +18) + Amestóse a la cola del reproductor en ventanu + Amestóse a la cola del reproductor en segundu planu + Avisu permanente + Depuración + Miscelánea + Ventanu + Namás se sofiten URLs HTTPS + Introduz la URL d\'una instancia + Amiestu d\'una instancia + Atopa les instancies que te presten en %s + Esbilla les instancies de PeerTube que prefieras + Serviciu + Nun pudo reconocese la URL. ¿Abrila con otra aplicación\? + Amuesa\'l mensaxe al primir el nel botón «En segundu planu» o «Ventanu» nel «Details:» de los vídeos + Amosar el mensaxe «Ten primío p\'amestar a la cola» + Amosar los vídeos siguientes y asemeyaos + Descarga + Rexistra los vídeos vistos + Amuesa los indicadores de los puntos de reprodución nes llistes + Puntos de reproducción nes llistes + Restaura l\'últimu puntu de reproducción + Historial de vídeos vistos + Cola automática + Amiesta un videu rellacionáu a la cola cuando se repoduz l\'últimu videu a una cola ensin repitición + Meter les tresmisiones siguientes a cola automática + Va trocase la cola del reproductor activu + Cambiar d\'un reproductor a otru pue trocar la cola + Confirmar la llimpieza de les coles + Duración de la gueta del avance/rebobináu rápidu + La gueta imprecisa permite al reproductor que guete les posiciones más rápido con menos precisión. La gueta de 5, 15 ó 25 segundos nun funciona con esti axuste. + Usar la gueta rápida imprecisa + Acuérdase del últimu tamañu y la última posición del ventanu + Acordase de les propiedaes del ventanu + Fai qu\'Android personalice\'l color del avisu permanente según el color principal de les miniatures (decátate qu\'esto nun ta disponible en tolos preseos) + Avisu con color + Nada + Atroxar nel búfer + Al debalu + Repitir + ¡Pues esbillar tres aiciones como máximu p\'amosar nel avisu permanente! + Edita cada aición d\'embaxo tocando nelles. Esbilla hasta tres p\'amosales nel avisu compautu col usu de los caxellos de la derercha. + Quintu botón d\'aición + Cuartu botón d\'aición + Tercer botón d\'aición + Segundu botón d\'aición + Primer botón d\'aición + Escala les miniatures de los vídeos que s\'amuesen nel avisu permanente de los 16:9 al 1:1 (podría distorsionar les imáxenes) + Afitar la escala 1:1 a les miniatures + Reproducción automática + Camuda les carpetes de descarga pa que faiga efeutu + Escueyi la carpeta de descarga pa los ficheros d\'audiu + Los ficheros d\'audiu baxaos atróxense equí + Carpeta de la descarga d\'audiu + Escueyi la carpeta de descarga pa los ficheros de videu + Los ficheros de videu baxaos atróxense equí + Carpeta de la descarga de vídeos + Meter nuna llista + Principal + Nun pudo camudar la soscripción + Esborriéstite de la canal + Mou ventanu + voltéu + Escoyer un restolador + Amosando los resultaos de: %s + Baxar el ficheru de fluxos + Baxar + Abrir nel mou ventanu + Instalar + Nun s\'atopó nengún reproductor de fluxos. ¿Instalar VLC\? + Toca «Buscar» pa entamar +\n \ No newline at end of file diff --git a/app/src/main/res/values-b+zh+HANS+CN/strings.xml b/app/src/main/res/values-b+zh+HANS+CN/strings.xml index 051677972..8076d279f 100644 --- a/app/src/main/res/values-b+zh+HANS+CN/strings.xml +++ b/app/src/main/res/values-b+zh+HANS+CN/strings.xml @@ -303,8 +303,8 @@ 正在加载请求的内容 导入数据库 导出数据库 - 覆盖当前历史记录和订阅 - 导出历史记录、订阅和播放列表 + 覆盖您的当前播放历史、订阅、播放列表和(可选)设置 + 导出历史记录、订阅、播放列表和设置 导出成功 导入成功 没有有效的ZIP文件 diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index 7c8b37d5d..812a1a815 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -255,7 +255,7 @@ Прыкладанне для прайгравання гэтага файла не ўстаноўлена Аб NewPipe Налады - Аб дадатку + А прыладзе Іншыя ліцэнзіі © %1$s %2$s пад ліцэнзіяй %3$s Не атрымалася загрузіць ліцэнзію @@ -314,7 +314,7 @@ Выдаліць Падрабязнасці Налады аўдыё - Зацісніце, каб дадаць у чаргу + Утрымлівайце, каб дадаць у чаргу Пачаць адсюль у плэеры Пачаць адсюль у фоне Пачаць адсюль у акне @@ -402,7 +402,7 @@ Мяняць гучнасць плэера жэстам Жэст яркасці Мяняць яркасць плэера жэстам - Абнаўлення + Абнаўленні Файл выдалены Апавяшчэнне аб абнаўленні Апавяшчэння аб новай версіі NewPipe @@ -476,7 +476,7 @@ Скончылася вольнае месца на прыладзе Прагрэс страчаны, так як файл быў выдалены Час злучэння выйшла - Вы ўпэўненыя\? + Вы ўпэўнены\? Абмежаваць чаргу загрузкі Толькі адна адначасовая загрузка Пачаць загрузку @@ -492,8 +492,8 @@ Выдаліць ўсе пазіцыі прайгравання Абмежаваны рэжым YouTube Падтрымліваюцца толькі адрасы URL HTTPS - Дадаць інстанцыю - Інстанцыі PeerTube + Дадаць экзэмпляр + Экзэмпляры PeerTube Змяніце папкі загрузкі, каб змены ўступілі ў сілу Вынікі для: %s Мініяцюра відэа ў 1:1 @@ -502,4 +502,58 @@ Кнопка трэцяга дзеяння Кнопка другога дзеяння Кнопка першага дзеяння + Групы каналаў + Як у сістэме + Мова прылады + Выберыце экзэмпляр + Выдалена %1$d загрузак + Выдаліць загружаныя файлы + Арыгінальныя тэксты з сэрвісаў будуць бачны ў ленце элементаў + Ачысціце cookie, якія NewPipe захоўвае пры рашэнні reCAPTCHA + Экзэмпляр ужо існуе + Немагчыма праверыць экзэмпляр + Увесці URL экзэмпляра + Абярыце любімыя экзэмпляры PeerTube + Актыўны плэер быў зменены + Змена плэера можа замяніць вашу чаргу + Запытаць, перш чым ачысціць чаргу + Ніколі + Толькі па Wi-Fi + Паказаць арыгінальны час на элементах + Уключыць гук + Цішына + Дадаць у чаргу + Даданае у чаргу + Чарга прайгравання + Найбольш папулярнае + Лакальнае + Нядаўна дададзенае + Няма закладак у плейлісце + Абярыце плэйліст + Кіёск па змаўчанні + Так + Націсніце \"Так\" калі вырашана + ∞ відэа + 100+ відэа + Багрэпарт на GitHub + Скапіруйце адфарматаваны багрэпарт + Дайце дазвол на адлюстраванне паверх іншых праграм + Дапамога + Выдаліць усе пазіцыі прайгравання\? + Выдаліць пазіцыі прайгравання + Ачысціць reCAPTCHA cookies + reCAPTCHA cookies былі ачышчаны + Выканаўцы + Альбомы + Песні + Відэа + Аўтаматычная чарга + Працягласць перамоткі ўперад/назад + Каляровыя апавяшчэнні + Нічога + Буферызацыя + Ператасаваць + Паўтор + Кнопка пятага дзеяння + Паведамленні \ No newline at end of file diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 2d79893ec..fbfe8eeda 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -258,8 +258,8 @@ Pouze jednou Importovat databázi Exportovat databázi - Přepíše vaši dosavadní historii a odběry - Exportuje historii, odběry a playlisty + Přepíše Vaši dosavadní historii, odběry, playlisty a (volitelně) nastavení + Exportuje historii, odběry, playlisty a nastavení Externí přehrávače nepodporují tyto druhy odkazů Neplatná URL Nenalezeny žádné video streamy diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 3176781ff..6effa7615 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -132,7 +132,7 @@ Entfernt Tonspur bei manchen Auflösungen Letzte Größe und Position des Pop-ups merken Gestensteuerung - Helligkeit und Lautstärke mittels Gesten einstellen + Gesten verwenden, um die Helligkeit und Lautstärke einzustellen Suchvorschläge Beim Suchen Vorschläge anzeigen Pop-up @@ -266,8 +266,8 @@ Gewünschten Inhalt laden Datenbank importieren Datenbank exportieren - Überschreibt deinen aktuellen Verlauf und deine Abonnements - Verlauf, Abonnements und Wiedergabelisten exportieren + Überschreibt deinen aktuellen Verlauf, Abonnements, Wiedergabelisten und (optionale) Einstellungen + Export von Verlauf, Abonnements, Wiedergabelisten und Einstellungen Keine gültige ZIP-Datei Warnung: Nicht alle Dateien konnten importiert werden. Dies wird deine aktuellen Einstellungen überschreiben. @@ -402,9 +402,9 @@ Neuer Tab Tab wählen Gestensteuerung für Lautstärke - Verwende Gesten um die Abspielerlautstärke einzustellen + Gesten verwenden, um die Lautstärke einzustellen Gestensteuerung für Helligkeit - Player-Helligkeit über Gesten steuern + Gesten verwenden, um die Helligkeit einzustellen Aktualisierungen Datei gelöscht App-Update-Benachrichtigung @@ -643,7 +643,7 @@ Lösche Cookies, die NewPipe speichert, wenn du ein reCAPTCHA löst reCAPTCHA-Cookies wurden gelöscht reCAPTCHA-Cookies löschen - Zeige Inhalt, der möglicherweise unpassend für Kinder ist, da er eine Altersbeschränkung (wie z.B. 18+) hat + Zeige altersbeschränkte Inhalte (bspw. 18+), welche möglicherweise unpassend für Kinder sein könnten Wiedergabe einreihen Android kann die Farbe der Benachrichtigung entsprechend der Hauptfarbe in der Miniaturansicht anpassen (beachte, dass dies nicht auf allen Geräten verfügbar ist) Benachrichtigung farblich anpassen diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index d5d633a29..fe16a89bc 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -265,8 +265,8 @@ Cargando contenido solicitado Importar base de datos Exportar base de datos - Anula su historial actual y suscripciones - Exportar historial, suscripciones y listas de reproducción + Anula su historial actual, suscripciones, listas de reproducción y (opcionalmente) ajustes + Exportar historial, suscripciones, listas de reproducción y ajustes Exportado Importado Archivo ZIP no válido diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 997b58305..4b04865bd 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -263,8 +263,8 @@ Chargement du contenu demandé Importer la base de données Exporter la base de données - Remplace votre historique et vos abonnements actuels - Exporte l’historique, les abonnements et les listes de lecture + Remplace votre historique, vos abonnements, vos listes de lecture et (en option) vos paramètres + Exporte l’historique, les abonnements, les listes de lecture et les paramètres Exporté Importé Fichier ZIP non valide diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index b080e9c9c..8da1918d3 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -268,7 +268,7 @@ תמונות מטמון נמחקו ניקוי מטמון נתוני העל מטמון נתוני העל התרוקן - ייצוא היסטוריה, מינויים ורשימות נגינה + ייצוא היסטוריה, מינויים, רשימות נגינה והגדרות מחיקת היסטוריית הצפייה היסטוריית הצפייה נמחקה. מחיקת היסטוריית החיפוש @@ -336,7 +336,7 @@ להמשיך תור נגינה סופית (בלתי מחזורית) על ידי הוספת תזרים קשור החלפת כיווניות העברה לראשי - משכתב את ההיסטוריה והמינויים הנוכחיים שלך + משכתב את ההיסטוריה, המינויים ו(אולי גם) את ההגדרות הנוכחיים שלך מחיקת היסטוריית התזרימים שהתנגנו ומיקומי הנגינה ערוצים רשימות נגינה diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 90a65531e..063686d8f 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -290,15 +290,15 @@ Prijeđi na glavni Uvoz baze podataka Izvoz baze podataka - Poništava vašu trenutnu povijest i pretplate - Izvezi povijest, pretplate i playliste - Očisti povijest gledanja + Poništava vašu trenutačnu povijest, pretplate, playliste i (opcionalno) postavke + Izvezi povijest, pretplate, playliste i postavke + Izbriši povijest gledanja Briše povijest reproduciranih streamova i pozicije reprodukcije - Obriši cijelu povijest gledanja\? - Povijest gledanja izbrisana. - Obriši povijest pretraživanja - Obriši cijelu povijest pretraživanja\? - Povijest pretraživanja obrisana. + Izbrisati cijelu povijest gledanja\? + Povijest gledanja je izbrisana. + Izbriši povijest pretraživanja + Izbrisati cijelu povijest pretraživanja\? + Povijest pretraživanja je izbrisana. Neispravan URL Nema takve mape Naziv datoteke ne može biti prazan @@ -477,9 +477,9 @@ Započni preuzimanja Zaustavi preuzimanja Pitaj gdje preuzeti - Obriši poziciju reprodukcije - Obriši sve pozicije reprodukcije - Obriši sve pozicije reprodukcije\? + Izbriši poziciju reprodukcije + Izbriši sve pozicije reprodukcije + Izbrisati sve pozicije reprodukcije\? Nitko ne gleda Nitko ne sluša Jezik će se promjeniti nakon ponovnog pokretanja aplikacije. @@ -641,4 +641,8 @@ Prikaži minijaturu kao pozadinu pri zaključanom ekranu i unutar obavijesti Prikaži minijaturu Prikaži izvorno vrijeme elemenata + „Okvir za pristup spremištu” omogućuje preuzimanje na SD karticu. +\nNeki uređaji nisu kompatibilni + Izvorni tekstovi usluga bit će vidljivi u elementima prijenosa + Dostupno je u nekim uslugama. Obično je puno brže, ali može vratiti ograničenu količinu predmeta i često nepotpune podatke (npr. bez trajanja, vrste predmeta, bez stanja uživo). \ No newline at end of file diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index ee11cd5c2..01f7e8d83 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -459,4 +459,8 @@ Lejátszási pozíciók törlése Találatok a következőre: %s Bélyegkép méretezése 1:1 arányra + Értesítés színezése + Semmi + Keverés + Ismétlés \ No newline at end of file diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 74147f3f5..4e23cce03 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -206,8 +206,8 @@ Alihkan ke Utama Impor basis data Ekspor basis data - Timpa riwayat dan langganan anda saat ini - Ekspor riwayat, langganan dan daftar putar + Timpa riwayat, langganan, daftar putar dan (opsional) pengaturan anda saat ini + Ekspor riwayat, langganan, daftar putar dan pengaturan Tidak bisa memutar stream ini Telah terjadi galat pemutar yang tidak bisa dipulihkan Memulihkan dari galat pemutar diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index e95fcc258..9a3f6ef4e 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -5,25 +5,25 @@ Nessun lettore multimediale trovato. Installare VLC\? Installa Annulla - Apri nel Browser + Apri nel browser Condividi Scarica Cerca Impostazioni Forse cercavi \"%1$s\"\? Condividi con - Scegli Browser + Scegli browser rotazione - Cartella Video Scaricati + Cartella video scaricati I video scaricati saranno salvati qui Scegli la cartella per i video scaricati - Risoluzione Predefinita + Risoluzione predefinita Riproduci con Kodi Installare l\'app Kore\? Mostra \"Riproduci con Kodi\" Mostra l\'opzione per riprodurre video tramite Kodi Audio - Formato Audio Predefinito + Formato audio predefinito Scarica Mostra video \"Prossimo\" e \"Simili\" URL non supportato @@ -36,9 +36,9 @@ Mi piace Impossibile creare la cartella di download \'%1$s\' Creata la cartella per i download \'%1$s\' - Usa Lettore Video Esterno - Usa Lettore Audio Esterno - Cartella Audio Scaricati + Usa lettore video esterno + Usa lettore audio esterno + Cartella audio scaricati Gli audio scaricati saranno salvati qui Scegli la cartella per gli audio scaricati Tema @@ -61,7 +61,7 @@ Mostra contenuti con restrizioni di età Tocca \"Cerca\" per iniziare \n - Riproduzione Automatica + Riproduzione automatica Riproduci i video quando NewPipe viene aperto da un\'altra app In diretta Impossibile analizzare completamente il sito web @@ -114,22 +114,22 @@ È richiesta la risoluzione del reCAPTCHA Più tardi - Apri in Modalità Popup - Modalità Popup + Apri in modalità popup + Modalità popup Riproduzione in modalità popup Disattivato Audio non disponibile per alcune risoluzioni - In Sottofondo + In sottofondo Popup - Risoluzione Predefinita Lettore Popup - Mostra Altre Risoluzioni + Risoluzione predefinita lettore popup + Mostra altre risoluzioni Solo alcuni dispositivi possono riprodurre video 2K/4K - Formato Video Predefinito + Formato video predefinito Ricorda proprietà lettore popup - Ricorda dimensione e posizione della finestra Popup - Controllo Gesti Lettore Multimediale + Ricorda dimensione e posizione del lettore popup + Controllo gesti lettore multimediale Usa i gesti per controllare luminosità e volume del lettore multimediale - Suggerimenti di Ricerca + Suggerimenti di ricerca Mostra suggerimenti durante la ricerca Popup Filtra i risultati @@ -264,16 +264,16 @@ Caricamento del contenuto richiesto Importa database Esporta database - Sovrascrive la cronologia e le iscrizioni attuali - Esporta la cronologia, le iscrizioni e le playlist + Sovrascrive la cronologia, le iscrizioni, le playlist e (facoltativamente) le impostazioni correnti + Esporta cronologia, iscrizioni, playlist e impostazioni Esportazione completa Importazione completa Nessun file ZIP valido Attenzione: Impossibile importare tutti i file. Questa operazione sostituirà le tue impostazioni attuali. Scarica il video - Mostra Informazioni - Playlist Salvate + Mostra informazioni + Playlist salvate Aggiungi a Trascina per riordinare Crea @@ -309,7 +309,7 @@ Il monitoraggio di memory leak potrebbe causare la mancata risposta dell\'applicazione durante il dumping dell\'heap Segnala errori «fuori del ciclo di vita» Forza la segnalazione di eccezioni Rx non consegnabili al di fuori del ciclo di vita dell\'attività dopo la chiusura - Usa Ricerca Rapida (Imprecisa) + Usa ricerca rapida (imprecisa) Consente al lettore multimediale di spostarsi più velocemente, ma con precisione ridotta. Spostamenti di 5, 15 o 25 secondi non funzionano con questo. Accoda automaticamente l\'elemento successivo Accoda un contenuto consigliato al termine della riproduzione, in una coda non ripetitiva @@ -347,7 +347,7 @@ Tieni presente che questa operazione può consumare una grande quantità di traffico dati. \n \nVuoi continuare? - Carica Copertine + Carica copertine Disabilita per prevenire il caricamento delle copertine, risparmiando dati e memoria. La modifica di questa opzione cancellerà la cache delle immagini in memoria e sul disco. Cache immagini svuotata Pulisci Cache Metadati @@ -401,7 +401,7 @@ Scegli scheda Gesti Controllo Volume Utilizza i gesti per controllare il volume del lettore multimediale - Gesti Controllo Luminosità + Gesti controllo luminosità Utilizza i gesti per controllare la luminosità del lettore multimediale Aggiornamenti File eliminato @@ -458,7 +458,7 @@ Eventi Conferenze Tempo per la connessione esaurito - Mostra Commenti + Mostra commenti Disattiva per nascondere i commenti Riproduzione automatica Nessun commento @@ -506,7 +506,7 @@ La lingua verrà cambiata al riavvio dell\'applicazione. Contenuti in evidenza predefiniti - Durata Avanzamento e Riavvolgimento Rapidi + Durata avanzamento e riavvolgimento rapidi Istanze PeerTube Seleziona le istanze PeerTube preferite Trova altre istanze su %s @@ -619,7 +619,7 @@ Solo Wi-Fi Avvia la riproduzione automaticamente — %s URL non riconosciuto. Vuoi aprirlo con un\'altra app\? - Accoda Automaticamente + Accoda automaticamente La coda del lettore attivo sarà sostituita Chiedi prima di svuotare la coda Cambiare tipo di riproduzione potrebbe sostituire gli elementi in coda @@ -635,7 +635,7 @@ Notifica Niente Ripeti - Ridimensiona Copertina alla Proporzione 1:1 + Ridimensiona copertina alla proporzione 1:1 Modifica la proporzione della copertina del video mostrata nella notifica da 16:9 a 1:1 (può introdurre distorsioni) Mostra memory leak Aggiunto alla coda @@ -646,7 +646,7 @@ Consente di usufruire della «Modalità con restrizioni» di YouTube, che esclude contenuti potenzialmente inappropriati per i minori Mostra contenuti che hanno un limite di età (es. 18+). Potrebbero essere inadatti ai bambini Lascia che Android modifichi il colore della notifica, secondo il colore principale della copertina (funzione non disponibile per tutti i dispositivi) - Colora Notifica + Colora notifica Mostra le copertine come sfondo della schermata di blocco e all\'interno delle notifiche Mostra copertina \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 675d299a4..e9253598d 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -93,7 +93,7 @@ このサーバーには対応していません ファイルが既に存在します URL の形式が正しくないか、通信が利用できません - NewPipeで保存中 + NewPipe ダウンロード中 タップして詳細を表示 お待ちください… クリップボードにコピーしました @@ -176,7 +176,7 @@ プレイリスト 元に戻す すべて再生 - NewPipeの通知 + NewPipe の通知 [不明] 動画の再生ができませんでした 回復不能な再生エラーが発生しました @@ -228,8 +228,8 @@ 一度だけ データベースをインポート データベースをエクスポート - 既存の履歴と登録リストは上書きされます - 履歴や登録リスト、プレイリストをエクスポートします + 既存の再生履歴、登録チャンネル一覧、プレイリストおよび(任意の)設定は上書きされます + 再生履歴、登録チャンネル一覧、プレイリストおよび設定をエクスポートします 再生エラーからの回復中 外部プレイヤーは、これらのタイプのリンクをサポートしていません 無効なURL @@ -324,11 +324,11 @@ すべての検索履歴を削除しますか? このファイル/コンテンツはありません - %s人が登録しています + チャンネル登録者数 %s人 再生なし - 再生回数 %s再生 + 再生回数 %s回 1 つのアイテムが削除されました。 支援する @@ -341,7 +341,7 @@ プレイリスト 「長押しして追加」のヒントを表示 トラック - NewPipeのバックグラウンドおよびポップアッププレイヤーの通知 + NewPipe のバックグラウンドおよびポップアッププレイヤーの通知 新着と人気 長押ししてキューに追加 ポップアップで連続再生を開始 diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index e0fa9becc..61dd01a92 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -550,4 +550,32 @@ %s에 대한 검색 결과 셔플 연속 재생 + 재생목록 페이지 + 썸네일 보기 + 그룹 이름이 없음 + + %d 일 + + + %d 시간 + + + %d 분 + + + %d 초 + + 시청 기록을 지우겠습니까\? + 시청 기록 지우기 + 재생목록 실행 + 알림 + URL을 인식할 수 없습니다. 다른 앱으로 여시겠습니까\? + 스트림을 비우기 전 확인을 요청합니다. + 안드로이드에서 썸네일의 색상에 따라 알림 색상을 조절합니다. (지원되지 않는 기기가 있을 수 있습니다.) + 버퍼링 + 다섯번째 버튼 + 네번째 버튼 + 세번째 버튼 + 두번째 버튼 + 다른 앱 위에 표시되는 권한 부여 \ No newline at end of file diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml index 3457fda56..fe9f56402 100644 --- a/app/src/main/res/values-ms/strings.xml +++ b/app/src/main/res/values-ms/strings.xml @@ -1,6 +1,7 @@ - Tekan carian untuk bermula + Tekan \"Cari\" untuk bermula +\n %1$s tontonan Diterbitkan pada %1$s Tiada pemain strim ditemui. Adakah anda mahu memasang VLC\? diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 0b65eeab9..a9ed08379 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -55,7 +55,7 @@ Automatisk avspilling Spiller en video når NewPipe blir forespurt av et annet program Innhold - Aldersbegrenset innhold + Vis aldersbegrenset innhold Feil Kunne ikke laste inn alle miniatyrbilder Kunne ikke dekryptere signaturen til videoens nettadresse @@ -225,7 +225,7 @@ Lydinnstillinger Hold for å legge i kø Vis \"Hold for å legge til\" -tips - Vis tips når bakgrunnen eller oppsprettsknappen i videoens «Detaljer:» trykkes + Vis tips når det trykkes på bakgrunnen eller oppsprettsknappen i videoens «Detaljer:» Lagt i kø for bakgrunnsavspiller Lagt i kø for oppsprettsspiller [Ukjent] @@ -265,7 +265,7 @@ Importer database Eksporter database Overstyrer din nåværende historikk og abonnementsliste - Eksporter historikk, abonnementer og spillelister + Eksporter historikk, abonnementer, spillelister og innstillinger Eksportert Importert Ingen gyldig ZIP-fil @@ -357,7 +357,10 @@ \n \n1. Gå til denne nettadressen: %1$s \n2. Logg inn når forespurt -\n3. En nedlasting av eksportfilen bør starte +\n3. En nedlasting av eksportfilen bør starte +\n4. Klikk på «Neste steg» og så på «Opprett eksport» +\n5. Klikk på «Last ned»-knappen etter den vises, og +\n6. Fra nedlastet takeout.zip, pakk ut .json-filen (vanligvis under «YouTube og YouTube Music/subscriptions/subscriptions.json» og importer den her. Importer en SoundCloud-profil ved å skrive enten nettadressen eller din ID: \n \n1. Skru på \"skrivebordsmodus\" i en nettleser ( siden er ikke tilgjengelig for mobile enheter) @@ -644,4 +647,6 @@ Vis innhold som muligens er upassende for barn, siden det har aldersgrense (som 18+). Få Android til å tilpasse merknadens farge i henhold til hovedfargen på miniatyrbildet (merk at dette ikke støttes på alle enheter) Fargelegg merknad + Vis miniatyrbilde på låseskjerm som bakgrunn og i merknader + Vis miniatyrbilde \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 820e88847..d4ee389f2 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -269,8 +269,8 @@ Odtwarzaj na pierwszym planie Importuj dane Eksportuj dane - Zastępuje Twoją bieżącą historię i subskrypcje - Eksportuje bieżącą historię, subskrypcje i playlisty + Zastępuje Twoją bieżącą historię, subskrypcje, playlisty i (opcjonalnie) ustawienia + Eksportuje bieżącą historię, subskrypcje, playlisty i ustawienia Przeciągnij, aby zmienić kolejność Utwórz Usuń bieżący diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index ff6805747..c0e61b392 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -262,8 +262,8 @@ Carregando conteúdo solicitado Importar base de dados Exportar base de dados - Substitui seu histórico e inscrições atuai - Exporte histórico, inscrições e playlists + Substitui seu histórico atual, inscrições, playlists e (opcionalmente) configurações + Exporte histórico, inscrições, playlists e configurações Exportado Importado Não há nenhum arquivo ZIP válido @@ -596,7 +596,7 @@ \n \nAtive \"%1$s\" nas configurações se quiser vê-lo. Sim, e vídeos parcialmente vistos - Vídeos vistos antes e depois de adicionar à lista de reprodução serão removidos. + Vídeos vistos antes e depois de adicionar à playlists serão removidos. \nTem certeza\? Isto não pode ser desfeito! Remover vídeos vistos\? Remover vistos diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index 3419c97e7..893abf31a 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -50,7 +50,7 @@ Devido às restrições de ExoPlayer, a duração da pesquisa foi definida para %d segundos Sobrescrever Sem som - Ver histórico + Histórico de visualizações %s visualização %s visualizações @@ -108,7 +108,7 @@ Artistas Não foi possível ligar ao servidor Mostrar uma opção para reproduzir o vídeo no Kodi - Modo de vista em lista + Modo de exibição A pesquisa inexata permite que esta seja mais rápida mas reduz a precisão. Procurar por 5, 15 ou 25 segundos não funciona corretamente. Permitir sobreposição a outras aplicações Reprodução automática @@ -121,7 +121,7 @@ Reportar no GitHub Gosto O histórico está desativado - Pasta para os vídeos + Pasta para os ficheiros de vídeo Prima \"Feito\" ao resolver A carregar… Áudio @@ -398,7 +398,7 @@ Colocar vídeo seguinte na fila Defina as suas instâncias favoritas PeerTube Importar/exportar - Exportar histórico, subscrições e listas de reprodução + Exportar histórico, subscrições, listas de reprodução e definições Melhor resolução Selecione um canal Escolher navegador @@ -453,7 +453,7 @@ Limpar histórico de pesquisas Erro Lembrar propriedades de popup - Os ficheiros de vídeo descarregados serão armazenados aqui + Os ficheiros de vídeo descarregados serão guardados aqui Mudar para principal Esta permissão é necessária \npara o modo popup @@ -471,7 +471,7 @@ Pendente Importado Automático - Substitui o histórico e as subscrições atuais + Substitui o seu histórico, subscrições, listas de reprodução e (opcionalmente) definições Popup k Não foi possível criar a pasta \'%1$s\' @@ -538,7 +538,7 @@ Carácter de substituição Vídeo Não existem vídeos para descarregar - Os ficheiros de áudio descarregados serão armazenados aqui + Os ficheiros de áudio descarregados serão guardados aqui Vídeos Meta-dados em cache limpos Mostrar dica ao premir em segundo plano ou no botão \"Detalhes:\" da janela popup diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 8a94cc436..9c4000230 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -16,8 +16,8 @@ rotação Utilizar reprodutor de vídeo externo Utilizar reprodutor de áudio externo - Pasta para os vídeos - Os ficheiros de vídeo descarregados serão armazenados aqui + Pasta para os ficheiros de vídeo + Os ficheiros de vídeo descarregados serão guardados aqui Escolha a pasta para colocar os ficheiros de vídeo Resolução padrão Reproduzir no Kodi @@ -46,7 +46,7 @@ Utilizar Tor (Experimental) Forçar tráfego via Tor para aumentar a privacidade (ainda não é suportada a emissão de vídeos). Pasta para ficheiros de áudio - Os ficheiros de áudio descarregados serão armazenados aqui + Os ficheiros de áudio descarregados serão guardados aqui Escolha a pasta para colocar os ficheiros de áudio Não foi possível criar a pasta \'%1$s\' Pasta \'%1$s\' criada com sucesso @@ -165,8 +165,8 @@ Novidades Histórico de pesquisa Guardar termos de pesquisa localmente - Ver histórico - Manter histórico dos vídeos vistos + Histórico de visualizações + Manter histórico dos vídeos visualizados Continuar reprodução Continuar reprodução após interrupções (ex. chamadas) Reprodutor @@ -237,8 +237,8 @@ Alternar orientação Importar base de dados Exportar base de dados - Substitui o histórico e as subscrições atuais - Exportar histórico, subscrições e listas de reprodução + Substitui o seu histórico, subscrições, listas de reprodução e (opcionalmente) definições + Exportar histórico, subscrições, listas de reprodução e definições Na fila do reprodutor em segundo plano Na fila do reprodutor popup Mudar para segundo plano @@ -417,7 +417,7 @@ Seleção Atualizações Mostrar uma notificação para pedir a atualização da aplicação se existir uma nova versão - Modo de vista em lista + Modo de exibição Lista Grelha Automático diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 65dfa46c0..c672c648f 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -26,7 +26,7 @@ Формат аудио по умолчанию Скачать URL не поддерживается - \"Следующее\" и похожие видео + Показать похожие видео Язык контента по умолчанию Видео и аудио Внешний вид @@ -153,7 +153,7 @@ Свободное и легковесное потоковое воспроизведение для Android. Открыть на GitHub Приветствуется всё — идеи, перевод, изменения дизайна, чистка кода или огромные изменения в коде. Чем больше сделано, тем лучше! - © %1$s %2$s под лицензией %3$s + © %1$s • %2$s • %3$s Помощь проекту Подписаться Не удалось изменить подписку @@ -162,7 +162,7 @@ Подписки Что нового История поиска - Хранить запросы поиска локально + Хранить запросы поиска (локально) История просмотров Продолжать воспроизведение Восстанавливать последнюю позицию @@ -282,8 +282,8 @@ Файл Импорт данных Экспорт данных - Текущие подписки, плейлисты и история будут заменены - Экспорт подписок, плейлистов и истории + Текущие подписки, плейлисты, история и (опционально) настройки будут заменены + Экспорт подписок, плейлистов, истории и настроек Папка не существует Папка или источник контента не существуют Файл не существует или нет разрешения на его чтение или запись @@ -383,7 +383,7 @@ При открытии ссылки Хотите импортировать настройки? Конфиденциальность - Проект NewPipe очень серьёзно относится к вашей конфиденциальности. Поэтому приложение не собирает никаких данных без вашего согласия. + Проект NewPipe очень серьёзно относится к вашей конфиденциальности. Приложение не собирает никаких данных без вашего согласия. \nПолитика конфиденциальности NewPipe подробно объясняет, какие данные отправляются и хранятся при отправке отчёта о сбоях. Прочитать политику В соответствии с Общим регламентом по защите данных ЕС (GDPR), обращаем ваше внимание на политику конфиденциальности NewPipe. Пожалуйста, внимательно ознакомьтесь с ней. @@ -580,19 +580,19 @@ Обновлять всегда Подписки не выбраны Группы каналов - Если обновление подписок кажется вам слишком медленным, попробуйте быстрый режим (включите в настройках или кнопкой внизу). + Если подписки обновляются слишком медленно, попробуйте быстрый режим (включите в настройках или кнопкой внизу). \n -\nNewPipe позволяет обновлять подписки двумя способами: -\n• получение канала целиком, медленное, но с полными сведениями; -\n• обновление по RSS, быстрое, но с потерей сведений. +\nNewPipe может обновлять подписки двумя способами: +\n• получение канала целиком, медленное, с полными сведениями; +\n• обновление по RSS, быстрое, с потерей сведений. \n -\nПри быстром обновлении теряются длительность элемента и его тип (нельзя определить, трансляция это или обычное видео), могут быть получены не все элементы канала. +\nПри быстром обновлении теряются длительность элемента и его тип (трансляция или обычное видео), могут быть получены не все элементы канала. \n -\nYouTube является примером такого сервиса, допуская быстрое обновление по RSS. +\nКак пример, YouTube поддерживает быстрое обновление. \n \nВыбор за вами: скорость или точность. Обновление по RSS, если доступно - Доступно для некоторых сервисов, быстрое, но может возвращать не всё содержимое канала и не содержать часть сведений (длительность, тип элемента, статус трансляции) + Доступно для некоторых сервисов, быстрое, но может возвращать не всё содержимое канала и не содержать часть сведений (длительность, статус трансляции) Период актуальности подписок после обновления — %s Это видео имеет возрастное ограничение. \n @@ -640,7 +640,7 @@ Перемешать Повтор В компактном уведомлении доступно не более трёх действий! - Действия можно отредактировать, нажав на них. Отметьте не более трёх для отображения в компактном уведомлении + Действия можно изменить, нажав на них. Отметьте не более трёх для отображения в компактном уведомлении Пятое действие Четвёртое действие Третье действие diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml index ea08b799b..2127434b6 100644 --- a/app/src/main/res/values-sc/strings.xml +++ b/app/src/main/res/values-sc/strings.xml @@ -443,8 +443,8 @@ Cheres iscantzellare totu sa cronologia de sos pompiados\? Iscantzellat sa cronologia de sos cuntenutos riproduidos e sas positziones de riprodutzione Isbòida sa cronologia de sos pompiados - Esporta sa cronologia, sos abbonamentos e sas iscalitas - Subraiscriet sa cronologia e sos abbonamentos atuales tuos + Esporta sa cronologia, sos abbonamentos, sas iscalitas e sas impostatziones + Subraiscriet sa cronologia, sos abbonamentos, sas iscalita e (optzionalmente) sas impostatziones tuas atuales Esporta sa base de datos Importa sa base de datos Cola a sa modalidade printzipale diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index e40fc0606..e08ab1b7b 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -231,8 +231,8 @@ Prepnúť na Video Importovať databázu Exportovať databázu - Prepíše aktuálnu históriu pozretí a odberov - Exportovať históriu, odbery a zoznamy skladieb + Prepíše aktuálnu históriu, odbery, zoznamy skladieb a (voliteľne aj) nastavenia + Exportovať históriu, odbery, zoznamy skladieb a nastavenia Nepodarilo sa prehrať tento stream Pri prehrávaní došlo k chybe a nemožno pokračovať Zotavovanie po chybe v prehrávaní diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 451d16a67..bc2b4ba5d 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -260,8 +260,8 @@ İstenen içerik yükleniyor Veri tabanını içe aktar Veri tabanını dışa aktar - Geçerli geçmişinizi ve aboneliklerinizi geçersiz kılar - Geçmişi, abonelikleri ve oynatma listelerini dışa aktar + Geçerli geçmişinizi, aboneliklerinizi, oynatma listelerinizi ve (isteğe bağlı olarak) ayarlarınızı geçersiz kılar + Geçmişi, abonelikleri, oynatma listelerini ve ayarları dışa aktar Dışa aktarıldı İçe aktarıldı Geçerli ZIP dosyası yok diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 460d137e9..cd66e7a78 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -228,8 +228,8 @@ 網站 匯入資料庫 匯出資料庫 - 覆蓋您目前的歷史記錄和訂閱 - 匯出歷史記錄、訂閱和播放清單 + 覆蓋您目前的歷史記錄、訂閱與(可選的)設定 + 匯出歷史記錄、訂閱、播放清單與設定 回饋 如欲了解更多有關 NewPipe 的資訊和新聞,請造訪我們的網站。 首頁內容 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 010d32369..c2b59642d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -222,8 +222,8 @@ Export database Clear reCAPTCHA cookies reCAPTCHA cookies have been cleared - Overrides your current history and subscriptions - Export history, subscriptions and playlists + Overrides your current history, subscriptions, playlists and (optionally) settings + Export history, subscriptions, playlists and settings Clear cookies that NewPipe stores when you solve a reCAPTCHA Clear watch history Deletes the history of played streams and the playback positions @@ -734,4 +734,4 @@ Whitelist cleared Uploader added to whitelist Uploader removed from whitelist - \ No newline at end of file + diff --git a/app/src/test/java/org/schabi/newpipe/ktx/OffsetDateTimeToCalendarTest.kt b/app/src/test/java/org/schabi/newpipe/ktx/OffsetDateTimeToCalendarTest.kt new file mode 100644 index 000000000..c93d36eb5 --- /dev/null +++ b/app/src/test/java/org/schabi/newpipe/ktx/OffsetDateTimeToCalendarTest.kt @@ -0,0 +1,31 @@ +package org.schabi.newpipe.ktx + +import org.junit.Assert.assertEquals +import org.junit.Test +import java.time.LocalDate +import java.time.OffsetDateTime +import java.time.ZoneId +import java.time.ZoneOffset +import java.util.Calendar +import java.util.TimeZone + +class OffsetDateTimeToCalendarTest { + @Test + fun testRelativeTimeWithCurrentOffsetDateTime() { + val calendar = LocalDate.of(2020, 1, 1).atStartOfDay().atOffset(ZoneOffset.UTC) + .toCalendar() + + assertEquals(2020, calendar[Calendar.YEAR]) + assertEquals(0, calendar[Calendar.MONTH]) + assertEquals(1, calendar[Calendar.DAY_OF_MONTH]) + assertEquals(0, calendar[Calendar.HOUR]) + assertEquals(0, calendar[Calendar.MINUTE]) + assertEquals(0, calendar[Calendar.SECOND]) + assertEquals(TimeZone.getTimeZone("UTC"), calendar.timeZone) + } + + @Test(expected = IllegalArgumentException::class) + fun testRelativeTimeWithFarOffOffsetDateTime() { + OffsetDateTime.MAX.minusYears(1).toCalendar() + } +} diff --git a/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java b/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java index 8cd99cc04..d126f8473 100644 --- a/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java +++ b/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java @@ -205,7 +205,7 @@ public class ListHelperTest { assertEquals(MediaFormat.M4A, stream.getFormat()); // Adding a new format and bitrate. Adding another stream will have no impact since - // it's not a prefered format. + // it's not a preferred format. testList.add(new AudioStream("", MediaFormat.WEBMA, /**/ 192)); stream = testList.get(ListHelper.getHighestQualityAudioIndex(MediaFormat.MP3, testList)); assertEquals(192, stream.average_bitrate); diff --git a/fastlane/metadata/android/en-US/changelogs/960.txt b/fastlane/metadata/android/en-US/changelogs/960.txt new file mode 100644 index 000000000..98c1865c7 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/960.txt @@ -0,0 +1,4 @@ +• Improved description of export database option in settings. +• Fixed YouTube comments parsing. +• Fixed display name of media.ccc.de service. +• Updated translations. \ No newline at end of file diff --git a/fastlane/metadata/android/es/changelogs/952.txt b/fastlane/metadata/android/es/changelogs/952.txt index 4b976c298..940121285 100644 --- a/fastlane/metadata/android/es/changelogs/952.txt +++ b/fastlane/metadata/android/es/changelogs/952.txt @@ -1,7 +1,7 @@ Mejorado • Reproducción automática disponible para todos los servicios (no sólo para YouTube) -Reparado +Arreglado • Streams relacionados, gracias al soporte de las nuevas continuaciones de YouTube • Videos con restricción de edad en YouTube -• [Android TV] Foco retenido +• [Android TV] Superposición de los destacados de foco persistente diff --git a/fastlane/metadata/android/es/changelogs/959.txt b/fastlane/metadata/android/es/changelogs/959.txt new file mode 100644 index 000000000..4ffc757ef --- /dev/null +++ b/fastlane/metadata/android/es/changelogs/959.txt @@ -0,0 +1,3 @@ +Arreglado un bucle interminable de caídas después de abrir el reportador de errores. +Lista actualizada de instancias de PeerTube que pueden ser abiertas automáticamente por NewPipe. +Traducciones actualizadas. diff --git a/fastlane/metadata/android/eu/changelogs/959.txt b/fastlane/metadata/android/eu/changelogs/959.txt new file mode 100644 index 000000000..97bf7c9b7 --- /dev/null +++ b/fastlane/metadata/android/eu/changelogs/959.txt @@ -0,0 +1,3 @@ +Akatsen txostena ireki ostean zeuden hutsegite amaigabeak konpondu dira. +NewPipekin automatikoki ireki daitezkeen PeerTube instantziak eguneratu dira. +Itzulpenak eguneratuta. diff --git a/fastlane/metadata/android/it/changelogs/770.txt b/fastlane/metadata/android/it/changelogs/770.txt new file mode 100644 index 000000000..5c06fb4e0 --- /dev/null +++ b/fastlane/metadata/android/it/changelogs/770.txt @@ -0,0 +1,4 @@ +Modifiche su 0.17.2 + +Correzionw +• Risolto il problema con nessun video disponibile diff --git a/fastlane/metadata/android/it/changelogs/830.txt b/fastlane/metadata/android/it/changelogs/830.txt new file mode 100644 index 000000000..b623a5554 --- /dev/null +++ b/fastlane/metadata/android/it/changelogs/830.txt @@ -0,0 +1 @@ +Aggiornato SoundCloud client_id per risolvere i problemi di SoundCloud. diff --git a/fastlane/metadata/android/it/changelogs/850.txt b/fastlane/metadata/android/it/changelogs/850.txt new file mode 100644 index 000000000..0bbe00f36 --- /dev/null +++ b/fastlane/metadata/android/it/changelogs/850.txt @@ -0,0 +1 @@ +In questa versione è stata aggiornata la versione del sito web di YouTube. La vecchia versione del sito web verrà interrotta a Marzo e quindi è necessario aggiornare NewPipe. diff --git a/fastlane/metadata/android/it/changelogs/860.txt b/fastlane/metadata/android/it/changelogs/860.txt new file mode 100644 index 000000000..6a0973a27 --- /dev/null +++ b/fastlane/metadata/android/it/changelogs/860.txt @@ -0,0 +1,7 @@ +Migliorato +• Salvare e ripristinare se intonazione e tempo sono sganciati o meno +• Supporta il ritaglio del display nel lettore +• Visualizzazione rotonda e numero di iscritti +• YouTube ottimizzato per utilizzare meno dati + +In questa versione sono stati corretti più di 15 errori relativi a YouTube. diff --git a/fastlane/metadata/android/it/changelogs/870.txt b/fastlane/metadata/android/it/changelogs/870.txt new file mode 100644 index 000000000..2a291f726 --- /dev/null +++ b/fastlane/metadata/android/it/changelogs/870.txt @@ -0,0 +1,2 @@ +Questa è una versione hotfix che aggiorna NewPipe per consentire nuovamente l'utilizzo di SoundCloud senza grossi problemi. +L'API v2 di SoundCloud viene ora utilizzata nell'estrattore e il rilevamento di ID client non validi è stato migliorato. diff --git a/fastlane/metadata/android/it/changelogs/910.txt b/fastlane/metadata/android/it/changelogs/910.txt new file mode 100644 index 000000000..4fbccfdf0 --- /dev/null +++ b/fastlane/metadata/android/it/changelogs/910.txt @@ -0,0 +1 @@ +Corretta la migrazione del database che in alcuni rari casi impediva l'avvio di NewPipe. diff --git a/fastlane/metadata/android/it/changelogs/956.txt b/fastlane/metadata/android/it/changelogs/956.txt new file mode 100644 index 000000000..7c698fe33 --- /dev/null +++ b/fastlane/metadata/android/it/changelogs/956.txt @@ -0,0 +1 @@ +[YouTube] Risolto un arresto anomalo durante il caricamento di qualsiasi video diff --git a/fastlane/metadata/android/nb-NO/changelogs/959.txt b/fastlane/metadata/android/nb-NO/changelogs/959.txt new file mode 100644 index 000000000..51a17c99c --- /dev/null +++ b/fastlane/metadata/android/nb-NO/changelogs/959.txt @@ -0,0 +1,3 @@ +Fikset uendelig kræsjløkke ved åpning av feilrapportering. +Oppdatert liste over PeerTube-isntanser som kan åpnes automatisk av NewPipe. +Oppdaterte oversettelser. diff --git a/fastlane/metadata/android/pl/changelogs/959.txt b/fastlane/metadata/android/pl/changelogs/959.txt new file mode 100644 index 000000000..b3eb9b932 --- /dev/null +++ b/fastlane/metadata/android/pl/changelogs/959.txt @@ -0,0 +1,3 @@ +Naprawiono niekończącą się pętlę awarii po otwarciu raportu o błędach. +Zaktualizowana lista instancji PeerTube, które mogą być otwierane automatycznie przez NewPipe. +Zaktualizowano tłumaczenia. diff --git a/fastlane/metadata/android/pt_BR/changelogs/959.txt b/fastlane/metadata/android/pt_BR/changelogs/959.txt new file mode 100644 index 000000000..d3210dfd2 --- /dev/null +++ b/fastlane/metadata/android/pt_BR/changelogs/959.txt @@ -0,0 +1,3 @@ +Corrigimos um loop interminável de crash depois de abrir o reportar erro. +Atualizado lista de instâncias do PeerTube que podem ser abertas automaticamente pelo NewPipe. +Traduções atualizadas. diff --git a/fastlane/metadata/android/ru/full_description.txt b/fastlane/metadata/android/ru/full_description.txt index 216e41ab4..d25f04b00 100644 --- a/fastlane/metadata/android/ru/full_description.txt +++ b/fastlane/metadata/android/ru/full_description.txt @@ -1 +1 @@ -NewPipe не использует библиотеки фреймворка Google или API YouTube, взаимодействуя только с сайтом сервиса для получения необходимых сведений. NewPipe может работать на устройствах без установленных Сервисов Google и не требует учётной записи YouTube. Это свободное программное обеспечение. +NewPipe не использует библиотеки фреймворка Google или API YouTube, взаимодействуя только с сайтом сервиса для получения необходимых сведений. NewPipe может работать на устройствах без установленных Сервисов Google и не требует учётной записи YouTube, это свободное программное обеспечение.