+ * The default is {@link DefaultHttpDataSource#DEFAULT_CONNECT_TIMEOUT_MILLIS}.
+ *
+ *
+ * @param connectTimeoutMsValue The connect timeout, in milliseconds, that will be used.
+ * @return This factory.
+ */
+ public Factory setConnectTimeoutMs(final int connectTimeoutMsValue) {
+ connectTimeoutMs = connectTimeoutMsValue;
+ return this;
+ }
+
+ /**
+ * Sets the read timeout, in milliseconds.
+ *
+ * The default is {@link DefaultHttpDataSource#DEFAULT_READ_TIMEOUT_MILLIS}.
+ *
+ * @param readTimeoutMsValue The connect timeout, in milliseconds, that will be used.
+ * @return This factory.
+ */
+ public Factory setReadTimeoutMs(final int readTimeoutMsValue) {
+ readTimeoutMs = readTimeoutMsValue;
+ return this;
+ }
+
+ /**
+ * Sets whether to allow cross protocol redirects.
+ *
+ *
The default is {@code false}.
+ *
+ * @param allowCrossProtocolRedirectsValue Whether to allow cross protocol redirects.
+ * @return This factory.
+ */
+ public Factory setAllowCrossProtocolRedirects(
+ final boolean allowCrossProtocolRedirectsValue) {
+ allowCrossProtocolRedirects = allowCrossProtocolRedirectsValue;
+ return this;
+ }
+
+ /**
+ * Sets whether the use of the {@code range} parameter instead of the {@code Range} header
+ * to request ranges of streams is enabled.
+ *
+ *
+ * Note that it must be not enabled on streams which are using a {@link
+ * com.google.android.exoplayer2.source.ProgressiveMediaSource}, as it will break playback
+ * for them (some exceptions may be thrown).
+ *
+ *
+ * @param rangeParameterEnabledValue whether the use of the {@code range} parameter instead
+ * of the {@code Range} header (must be only enabled when
+ * non-{@code ProgressiveMediaSource}s)
+ * @return This factory.
+ */
+ public Factory setRangeParameterEnabled(final boolean rangeParameterEnabledValue) {
+ rangeParameterEnabled = rangeParameterEnabledValue;
+ return this;
+ }
+
+ /**
+ * Sets whether the use of the {@code rn}, which stands for request number, parameter is
+ * enabled.
+ *
+ *
+ * Note that it should be not enabled on streams which are using {@code /} to delimit URLs
+ * parameters, such as the streams of HLS manifests.
+ *
+ *
+ * @param rnParameterEnabledValue whether the appending the {@code rn} parameter to
+ * {@code videoplayback} URLs
+ * @return This factory.
+ */
+ public Factory setRnParameterEnabled(final boolean rnParameterEnabledValue) {
+ rnParameterEnabled = rnParameterEnabledValue;
+ return this;
+ }
+
+ /**
+ * Sets a content type {@link Predicate}. If a content type is rejected by the predicate
+ * then a {@link HttpDataSource.InvalidContentTypeException} is thrown from
+ * {@link YoutubeHttpDataSource#open(DataSpec)}.
+ *
+ * contentTypePredicateToSet) {
+ this.contentTypePredicate = contentTypePredicateToSet;
+ return this;
+ }
+
+ /**
+ * Sets the {@link TransferListener} that will be used.
+ *
+ * The default is {@code null}.
+ *
+ *
See {@link DataSource#addTransferListener(TransferListener)}.
+ *
+ * @param transferListenerToUse The listener that will be used.
+ * @return This factory.
+ */
+ public Factory setTransferListener(
+ @Nullable final TransferListener transferListenerToUse) {
+ this.transferListener = transferListenerToUse;
+ return this;
+ }
+
+ /**
+ * Sets whether we should keep the POST method and body when we have HTTP 302 redirects for
+ * a POST request.
+ *
+ * @param keepPostFor302RedirectsValue Whether we should keep the POST method and body when
+ * we have HTTP 302 redirects for a POST request.
+ * @return This factory.
+ */
+ public Factory setKeepPostFor302Redirects(final boolean keepPostFor302RedirectsValue) {
+ this.keepPostFor302Redirects = keepPostFor302RedirectsValue;
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public YoutubeHttpDataSource createDataSource() {
+ final YoutubeHttpDataSource dataSource = new YoutubeHttpDataSource(
+ connectTimeoutMs,
+ readTimeoutMs,
+ allowCrossProtocolRedirects,
+ rangeParameterEnabled,
+ rnParameterEnabled,
+ defaultRequestProperties,
+ contentTypePredicate,
+ keepPostFor302Redirects);
+ if (transferListener != null) {
+ dataSource.addTransferListener(transferListener);
+ }
+ return dataSource;
+ }
+ }
+
+ private static final String TAG = YoutubeHttpDataSource.class.getSimpleName();
+ private static final int MAX_REDIRECTS = 20; // Same limit as okhttp.
+ private static final int HTTP_STATUS_TEMPORARY_REDIRECT = 307;
+ private static final int HTTP_STATUS_PERMANENT_REDIRECT = 308;
+ private static final long MAX_BYTES_TO_DRAIN = 2048;
+
+ private static final String RN_PARAMETER = "&rn=";
+ private static final String YOUTUBE_BASE_URL = "https://www.youtube.com";
+
+ private final boolean allowCrossProtocolRedirects;
+ private final boolean rangeParameterEnabled;
+ private final boolean rnParameterEnabled;
+
+ private final int connectTimeoutMillis;
+ private final int readTimeoutMillis;
+ @Nullable
+ private final RequestProperties defaultRequestProperties;
+ private final RequestProperties requestProperties;
+ private final boolean keepPostFor302Redirects;
+
+ @Nullable
+ private final Predicate contentTypePredicate;
+ @Nullable
+ private DataSpec dataSpec;
+ @Nullable
+ private HttpURLConnection connection;
+ @Nullable
+ private InputStream inputStream;
+ private boolean opened;
+ private int responseCode;
+ private long bytesToRead;
+ private long bytesRead;
+
+ private long requestNumber;
+
+ @SuppressWarnings("checkstyle:ParameterNumber")
+ private YoutubeHttpDataSource(final int connectTimeoutMillis,
+ final int readTimeoutMillis,
+ final boolean allowCrossProtocolRedirects,
+ final boolean rangeParameterEnabled,
+ final boolean rnParameterEnabled,
+ @Nullable final RequestProperties defaultRequestProperties,
+ @Nullable final Predicate contentTypePredicate,
+ final boolean keepPostFor302Redirects) {
+ super(true);
+ this.connectTimeoutMillis = connectTimeoutMillis;
+ this.readTimeoutMillis = readTimeoutMillis;
+ this.allowCrossProtocolRedirects = allowCrossProtocolRedirects;
+ this.rangeParameterEnabled = rangeParameterEnabled;
+ this.rnParameterEnabled = rnParameterEnabled;
+ this.defaultRequestProperties = defaultRequestProperties;
+ this.contentTypePredicate = contentTypePredicate;
+ this.requestProperties = new RequestProperties();
+ this.keepPostFor302Redirects = keepPostFor302Redirects;
+ this.requestNumber = 0;
+ }
+
+ @Override
+ @Nullable
+ public Uri getUri() {
+ return connection == null ? null : Uri.parse(connection.getURL().toString());
+ }
+
+ @Override
+ public int getResponseCode() {
+ return connection == null || responseCode <= 0 ? -1 : responseCode;
+ }
+
+ @NonNull
+ @Override
+ public Map> getResponseHeaders() {
+ if (connection == null) {
+ return ImmutableMap.of();
+ }
+ // connection.getHeaderFields() always contains a null key with a value like
+ // ["HTTP/1.1 200 OK"]. The response code is available from
+ // HttpURLConnection#getResponseCode() and the HTTP version is fixed when establishing the
+ // connection.
+ // DataSource#getResponseHeaders() doesn't allow null keys in the returned map, so we need
+ // to remove it.
+ // connection.getHeaderFields() returns a special unmodifiable case-insensitive Map
+ // so we can't just remove the null key or make a copy without the null key. Instead we
+ // wrap it in a ForwardingMap subclass that ignores and filters out null keys in the read
+ // methods.
+ return new NullFilteringHeadersMap(connection.getHeaderFields());
+ }
+
+ @Override
+ public void setRequestProperty(@NonNull final String name, @NonNull final String value) {
+ checkNotNull(name);
+ checkNotNull(value);
+ requestProperties.set(name, value);
+ }
+
+ @Override
+ public void clearRequestProperty(@NonNull final String name) {
+ checkNotNull(name);
+ requestProperties.remove(name);
+ }
+
+ @Override
+ public void clearAllRequestProperties() {
+ requestProperties.clear();
+ }
+
+ /**
+ * Opens the source to read the specified data.
+ */
+ @Override
+ public long open(@NonNull final DataSpec dataSpecParameter) throws HttpDataSourceException {
+ this.dataSpec = dataSpecParameter;
+ bytesRead = 0;
+ bytesToRead = 0;
+ transferInitializing(dataSpecParameter);
+
+ final HttpURLConnection httpURLConnection;
+ final String responseMessage;
+ try {
+ this.connection = makeConnection(dataSpec);
+ httpURLConnection = this.connection;
+ responseCode = httpURLConnection.getResponseCode();
+ responseMessage = httpURLConnection.getResponseMessage();
+ } catch (final IOException e) {
+ closeConnectionQuietly();
+ throw HttpDataSourceException.createForIOException(e, dataSpec,
+ HttpDataSourceException.TYPE_OPEN);
+ }
+
+ // Check for a valid response code.
+ if (responseCode < 200 || responseCode > 299) {
+ final Map> headers = httpURLConnection.getHeaderFields();
+ if (responseCode == 416) {
+ final long documentSize = HttpUtil.getDocumentSize(
+ httpURLConnection.getHeaderField(HttpHeaders.CONTENT_RANGE));
+ if (dataSpecParameter.position == documentSize) {
+ opened = true;
+ transferStarted(dataSpecParameter);
+ return dataSpecParameter.length != C.LENGTH_UNSET
+ ? dataSpecParameter.length
+ : 0;
+ }
+ }
+
+ final InputStream errorStream = httpURLConnection.getErrorStream();
+ byte[] errorResponseBody;
+ try {
+ errorResponseBody = errorStream != null
+ ? Util.toByteArray(errorStream)
+ : Util.EMPTY_BYTE_ARRAY;
+ } catch (final IOException e) {
+ errorResponseBody = Util.EMPTY_BYTE_ARRAY;
+ }
+
+ closeConnectionQuietly();
+ final IOException cause = responseCode == 416 ? new DataSourceException(
+ PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE)
+ : null;
+ throw new InvalidResponseCodeException(responseCode, responseMessage, cause, headers,
+ dataSpec, errorResponseBody);
+ }
+
+ // Check for a valid content type.
+ final String contentType = httpURLConnection.getContentType();
+ if (contentTypePredicate != null && !contentTypePredicate.apply(contentType)) {
+ closeConnectionQuietly();
+ throw new InvalidContentTypeException(contentType, dataSpecParameter);
+ }
+
+ final long bytesToSkip;
+ if (!rangeParameterEnabled) {
+ // If we requested a range starting from a non-zero position and received a 200 rather
+ // than a 206, then the server does not support partial requests. We'll need to
+ // manually skip to the requested position.
+ bytesToSkip = responseCode == 200 && dataSpecParameter.position != 0
+ ? dataSpecParameter.position
+ : 0;
+ } else {
+ bytesToSkip = 0;
+ }
+
+
+ // Determine the length of the data to be read, after skipping.
+ final boolean isCompressed = isCompressed(httpURLConnection);
+ if (!isCompressed) {
+ if (dataSpecParameter.length != C.LENGTH_UNSET) {
+ bytesToRead = dataSpecParameter.length;
+ } else {
+ final long contentLength = HttpUtil.getContentLength(
+ httpURLConnection.getHeaderField(HttpHeaders.CONTENT_LENGTH),
+ httpURLConnection.getHeaderField(HttpHeaders.CONTENT_RANGE));
+ bytesToRead = contentLength != C.LENGTH_UNSET
+ ? (contentLength - bytesToSkip)
+ : C.LENGTH_UNSET;
+ }
+ } else {
+ // Gzip is enabled. If the server opts to use gzip then the content length in the
+ // response will be that of the compressed data, which isn't what we want. Always use
+ // the dataSpec length in this case.
+ bytesToRead = dataSpecParameter.length;
+ }
+
+ try {
+ inputStream = httpURLConnection.getInputStream();
+ if (isCompressed) {
+ inputStream = new GZIPInputStream(inputStream);
+ }
+ } catch (final IOException e) {
+ closeConnectionQuietly();
+ throw new HttpDataSourceException(e, dataSpec,
+ PlaybackException.ERROR_CODE_IO_UNSPECIFIED,
+ HttpDataSourceException.TYPE_OPEN);
+ }
+
+ opened = true;
+ transferStarted(dataSpecParameter);
+
+ try {
+ skipFully(bytesToSkip, dataSpec);
+ } catch (final IOException e) {
+ closeConnectionQuietly();
+ if (e instanceof HttpDataSourceException) {
+ throw (HttpDataSourceException) e;
+ }
+ throw new HttpDataSourceException(e, dataSpec,
+ PlaybackException.ERROR_CODE_IO_UNSPECIFIED,
+ HttpDataSourceException.TYPE_OPEN);
+ }
+
+ return bytesToRead;
+ }
+
+ @Override
+ public int read(@NonNull final byte[] buffer, final int offset, final int length)
+ throws HttpDataSourceException {
+ try {
+ return readInternal(buffer, offset, length);
+ } catch (final IOException e) {
+ throw HttpDataSourceException.createForIOException(e, castNonNull(dataSpec),
+ HttpDataSourceException.TYPE_READ);
+ }
+ }
+
+ @Override
+ public void close() throws HttpDataSourceException {
+ try {
+ final InputStream connectionInputStream = this.inputStream;
+ if (connectionInputStream != null) {
+ final long bytesRemaining = bytesToRead == C.LENGTH_UNSET
+ ? C.LENGTH_UNSET
+ : bytesToRead - bytesRead;
+ maybeTerminateInputStream(connection, bytesRemaining);
+
+ try {
+ connectionInputStream.close();
+ } catch (final IOException e) {
+ throw new HttpDataSourceException(e, castNonNull(dataSpec),
+ PlaybackException.ERROR_CODE_IO_UNSPECIFIED,
+ HttpDataSourceException.TYPE_CLOSE);
+ }
+ }
+ } finally {
+ inputStream = null;
+ closeConnectionQuietly();
+ if (opened) {
+ opened = false;
+ transferEnded();
+ }
+ }
+ }
+
+ @NonNull
+ private HttpURLConnection makeConnection(@NonNull final DataSpec dataSpecToUse)
+ throws IOException {
+ URL url = new URL(dataSpecToUse.uri.toString());
+ @HttpMethod int httpMethod = dataSpecToUse.httpMethod;
+ @Nullable byte[] httpBody = dataSpecToUse.httpBody;
+ final long position = dataSpecToUse.position;
+ final long length = dataSpecToUse.length;
+ final boolean allowGzip = dataSpecToUse.isFlagSet(DataSpec.FLAG_ALLOW_GZIP);
+
+ if (!allowCrossProtocolRedirects && !keepPostFor302Redirects) {
+ // HttpURLConnection disallows cross-protocol redirects, but otherwise performs
+ // redirection automatically. This is the behavior we want, so use it.
+ return makeConnection(url, httpMethod, httpBody, position, length, allowGzip, true,
+ dataSpecToUse.httpRequestHeaders);
+ }
+
+ // We need to handle redirects ourselves to allow cross-protocol redirects or to keep the
+ // POST request method for 302.
+ int redirectCount = 0;
+ while (redirectCount++ <= MAX_REDIRECTS) {
+ final HttpURLConnection httpURLConnection = makeConnection(url, httpMethod, httpBody,
+ position, length, allowGzip, false, dataSpecToUse.httpRequestHeaders);
+ final int httpURLConnectionResponseCode = httpURLConnection.getResponseCode();
+ final String location = httpURLConnection.getHeaderField("Location");
+ if ((httpMethod == DataSpec.HTTP_METHOD_GET || httpMethod == DataSpec.HTTP_METHOD_HEAD)
+ && (httpURLConnectionResponseCode == HttpURLConnection.HTTP_MULT_CHOICE
+ || httpURLConnectionResponseCode == HttpURLConnection.HTTP_MOVED_PERM
+ || httpURLConnectionResponseCode == HttpURLConnection.HTTP_MOVED_TEMP
+ || httpURLConnectionResponseCode == HttpURLConnection.HTTP_SEE_OTHER
+ || httpURLConnectionResponseCode == HTTP_STATUS_TEMPORARY_REDIRECT
+ || httpURLConnectionResponseCode == HTTP_STATUS_PERMANENT_REDIRECT)) {
+ httpURLConnection.disconnect();
+ url = handleRedirect(url, location, dataSpecToUse);
+ } else if (httpMethod == DataSpec.HTTP_METHOD_POST
+ && (httpURLConnectionResponseCode == HttpURLConnection.HTTP_MULT_CHOICE
+ || httpURLConnectionResponseCode == HttpURLConnection.HTTP_MOVED_PERM
+ || httpURLConnectionResponseCode == HttpURLConnection.HTTP_MOVED_TEMP
+ || httpURLConnectionResponseCode == HttpURLConnection.HTTP_SEE_OTHER)) {
+ httpURLConnection.disconnect();
+ final boolean shouldKeepPost = keepPostFor302Redirects
+ && responseCode == HttpURLConnection.HTTP_MOVED_TEMP;
+ if (!shouldKeepPost) {
+ // POST request follows the redirect and is transformed into a GET request.
+ httpMethod = DataSpec.HTTP_METHOD_GET;
+ httpBody = null;
+ }
+ url = handleRedirect(url, location, dataSpecToUse);
+ } else {
+ return httpURLConnection;
+ }
+ }
+
+ // If we get here we've been redirected more times than are permitted.
+ throw new HttpDataSourceException(
+ new NoRouteToHostException("Too many redirects: " + redirectCount),
+ dataSpecToUse,
+ PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED,
+ HttpDataSourceException.TYPE_OPEN);
+ }
+
+ /**
+ * Configures a connection and opens it.
+ *
+ * @param url The url to connect to.
+ * @param httpMethod The http method.
+ * @param httpBody The body data, or {@code null} if not required.
+ * @param position The byte offset of the requested data.
+ * @param length The length of the requested data, or {@link C#LENGTH_UNSET}.
+ * @param allowGzip Whether to allow the use of gzip.
+ * @param followRedirects Whether to follow redirects.
+ * @param requestParameters parameters (HTTP headers) to include in request.
+ * @return the connection opened
+ */
+ @SuppressWarnings("checkstyle:ParameterNumber")
+ @NonNull
+ private HttpURLConnection makeConnection(
+ @NonNull final URL url,
+ @HttpMethod final int httpMethod,
+ @Nullable final byte[] httpBody,
+ final long position,
+ final long length,
+ final boolean allowGzip,
+ final boolean followRedirects,
+ final Map requestParameters) throws IOException {
+ // This is the method that contains breaking changes with respect to DefaultHttpDataSource!
+
+ String requestUrl = url.toString();
+
+ // Don't add the request number parameter if it has been already added (for instance in
+ // DASH manifests) or if that's not a videoplayback URL
+ final boolean isVideoPlaybackUrl = url.getPath().startsWith("/videoplayback");
+ if (isVideoPlaybackUrl && rnParameterEnabled && !requestUrl.contains(RN_PARAMETER)) {
+ requestUrl += RN_PARAMETER + requestNumber;
+ ++requestNumber;
+ }
+
+ if (rangeParameterEnabled && isVideoPlaybackUrl) {
+ final String rangeParameterBuilt = buildRangeParameter(position, length);
+ if (rangeParameterBuilt != null) {
+ requestUrl += rangeParameterBuilt;
+ }
+ }
+
+ final HttpURLConnection httpURLConnection = openConnection(new URL(requestUrl));
+ httpURLConnection.setConnectTimeout(connectTimeoutMillis);
+ httpURLConnection.setReadTimeout(readTimeoutMillis);
+
+ final Map requestHeaders = new HashMap<>();
+ if (defaultRequestProperties != null) {
+ requestHeaders.putAll(defaultRequestProperties.getSnapshot());
+ }
+ requestHeaders.putAll(requestProperties.getSnapshot());
+ requestHeaders.putAll(requestParameters);
+
+ for (final Map.Entry property : requestHeaders.entrySet()) {
+ httpURLConnection.setRequestProperty(property.getKey(), property.getValue());
+ }
+
+ if (!rangeParameterEnabled) {
+ final String rangeHeader = buildRangeRequestHeader(position, length);
+ if (rangeHeader != null) {
+ httpURLConnection.setRequestProperty(HttpHeaders.RANGE, rangeHeader);
+ }
+ }
+
+ if (isWebStreamingUrl(requestUrl)
+ || isTvHtml5SimplyEmbeddedPlayerStreamingUrl(requestUrl)) {
+ httpURLConnection.setRequestProperty(HttpHeaders.ORIGIN, YOUTUBE_BASE_URL);
+ httpURLConnection.setRequestProperty(HttpHeaders.REFERER, YOUTUBE_BASE_URL);
+ httpURLConnection.setRequestProperty(HttpHeaders.SEC_FETCH_DEST, "empty");
+ httpURLConnection.setRequestProperty(HttpHeaders.SEC_FETCH_MODE, "cors");
+ httpURLConnection.setRequestProperty(HttpHeaders.SEC_FETCH_SITE, "cross-site");
+ }
+
+ httpURLConnection.setRequestProperty(HttpHeaders.TE, "trailers");
+
+ final boolean isAndroidStreamingUrl = isAndroidStreamingUrl(requestUrl);
+ final boolean isIosStreamingUrl = isIosStreamingUrl(requestUrl);
+ if (isAndroidStreamingUrl) {
+ // Improvement which may be done: find the content country used to request YouTube
+ // contents to add it in the user agent instead of using the default
+ httpURLConnection.setRequestProperty(HttpHeaders.USER_AGENT,
+ getAndroidUserAgent(null));
+ } else if (isIosStreamingUrl) {
+ httpURLConnection.setRequestProperty(HttpHeaders.USER_AGENT,
+ getIosUserAgent(null));
+ } else {
+ // non-mobile user agent
+ httpURLConnection.setRequestProperty(HttpHeaders.USER_AGENT, DownloaderImpl.USER_AGENT);
+ }
+
+ httpURLConnection.setRequestProperty(HttpHeaders.ACCEPT_ENCODING,
+ allowGzip ? "gzip" : "identity");
+ httpURLConnection.setInstanceFollowRedirects(followRedirects);
+ httpURLConnection.setDoOutput(httpBody != null);
+
+ // Mobile clients uses POST requests to fetch contents
+ httpURLConnection.setRequestMethod(isAndroidStreamingUrl || isIosStreamingUrl
+ ? "POST"
+ : DataSpec.getStringForHttpMethod(httpMethod));
+
+ if (httpBody != null) {
+ httpURLConnection.setFixedLengthStreamingMode(httpBody.length);
+ httpURLConnection.connect();
+ final OutputStream os = httpURLConnection.getOutputStream();
+ os.write(httpBody);
+ os.close();
+ } else {
+ httpURLConnection.connect();
+ }
+ return httpURLConnection;
+ }
+
+ /**
+ * Creates an {@link HttpURLConnection} that is connected with the {@code url}.
+ *
+ * @param url the {@link URL} to create an {@link HttpURLConnection}
+ * @return an {@link HttpURLConnection} created with the {@code url}
+ */
+ private HttpURLConnection openConnection(@NonNull final URL url) throws IOException {
+ return (HttpURLConnection) url.openConnection();
+ }
+
+ /**
+ * Handles a redirect.
+ *
+ * @param originalUrl The original URL.
+ * @param location The Location header in the response. May be {@code null}.
+ * @param dataSpecToHandleRedirect The {@link DataSpec}.
+ * @return The next URL.
+ * @throws HttpDataSourceException If redirection isn't possible.
+ */
+ @NonNull
+ private URL handleRedirect(final URL originalUrl,
+ @Nullable final String location,
+ final DataSpec dataSpecToHandleRedirect)
+ throws HttpDataSourceException {
+ if (location == null) {
+ throw new HttpDataSourceException("Null location redirect", dataSpecToHandleRedirect,
+ PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED,
+ HttpDataSourceException.TYPE_OPEN);
+ }
+
+ // Form the new url.
+ final URL url;
+ try {
+ url = new URL(originalUrl, location);
+ } catch (final MalformedURLException e) {
+ throw new HttpDataSourceException(e, dataSpecToHandleRedirect,
+ PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED,
+ HttpDataSourceException.TYPE_OPEN);
+ }
+
+ // Check that the protocol of the new url is supported.
+ final String protocol = url.getProtocol();
+ if (!"https".equals(protocol) && !"http".equals(protocol)) {
+ throw new HttpDataSourceException("Unsupported protocol redirect: " + protocol,
+ dataSpecToHandleRedirect,
+ PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED,
+ HttpDataSourceException.TYPE_OPEN);
+ }
+
+ if (!allowCrossProtocolRedirects && !protocol.equals(originalUrl.getProtocol())) {
+ throw new HttpDataSourceException(
+ "Disallowed cross-protocol redirect ("
+ + originalUrl.getProtocol()
+ + " to "
+ + protocol
+ + ")",
+ dataSpecToHandleRedirect,
+ PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED,
+ HttpDataSourceException.TYPE_OPEN);
+ }
+
+ return url;
+ }
+
+ /**
+ * Attempts to skip the specified number of bytes in full.
+ *
+ * @param bytesToSkip The number of bytes to skip.
+ * @param dataSpecToUse The {@link DataSpec}.
+ * @throws IOException If the thread is interrupted during the operation, or if the data ended
+ * before skipping the specified number of bytes.
+ */
+ @SuppressWarnings("checkstyle:FinalParameters")
+ private void skipFully(long bytesToSkip, final DataSpec dataSpecToUse) throws IOException {
+ if (bytesToSkip == 0) {
+ return;
+ }
+
+ final byte[] skipBuffer = new byte[4096];
+ while (bytesToSkip > 0) {
+ final int readLength = (int) min(bytesToSkip, skipBuffer.length);
+ final int read = castNonNull(inputStream).read(skipBuffer, 0, readLength);
+ if (Thread.currentThread().isInterrupted()) {
+ throw new HttpDataSourceException(
+ new InterruptedIOException(),
+ dataSpecToUse,
+ PlaybackException.ERROR_CODE_IO_UNSPECIFIED,
+ HttpDataSourceException.TYPE_OPEN);
+ }
+
+ if (read == -1) {
+ throw new HttpDataSourceException(
+ dataSpecToUse,
+ PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE,
+ HttpDataSourceException.TYPE_OPEN);
+ }
+
+ bytesToSkip -= read;
+ bytesTransferred(read);
+ }
+ }
+
+ /**
+ * Reads up to {@code length} bytes of data and stores them into {@code buffer}, starting at
+ * index {@code offset}.
+ *
+ *
+ * This method blocks until at least one byte of data can be read, the end of the opened range
+ * is detected, or an exception is thrown.
+ *
+ *
+ * @param buffer The buffer into which the read data should be stored.
+ * @param offset The start offset into {@code buffer} at which data should be written.
+ * @param readLength The maximum number of bytes to read.
+ * @return The number of bytes read, or {@link C#RESULT_END_OF_INPUT} if the end of the opened
+ * range is reached.
+ * @throws IOException If an error occurs reading from the source.
+ */
+ @SuppressWarnings("checkstyle:FinalParameters")
+ private int readInternal(final byte[] buffer, final int offset, int readLength)
+ throws IOException {
+ if (readLength == 0) {
+ return 0;
+ }
+ if (bytesToRead != C.LENGTH_UNSET) {
+ final long bytesRemaining = bytesToRead - bytesRead;
+ if (bytesRemaining == 0) {
+ return C.RESULT_END_OF_INPUT;
+ }
+ readLength = (int) min(readLength, bytesRemaining);
+ }
+
+ final int read = castNonNull(inputStream).read(buffer, offset, readLength);
+ if (read == -1) {
+ return C.RESULT_END_OF_INPUT;
+ }
+
+ bytesRead += read;
+ bytesTransferred(read);
+ return read;
+ }
+
+ /**
+ * On platform API levels 19 and 20, okhttp's implementation of {@link InputStream#close} can
+ * block for a long time if the stream has a lot of data remaining. Call this method before
+ * closing the input stream to make a best effort to cause the input stream to encounter an
+ * unexpected end of input, working around this issue. On other platform API levels, the method
+ * does nothing.
+ *
+ * @param connection The connection whose {@link InputStream} should be terminated.
+ * @param bytesRemaining The number of bytes remaining to be read from the input stream if its
+ * length is known. {@link C#LENGTH_UNSET} otherwise.
+ */
+ private static void maybeTerminateInputStream(@Nullable final HttpURLConnection connection,
+ final long bytesRemaining) {
+ if (connection == null || Util.SDK_INT < 19 || Util.SDK_INT > 20) {
+ return;
+ }
+
+ try {
+ final InputStream inputStream = connection.getInputStream();
+ if (bytesRemaining == C.LENGTH_UNSET) {
+ // If the input stream has already ended, do nothing. The socket may be re-used.
+ if (inputStream.read() == -1) {
+ return;
+ }
+ } else if (bytesRemaining <= MAX_BYTES_TO_DRAIN) {
+ // There isn't much data left. Prefer to allow it to drain, which may allow the
+ // socket to be re-used.
+ return;
+ }
+ final String className = inputStream.getClass().getName();
+ if ("com.android.okhttp.internal.http.HttpTransport$ChunkedInputStream"
+ .equals(className)
+ || "com.android.okhttp.internal.http.HttpTransport$FixedLengthInputStream"
+ .equals(className)) {
+ final Class> superclass = inputStream.getClass().getSuperclass();
+ final Method unexpectedEndOfInput = checkNotNull(superclass).getDeclaredMethod(
+ "unexpectedEndOfInput");
+ unexpectedEndOfInput.setAccessible(true);
+ unexpectedEndOfInput.invoke(inputStream);
+ }
+ } catch (final Exception e) {
+ // If an IOException then the connection didn't ever have an input stream, or it was
+ // closed already. If another type of exception then something went wrong, most likely
+ // the device isn't using okhttp.
+ }
+ }
+
+ /**
+ * Closes the current connection quietly, if there is one.
+ */
+ private void closeConnectionQuietly() {
+ if (connection != null) {
+ try {
+ connection.disconnect();
+ } catch (final Exception e) {
+ Log.e(TAG, "Unexpected error while disconnecting", e);
+ }
+ connection = null;
+ }
+ }
+
+ private static boolean isCompressed(@NonNull final HttpURLConnection connection) {
+ final String contentEncoding = connection.getHeaderField("Content-Encoding");
+ return "gzip".equalsIgnoreCase(contentEncoding);
+ }
+
+ /**
+ * Builds a {@code range} parameter for the given position and length.
+ *
+ *
+ * To fetch its contents, YouTube use range requests which append a {@code range} parameter
+ * to videoplayback URLs instead of the {@code Range} header (even if the server respond
+ * correctly when requesting a range of a ressouce with it).
+ *
+ *
+ *
+ * The parameter works in the same way as the header.
+ *
+ *
+ * @param position The request position.
+ * @param length The request length, or {@link C#LENGTH_UNSET} if the request is unbounded.
+ * @return The corresponding {@code range} parameter, or {@code null} if this parameter is
+ * unnecessary because the whole resource is being requested.
+ */
+ @Nullable
+ private static String buildRangeParameter(final long position, final long length) {
+ if (position == 0 && length == C.LENGTH_UNSET) {
+ return null;
+ }
+
+ final StringBuilder rangeParameter = new StringBuilder();
+ rangeParameter.append("&range=");
+ rangeParameter.append(position);
+ rangeParameter.append("-");
+ if (length != C.LENGTH_UNSET) {
+ rangeParameter.append(position + length - 1);
+ }
+ return rangeParameter.toString();
+ }
+
+ private static final class NullFilteringHeadersMap
+ extends ForwardingMap> {
+ private final Map> headers;
+
+ NullFilteringHeadersMap(final Map> headers) {
+ this.headers = headers;
+ }
+
+ @NonNull
+ @Override
+ protected Map> delegate() {
+ return headers;
+ }
+
+ @Override
+ public boolean containsKey(@Nullable final Object key) {
+ return key != null && super.containsKey(key);
+ }
+
+ @Nullable
+ @Override
+ public List get(@Nullable final Object key) {
+ return key == null ? null : super.get(key);
+ }
+
+ @NonNull
+ @Override
+ public Set keySet() {
+ return Sets.filter(super.keySet(), Objects::nonNull);
+ }
+
+ @NonNull
+ @Override
+ public Set>> entrySet() {
+ return Sets.filter(super.entrySet(), entry -> entry.getKey() != null);
+ }
+
+ @Override
+ public int size() {
+ return super.size() - (super.containsKey(null) ? 1 : 0);
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return super.isEmpty() || (super.size() == 1 && super.containsKey(null));
+ }
+
+ @Override
+ public boolean containsValue(@Nullable final Object value) {
+ return super.standardContainsValue(value);
+ }
+
+ @Override
+ public boolean equals(@Nullable final Object object) {
+ return object != null && super.standardEquals(object);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.standardHashCode();
+ }
+ }
+}
+
diff --git a/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java b/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java
index 794fe9b3c..a7fb40c47 100644
--- a/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java
+++ b/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java
@@ -126,6 +126,14 @@ public class PlayerGestureListener
}
private void onScrollMainVolume(final float distanceX, final float distanceY) {
+ // If we just started sliding, change the progress bar to match the system volume
+ if (player.getVolumeRelativeLayout().getVisibility() != View.VISIBLE) {
+ final float volumePercent = player
+ .getAudioReactor().getVolume() / (float) maxVolume;
+ player.getVolumeProgressBar().setProgress(
+ (int) (volumePercent * player.getMaxGestureLength()));
+ }
+
player.getVolumeProgressBar().incrementProgressBy((int) distanceY);
final float currentProgressPercent = (float) player
.getVolumeProgressBar().getProgress() / player.getMaxGestureLength();
diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java b/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java
index 98e04d466..d189616d1 100644
--- a/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java
+++ b/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java
@@ -1,96 +1,46 @@
package org.schabi.newpipe.player.helper;
import android.content.Context;
-import android.util.Log;
-import com.google.android.exoplayer2.database.StandaloneDatabaseProvider;
+import androidx.annotation.NonNull;
+
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSource;
-import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.upstream.FileDataSource;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.upstream.cache.CacheDataSink;
import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
-import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor;
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
-import java.io.File;
+final class CacheFactory implements DataSource.Factory {
+ private static final int CACHE_FLAGS = CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR;
-import androidx.annotation.NonNull;
+ private final Context context;
+ private final TransferListener transferListener;
+ private final DataSource.Factory upstreamDataSourceFactory;
+ private final SimpleCache cache;
-/* package-private */ class CacheFactory implements DataSource.Factory {
- private static final String TAG = "CacheFactory";
-
- private static final String CACHE_FOLDER_NAME = "exoplayer";
- private static final int CACHE_FLAGS = CacheDataSource.FLAG_BLOCK_ON_CACHE
- | CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR;
-
- private final DataSource.Factory dataSourceFactory;
- private final File cacheDir;
- private final long maxFileSize;
-
- // Creating cache on every instance may cause problems with multiple players when
- // sources are not ExtractorMediaSource
- // see: https://stackoverflow.com/questions/28700391/using-cache-in-exoplayer
- // todo: make this a singleton?
- private static SimpleCache cache;
-
- CacheFactory(@NonNull final Context context,
- @NonNull final String userAgent,
- @NonNull final TransferListener transferListener) {
- this(context, userAgent, transferListener, PlayerHelper.getPreferredCacheSize(),
- PlayerHelper.getPreferredFileSize());
- }
-
- private CacheFactory(@NonNull final Context context,
- @NonNull final String userAgent,
- @NonNull final TransferListener transferListener,
- final long maxCacheSize,
- final long maxFileSize) {
- this.maxFileSize = maxFileSize;
-
- dataSourceFactory = new DefaultDataSource
- .Factory(context, new DefaultHttpDataSource.Factory().setUserAgent(userAgent))
- .setTransferListener(transferListener);
- cacheDir = new File(context.getExternalCacheDir(), CACHE_FOLDER_NAME);
- if (!cacheDir.exists()) {
- //noinspection ResultOfMethodCallIgnored
- cacheDir.mkdir();
- }
-
- if (cache == null) {
- final LeastRecentlyUsedCacheEvictor evictor
- = new LeastRecentlyUsedCacheEvictor(maxCacheSize);
- cache = new SimpleCache(cacheDir, evictor, new StandaloneDatabaseProvider(context));
- }
+ CacheFactory(final Context context,
+ final TransferListener transferListener,
+ final SimpleCache cache,
+ final DataSource.Factory upstreamDataSourceFactory) {
+ this.context = context;
+ this.transferListener = transferListener;
+ this.cache = cache;
+ this.upstreamDataSourceFactory = upstreamDataSourceFactory;
}
@NonNull
@Override
public DataSource createDataSource() {
- Log.d(TAG, "initExoPlayerCache: cacheDir = " + cacheDir.getAbsolutePath());
+ final DefaultDataSource dataSource = new DefaultDataSource.Factory(context,
+ upstreamDataSourceFactory)
+ .setTransferListener(transferListener)
+ .createDataSource();
- final DataSource dataSource = dataSourceFactory.createDataSource();
final FileDataSource fileSource = new FileDataSource();
- final CacheDataSink dataSink = new CacheDataSink(cache, maxFileSize);
-
+ final CacheDataSink dataSink
+ = new CacheDataSink(cache, PlayerHelper.getPreferredFileSize());
return new CacheDataSource(cache, dataSource, fileSource, dataSink, CACHE_FLAGS, null);
}
-
- public void tryDeleteCacheFiles() {
- if (!cacheDir.exists() || !cacheDir.isDirectory()) {
- return;
- }
-
- try {
- for (final File file : cacheDir.listFiles()) {
- final String filePath = file.getAbsolutePath();
- final boolean deleteSuccessful = file.delete();
-
- Log.d(TAG, "tryDeleteCacheFiles: " + filePath + " deleted = " + deleteSuccessful);
- }
- } catch (final Exception e) {
- Log.e(TAG, "Failed to delete file.", e);
- }
- }
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java b/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java
index c12ba754a..a8735dc08 100644
--- a/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java
+++ b/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java
@@ -13,6 +13,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.media.session.MediaButtonReceiver;
+import com.google.android.exoplayer2.ForwardingPlayer;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
@@ -55,7 +56,17 @@ public class MediaSessionManager {
sessionConnector = new MediaSessionConnector(mediaSession);
sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, callback));
- sessionConnector.setPlayer(player);
+ sessionConnector.setPlayer(new ForwardingPlayer(player) {
+ @Override
+ public void play() {
+ callback.play();
+ }
+
+ @Override
+ public void pause() {
+ callback.pause();
+ }
+ });
}
@Nullable
diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java
index 1a55c21c3..19a5a645b 100644
--- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java
+++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java
@@ -1,123 +1,121 @@
package org.schabi.newpipe.player.helper;
+import static org.schabi.newpipe.ktx.ViewUtils.animateRotation;
import static org.schabi.newpipe.player.Player.DEBUG;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
+import static org.schabi.newpipe.util.ThemeHelper.resolveDrawable;
import android.app.Dialog;
import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
import android.os.Bundle;
import android.util.Log;
+import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
-import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import androidx.preference.PreferenceManager;
import org.schabi.newpipe.R;
+import org.schabi.newpipe.databinding.DialogPlaybackParameterBinding;
+import org.schabi.newpipe.player.Player;
import org.schabi.newpipe.util.SimpleOnSeekBarChangeListener;
import org.schabi.newpipe.util.SliderStrategy;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.function.DoubleConsumer;
+import java.util.function.DoubleFunction;
+import java.util.function.DoubleSupplier;
+
+import icepick.Icepick;
+import icepick.State;
+
public class PlaybackParameterDialog extends DialogFragment {
+ private static final String TAG = "PlaybackParameterDialog";
+
// Minimum allowable range in ExoPlayer
- private static final double MINIMUM_PLAYBACK_VALUE = 0.10f;
- private static final double MAXIMUM_PLAYBACK_VALUE = 3.00f;
+ private static final double MIN_PITCH_OR_SPEED = 0.10f;
+ private static final double MAX_PITCH_OR_SPEED = 3.00f;
- private static final char STEP_UP_SIGN = '+';
- private static final char STEP_DOWN_SIGN = '-';
+ private static final boolean PITCH_CTRL_MODE_PERCENT = false;
+ private static final boolean PITCH_CTRL_MODE_SEMITONE = true;
- private static final double STEP_ONE_PERCENT_VALUE = 0.01f;
- private static final double STEP_FIVE_PERCENT_VALUE = 0.05f;
- private static final double STEP_TEN_PERCENT_VALUE = 0.10f;
- private static final double STEP_TWENTY_FIVE_PERCENT_VALUE = 0.25f;
- private static final double STEP_ONE_HUNDRED_PERCENT_VALUE = 1.00f;
+ private static final double STEP_1_PERCENT_VALUE = 0.01f;
+ private static final double STEP_5_PERCENT_VALUE = 0.05f;
+ private static final double STEP_10_PERCENT_VALUE = 0.10f;
+ private static final double STEP_25_PERCENT_VALUE = 0.25f;
+ private static final double STEP_100_PERCENT_VALUE = 1.00f;
private static final double DEFAULT_TEMPO = 1.00f;
- private static final double DEFAULT_PITCH = 1.00f;
- private static final int DEFAULT_SEMITONES = 0;
- private static final double DEFAULT_STEP = STEP_TWENTY_FIVE_PERCENT_VALUE;
+ private static final double DEFAULT_PITCH_PERCENT = 1.00f;
+ private static final double DEFAULT_STEP = STEP_25_PERCENT_VALUE;
private static final boolean DEFAULT_SKIP_SILENCE = false;
- @NonNull
- private static final String TAG = "PlaybackParameterDialog";
- @NonNull
- private static final String INITIAL_TEMPO_KEY = "initial_tempo_key";
- @NonNull
- private static final String INITIAL_PITCH_KEY = "initial_pitch_key";
+ private static final SliderStrategy QUADRATIC_STRATEGY = new SliderStrategy.Quadratic(
+ MIN_PITCH_OR_SPEED,
+ MAX_PITCH_OR_SPEED,
+ 1.00f,
+ 10_000);
- @NonNull
- private static final String TEMPO_KEY = "tempo_key";
- @NonNull
- private static final String PITCH_KEY = "pitch_key";
- @NonNull
- private static final String STEP_SIZE_KEY = "step_size_key";
+ private static final SliderStrategy SEMITONE_STRATEGY = new SliderStrategy() {
+ @Override
+ public int progressOf(final double value) {
+ return PlayerSemitoneHelper.percentToSemitones(value) + 12;
+ }
- @NonNull
- private final SliderStrategy strategy = new SliderStrategy.Quadratic(
- MINIMUM_PLAYBACK_VALUE, MAXIMUM_PLAYBACK_VALUE,
- /*centerAt=*/1.00f, /*sliderGranularity=*/10000);
+ @Override
+ public double valueOf(final int progress) {
+ return PlayerSemitoneHelper.semitonesToPercent(progress - 12);
+ }
+ };
@Nullable
private Callback callback;
- private double initialTempo = DEFAULT_TEMPO;
- private double initialPitch = DEFAULT_PITCH;
- private int initialSemitones = DEFAULT_SEMITONES;
- private boolean initialSkipSilence = DEFAULT_SKIP_SILENCE;
- private double tempo = DEFAULT_TEMPO;
- private double pitch = DEFAULT_PITCH;
- private int semitones = DEFAULT_SEMITONES;
+ @State
+ double initialTempo = DEFAULT_TEMPO;
+ @State
+ double initialPitchPercent = DEFAULT_PITCH_PERCENT;
+ @State
+ boolean initialSkipSilence = DEFAULT_SKIP_SILENCE;
- @Nullable
- private SeekBar tempoSlider;
- @Nullable
- private TextView tempoCurrentText;
- @Nullable
- private TextView tempoStepDownText;
- @Nullable
- private TextView tempoStepUpText;
- @Nullable
- private SeekBar pitchSlider;
- @Nullable
- private TextView pitchCurrentText;
- @Nullable
- private TextView pitchStepDownText;
- @Nullable
- private TextView pitchStepUpText;
- @Nullable
- private SeekBar semitoneSlider;
- @Nullable
- private TextView semitoneCurrentText;
- @Nullable
- private TextView semitoneStepDownText;
- @Nullable
- private TextView semitoneStepUpText;
- @Nullable
- private CheckBox unhookingCheckbox;
- @Nullable
- private CheckBox skipSilenceCheckbox;
- @Nullable
- private CheckBox adjustBySemitonesCheckbox;
+ @State
+ double tempo = DEFAULT_TEMPO;
+ @State
+ double pitchPercent = DEFAULT_PITCH_PERCENT;
+ @State
+ boolean skipSilence = DEFAULT_SKIP_SILENCE;
- public static PlaybackParameterDialog newInstance(final double playbackTempo,
- final double playbackPitch,
- final boolean playbackSkipSilence,
- final Callback callback) {
+ private DialogPlaybackParameterBinding binding;
+
+ public static PlaybackParameterDialog newInstance(
+ final double playbackTempo,
+ final double playbackPitch,
+ final boolean playbackSkipSilence,
+ final Callback callback
+ ) {
final PlaybackParameterDialog dialog = new PlaybackParameterDialog();
dialog.callback = callback;
+
dialog.initialTempo = playbackTempo;
- dialog.initialPitch = playbackPitch;
-
- dialog.tempo = playbackTempo;
- dialog.pitch = playbackPitch;
- dialog.semitones = dialog.percentToSemitones(playbackPitch);
-
+ dialog.initialPitchPercent = playbackPitch;
dialog.initialSkipSilence = playbackSkipSilence;
+
+ dialog.tempo = dialog.initialTempo;
+ dialog.pitchPercent = dialog.initialPitchPercent;
+ dialog.skipSilence = dialog.initialSkipSilence;
+
return dialog;
}
@@ -135,29 +133,10 @@ public class PlaybackParameterDialog extends DialogFragment {
}
}
- @Override
- public void onCreate(@Nullable final Bundle savedInstanceState) {
- assureCorrectAppLanguage(getContext());
- super.onCreate(savedInstanceState);
- if (savedInstanceState != null) {
- initialTempo = savedInstanceState.getDouble(INITIAL_TEMPO_KEY, DEFAULT_TEMPO);
- initialPitch = savedInstanceState.getDouble(INITIAL_PITCH_KEY, DEFAULT_PITCH);
- initialSemitones = percentToSemitones(initialPitch);
-
- tempo = savedInstanceState.getDouble(TEMPO_KEY, DEFAULT_TEMPO);
- pitch = savedInstanceState.getDouble(PITCH_KEY, DEFAULT_PITCH);
- semitones = percentToSemitones(pitch);
- }
- }
-
@Override
public void onSaveInstanceState(@NonNull final Bundle outState) {
super.onSaveInstanceState(outState);
- outState.putDouble(INITIAL_TEMPO_KEY, initialTempo);
- outState.putDouble(INITIAL_PITCH_KEY, initialPitch);
-
- outState.putDouble(TEMPO_KEY, getCurrentTempo());
- outState.putDouble(PITCH_KEY, getCurrentPitch());
+ Icepick.saveInstanceState(this, outState);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -168,327 +147,345 @@ public class PlaybackParameterDialog extends DialogFragment {
@Override
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
assureCorrectAppLanguage(getContext());
- final View view = View.inflate(getContext(), R.layout.dialog_playback_parameter, null);
- setupControlViews(view);
+ Icepick.restoreInstanceState(this, savedInstanceState);
+
+ binding = DialogPlaybackParameterBinding.inflate(LayoutInflater.from(getContext()));
+ initUI();
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity())
- .setView(view)
+ .setView(binding.getRoot())
.setCancelable(true)
- .setNegativeButton(R.string.cancel, (dialogInterface, i) ->
- setPlaybackParameters(initialTempo, initialPitch,
- initialSemitones, initialSkipSilence))
- .setNeutralButton(R.string.playback_reset, (dialogInterface, i) ->
- setPlaybackParameters(DEFAULT_TEMPO, DEFAULT_PITCH,
- DEFAULT_SEMITONES, DEFAULT_SKIP_SILENCE))
- .setPositiveButton(R.string.ok, (dialogInterface, i) ->
- setCurrentPlaybackParameters());
+ .setNegativeButton(R.string.cancel, (dialogInterface, i) -> {
+ setAndUpdateTempo(initialTempo);
+ setAndUpdatePitch(initialPitchPercent);
+ setAndUpdateSkipSilence(initialSkipSilence);
+ updateCallback();
+ })
+ .setNeutralButton(R.string.playback_reset, (dialogInterface, i) -> {
+ setAndUpdateTempo(DEFAULT_TEMPO);
+ setAndUpdatePitch(DEFAULT_PITCH_PERCENT);
+ setAndUpdateSkipSilence(DEFAULT_SKIP_SILENCE);
+ updateCallback();
+ })
+ .setPositiveButton(R.string.ok, (dialogInterface, i) -> updateCallback());
return dialogBuilder.create();
}
/*//////////////////////////////////////////////////////////////////////////
- // Control Views
+ // UI Initialization and Control
//////////////////////////////////////////////////////////////////////////*/
- private void setupControlViews(@NonNull final View rootView) {
- setupHookingControl(rootView);
- setupSkipSilenceControl(rootView);
- setupAdjustBySemitonesControl(rootView);
+ private void initUI() {
+ // Tempo
+ setText(binding.tempoMinimumText, PlayerHelper::formatSpeed, MIN_PITCH_OR_SPEED);
+ setText(binding.tempoMaximumText, PlayerHelper::formatSpeed, MAX_PITCH_OR_SPEED);
- setupTempoControl(rootView);
- setupPitchControl(rootView);
- setupSemitoneControl(rootView);
+ binding.tempoSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAX_PITCH_OR_SPEED));
+ setAndUpdateTempo(tempo);
+ binding.tempoSeekbar.setOnSeekBarChangeListener(
+ getTempoOrPitchSeekbarChangeListener(
+ QUADRATIC_STRATEGY,
+ this::onTempoSliderUpdated));
- togglePitchSliderType(rootView);
+ registerOnStepClickListener(
+ binding.tempoStepDown,
+ () -> tempo,
+ -1,
+ this::onTempoSliderUpdated);
+ registerOnStepClickListener(
+ binding.tempoStepUp,
+ () -> tempo,
+ 1,
+ this::onTempoSliderUpdated);
- setupStepSizeSelector(rootView);
+ // Pitch
+ binding.pitchToogleControlModes.setOnClickListener(v -> {
+ final boolean isCurrentlyVisible =
+ binding.pitchControlModeTabs.getVisibility() == View.GONE;
+ binding.pitchControlModeTabs.setVisibility(isCurrentlyVisible
+ ? View.VISIBLE
+ : View.GONE);
+ animateRotation(binding.pitchToogleControlModes,
+ Player.DEFAULT_CONTROLS_DURATION,
+ isCurrentlyVisible ? 180 : 0);
+ });
+
+ getPitchControlModeComponentMappings()
+ .forEach(this::setupPitchControlModeTextView);
+ // Initialization is done at the end
+
+ // Pitch - Percent
+ setText(binding.pitchPercentMinimumText, PlayerHelper::formatPitch, MIN_PITCH_OR_SPEED);
+ setText(binding.pitchPercentMaximumText, PlayerHelper::formatPitch, MAX_PITCH_OR_SPEED);
+
+ binding.pitchPercentSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAX_PITCH_OR_SPEED));
+ setAndUpdatePitch(pitchPercent);
+ binding.pitchPercentSeekbar.setOnSeekBarChangeListener(
+ getTempoOrPitchSeekbarChangeListener(
+ QUADRATIC_STRATEGY,
+ this::onPitchPercentSliderUpdated));
+
+ registerOnStepClickListener(
+ binding.pitchPercentStepDown,
+ () -> pitchPercent,
+ -1,
+ this::onPitchPercentSliderUpdated);
+ registerOnStepClickListener(
+ binding.pitchPercentStepUp,
+ () -> pitchPercent,
+ 1,
+ this::onPitchPercentSliderUpdated);
+
+ // Pitch - Semitone
+ binding.pitchSemitoneSeekbar.setOnSeekBarChangeListener(
+ getTempoOrPitchSeekbarChangeListener(
+ SEMITONE_STRATEGY,
+ this::onPitchPercentSliderUpdated));
+
+ registerOnSemitoneStepClickListener(
+ binding.pitchSemitoneStepDown,
+ -1,
+ this::onPitchPercentSliderUpdated);
+ registerOnSemitoneStepClickListener(
+ binding.pitchSemitoneStepUp,
+ 1,
+ this::onPitchPercentSliderUpdated);
+
+ // Steps
+ getStepSizeComponentMappings()
+ .forEach(this::setupStepTextView);
+ // Initialize UI
+ setStepSizeToUI(getCurrentStepSize());
+
+ // Bottom controls
+ bindCheckboxWithBoolPref(
+ binding.unhookCheckbox,
+ R.string.playback_unhook_key,
+ true,
+ isChecked -> {
+ if (!isChecked) {
+ // when unchecked, slide back to the minimum of current tempo or pitch
+ ensureHookIsValidAndUpdateCallBack();
+ }
+ });
+
+ setAndUpdateSkipSilence(skipSilence);
+ binding.skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
+ skipSilence = isChecked;
+ updateCallback();
+ });
+
+ // PitchControlMode has to be initialized at the end because it requires the unhookCheckbox
+ changePitchControlMode(isCurrentPitchControlModeSemitone());
}
- private void togglePitchSliderType(@NonNull final View rootView) {
- final RelativeLayout pitchControl = rootView.findViewById(R.id.pitchControl);
- final RelativeLayout semitoneControl = rootView.findViewById(R.id.semitoneControl);
+ // -- General formatting --
- final View separatorStepSizeSelector =
- rootView.findViewById(R.id.separatorStepSizeSelector);
- final RelativeLayout.LayoutParams params =
- (RelativeLayout.LayoutParams) separatorStepSizeSelector.getLayoutParams();
- if (pitchControl != null && semitoneControl != null && unhookingCheckbox != null) {
- if (getCurrentAdjustBySemitones()) {
- // replaces pitchControl slider with semitoneControl slider
- pitchControl.setVisibility(View.GONE);
- semitoneControl.setVisibility(View.VISIBLE);
- params.addRule(RelativeLayout.BELOW, R.id.semitoneControl);
-
- // forces unhook for semitones
- unhookingCheckbox.setChecked(true);
- unhookingCheckbox.setEnabled(false);
-
- setupTempoStepSizeSelector(rootView);
- } else {
- semitoneControl.setVisibility(View.GONE);
- pitchControl.setVisibility(View.VISIBLE);
- params.addRule(RelativeLayout.BELOW, R.id.pitchControl);
-
- // (re)enables hooking selection
- unhookingCheckbox.setEnabled(true);
- setupCombinedStepSizeSelector(rootView);
- }
- }
+ private void setText(
+ final TextView textView,
+ final DoubleFunction formatter,
+ final double value
+ ) {
+ Objects.requireNonNull(textView).setText(formatter.apply(value));
}
- private void setupTempoControl(@NonNull final View rootView) {
- tempoSlider = rootView.findViewById(R.id.tempoSeekbar);
- final TextView tempoMinimumText = rootView.findViewById(R.id.tempoMinimumText);
- final TextView tempoMaximumText = rootView.findViewById(R.id.tempoMaximumText);
- tempoCurrentText = rootView.findViewById(R.id.tempoCurrentText);
- tempoStepUpText = rootView.findViewById(R.id.tempoStepUp);
- tempoStepDownText = rootView.findViewById(R.id.tempoStepDown);
+ // -- Steps --
- if (tempoCurrentText != null) {
- tempoCurrentText.setText(PlayerHelper.formatSpeed(tempo));
- }
- if (tempoMaximumText != null) {
- tempoMaximumText.setText(PlayerHelper.formatSpeed(MAXIMUM_PLAYBACK_VALUE));
- }
- if (tempoMinimumText != null) {
- tempoMinimumText.setText(PlayerHelper.formatSpeed(MINIMUM_PLAYBACK_VALUE));
- }
-
- if (tempoSlider != null) {
- tempoSlider.setMax(strategy.progressOf(MAXIMUM_PLAYBACK_VALUE));
- tempoSlider.setProgress(strategy.progressOf(tempo));
- tempoSlider.setOnSeekBarChangeListener(getOnTempoChangedListener());
- }
+ private void registerOnStepClickListener(
+ final TextView stepTextView,
+ final DoubleSupplier currentValueSupplier,
+ final double direction, // -1 for step down, +1 for step up
+ final DoubleConsumer newValueConsumer
+ ) {
+ stepTextView.setOnClickListener(view -> {
+ newValueConsumer.accept(
+ currentValueSupplier.getAsDouble() + 1 * getCurrentStepSize() * direction);
+ updateCallback();
+ });
}
- private void setupPitchControl(@NonNull final View rootView) {
- pitchSlider = rootView.findViewById(R.id.pitchSeekbar);
- final TextView pitchMinimumText = rootView.findViewById(R.id.pitchMinimumText);
- final TextView pitchMaximumText = rootView.findViewById(R.id.pitchMaximumText);
- pitchCurrentText = rootView.findViewById(R.id.pitchCurrentText);
- pitchStepDownText = rootView.findViewById(R.id.pitchStepDown);
- pitchStepUpText = rootView.findViewById(R.id.pitchStepUp);
-
- if (pitchCurrentText != null) {
- pitchCurrentText.setText(PlayerHelper.formatPitch(pitch));
- }
- if (pitchMaximumText != null) {
- pitchMaximumText.setText(PlayerHelper.formatPitch(MAXIMUM_PLAYBACK_VALUE));
- }
- if (pitchMinimumText != null) {
- pitchMinimumText.setText(PlayerHelper.formatPitch(MINIMUM_PLAYBACK_VALUE));
- }
-
- if (pitchSlider != null) {
- pitchSlider.setMax(strategy.progressOf(MAXIMUM_PLAYBACK_VALUE));
- pitchSlider.setProgress(strategy.progressOf(pitch));
- pitchSlider.setOnSeekBarChangeListener(getOnPitchChangedListener());
- }
+ private void registerOnSemitoneStepClickListener(
+ final TextView stepTextView,
+ final int direction, // -1 for step down, +1 for step up
+ final DoubleConsumer newValueConsumer
+ ) {
+ stepTextView.setOnClickListener(view -> {
+ newValueConsumer.accept(PlayerSemitoneHelper.semitonesToPercent(
+ PlayerSemitoneHelper.percentToSemitones(this.pitchPercent) + direction));
+ updateCallback();
+ });
}
- private void setupSemitoneControl(@NonNull final View rootView) {
- semitoneSlider = rootView.findViewById(R.id.semitoneSeekbar);
- semitoneCurrentText = rootView.findViewById(R.id.semitoneCurrentText);
- semitoneStepDownText = rootView.findViewById(R.id.semitoneStepDown);
- semitoneStepUpText = rootView.findViewById(R.id.semitoneStepUp);
+ // -- Pitch --
- if (semitoneCurrentText != null) {
- semitoneCurrentText.setText(getSignedSemitonesString(semitones));
- }
-
- if (semitoneSlider != null) {
- setSemitoneSlider(semitones);
- semitoneSlider.setOnSeekBarChangeListener(getOnSemitoneChangedListener());
- }
-
- }
-
- private void setupHookingControl(@NonNull final View rootView) {
- unhookingCheckbox = rootView.findViewById(R.id.unhookCheckbox);
- if (unhookingCheckbox != null) {
- // restores whether pitch and tempo are unhooked or not
- unhookingCheckbox.setChecked(PreferenceManager
- .getDefaultSharedPreferences(requireContext())
- .getBoolean(getString(R.string.playback_unhook_key), true));
-
- unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
- // saves whether pitch and tempo are unhooked or not
- PreferenceManager.getDefaultSharedPreferences(requireContext())
- .edit()
- .putBoolean(getString(R.string.playback_unhook_key), isChecked)
- .apply();
-
- if (!isChecked) {
- // when unchecked, slides back to the minimum of current tempo or pitch
- final double minimum = Math.min(getCurrentPitch(), getCurrentTempo());
- setSliders(minimum);
- setCurrentPlaybackParameters();
- }
- });
- }
- }
-
- private void setupSkipSilenceControl(@NonNull final View rootView) {
- skipSilenceCheckbox = rootView.findViewById(R.id.skipSilenceCheckbox);
- if (skipSilenceCheckbox != null) {
- skipSilenceCheckbox.setChecked(initialSkipSilence);
- skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) ->
- setCurrentPlaybackParameters());
- }
- }
-
- private void setupAdjustBySemitonesControl(@NonNull final View rootView) {
- adjustBySemitonesCheckbox = rootView.findViewById(R.id.adjustBySemitonesCheckbox);
- if (adjustBySemitonesCheckbox != null) {
- // restores whether semitone adjustment is used or not
- adjustBySemitonesCheckbox.setChecked(PreferenceManager
- .getDefaultSharedPreferences(requireContext())
- .getBoolean(getString(R.string.playback_adjust_by_semitones_key), true));
-
- // stores whether semitone adjustment is used or not
- adjustBySemitonesCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
- PreferenceManager.getDefaultSharedPreferences(requireContext())
+ private void setupPitchControlModeTextView(
+ final boolean semitones,
+ final TextView textView
+ ) {
+ textView.setOnClickListener(view -> {
+ PreferenceManager.getDefaultSharedPreferences(requireContext())
.edit()
- .putBoolean(getString(R.string.playback_adjust_by_semitones_key), isChecked)
+ .putBoolean(getString(R.string.playback_adjust_by_semitones_key), semitones)
.apply();
- togglePitchSliderType(rootView);
- if (isChecked) {
- setPlaybackParameters(
- getCurrentTempo(),
- getCurrentPitch(),
- Integer.min(12,
- Integer.max(-12, percentToSemitones(getCurrentPitch())
- )),
- getCurrentSkipSilence()
+
+ changePitchControlMode(semitones);
+ });
+ }
+
+ private Map getPitchControlModeComponentMappings() {
+ final Map mappings = new HashMap<>();
+ mappings.put(PITCH_CTRL_MODE_PERCENT, binding.pitchControlModePercent);
+ mappings.put(PITCH_CTRL_MODE_SEMITONE, binding.pitchControlModeSemitone);
+ return mappings;
+ }
+
+ private void changePitchControlMode(final boolean semitones) {
+ // Bring all textviews into a normal state
+ final Map pitchCtrlModeComponentMapping =
+ getPitchControlModeComponentMappings();
+ pitchCtrlModeComponentMapping.forEach((v, textView) -> textView.setBackground(
+ resolveDrawable(requireContext(), R.attr.selectableItemBackground)));
+
+ // Mark the selected textview
+ final TextView textView = pitchCtrlModeComponentMapping.get(semitones);
+ if (textView != null) {
+ textView.setBackground(new LayerDrawable(new Drawable[]{
+ resolveDrawable(requireContext(), R.attr.dashed_border),
+ resolveDrawable(requireContext(), R.attr.selectableItemBackground)
+ }));
+ }
+
+ // Show or hide component
+ binding.pitchPercentControl.setVisibility(semitones ? View.GONE : View.VISIBLE);
+ binding.pitchSemitoneControl.setVisibility(semitones ? View.VISIBLE : View.GONE);
+
+ if (semitones) {
+ // Recalculate pitch percent when changing to semitone
+ // (as it could be an invalid semitone value)
+ final double newPitchPercent = calcValidPitch(pitchPercent);
+
+ // If the values differ set the new pitch
+ if (this.pitchPercent != newPitchPercent) {
+ if (DEBUG) {
+ Log.d(TAG, "Bringing pitchPercent to correct corresponding semitone: "
+ + "currentPitchPercent = " + pitchPercent + ", "
+ + "newPitchPercent = " + newPitchPercent
);
- setSemitoneSlider(Integer.min(12,
- Integer.max(-12, percentToSemitones(getCurrentPitch()))
- ));
- } else {
- setPlaybackParameters(
- getCurrentTempo(),
- semitonesToPercent(getCurrentSemitones()),
- getCurrentSemitones(),
- getCurrentSkipSilence()
- );
- setPitchSlider(semitonesToPercent(getCurrentSemitones()));
}
- });
+ this.onPitchPercentSliderUpdated(newPitchPercent);
+ updateCallback();
+ }
+ } else if (!binding.unhookCheckbox.isChecked()) {
+ // When changing to percent it's possible that tempo is != pitch
+ ensureHookIsValidAndUpdateCallBack();
}
}
- private void setupStepSizeSelector(@NonNull final View rootView) {
- setStepSize(PreferenceManager
+ private boolean isCurrentPitchControlModeSemitone() {
+ return PreferenceManager.getDefaultSharedPreferences(requireContext())
+ .getBoolean(
+ getString(R.string.playback_adjust_by_semitones_key),
+ PITCH_CTRL_MODE_PERCENT);
+ }
+
+ // -- Steps (Set) --
+
+ private void setupStepTextView(
+ final double stepSizeValue,
+ final TextView textView
+ ) {
+ setText(textView, PlaybackParameterDialog::getPercentString, stepSizeValue);
+ textView.setOnClickListener(view -> {
+ PreferenceManager.getDefaultSharedPreferences(requireContext())
+ .edit()
+ .putFloat(getString(R.string.adjustment_step_key), (float) stepSizeValue)
+ .apply();
+
+ setStepSizeToUI(stepSizeValue);
+ });
+ }
+
+ private Map getStepSizeComponentMappings() {
+ final Map mappings = new HashMap<>();
+ mappings.put(STEP_1_PERCENT_VALUE, binding.stepSizeOnePercent);
+ mappings.put(STEP_5_PERCENT_VALUE, binding.stepSizeFivePercent);
+ mappings.put(STEP_10_PERCENT_VALUE, binding.stepSizeTenPercent);
+ mappings.put(STEP_25_PERCENT_VALUE, binding.stepSizeTwentyFivePercent);
+ mappings.put(STEP_100_PERCENT_VALUE, binding.stepSizeOneHundredPercent);
+ return mappings;
+ }
+
+ private void setStepSizeToUI(final double newStepSize) {
+ // Bring all textviews into a normal state
+ final Map stepSiteComponentMapping = getStepSizeComponentMappings();
+ stepSiteComponentMapping.forEach((v, textView) -> textView.setBackground(
+ resolveDrawable(requireContext(), R.attr.selectableItemBackground)));
+
+ // Mark the selected textview
+ final TextView textView = stepSiteComponentMapping.get(newStepSize);
+ if (textView != null) {
+ textView.setBackground(new LayerDrawable(new Drawable[]{
+ resolveDrawable(requireContext(), R.attr.dashed_border),
+ resolveDrawable(requireContext(), R.attr.selectableItemBackground)
+ }));
+ }
+
+ // Bind to the corresponding control components
+ binding.tempoStepUp.setText(getStepUpPercentString(newStepSize));
+ binding.tempoStepDown.setText(getStepDownPercentString(newStepSize));
+
+ binding.pitchPercentStepUp.setText(getStepUpPercentString(newStepSize));
+ binding.pitchPercentStepDown.setText(getStepDownPercentString(newStepSize));
+ }
+
+ private double getCurrentStepSize() {
+ return PreferenceManager.getDefaultSharedPreferences(requireContext())
+ .getFloat(getString(R.string.adjustment_step_key), (float) DEFAULT_STEP);
+ }
+
+ // -- Additional options --
+
+ private void setAndUpdateSkipSilence(final boolean newSkipSilence) {
+ this.skipSilence = newSkipSilence;
+ binding.skipSilenceCheckbox.setChecked(newSkipSilence);
+ }
+
+ @SuppressWarnings("SameParameterValue") // this method was written to be reusable
+ private void bindCheckboxWithBoolPref(
+ @NonNull final CheckBox checkBox,
+ @StringRes final int resId,
+ final boolean defaultValue,
+ @NonNull final Consumer onInitialValueOrValueChange
+ ) {
+ final boolean prefValue = PreferenceManager
.getDefaultSharedPreferences(requireContext())
- .getFloat(getString(R.string.adjustment_step_key), (float) DEFAULT_STEP));
+ .getBoolean(getString(resId), defaultValue);
- final TextView stepSizeOnePercentText = rootView.findViewById(R.id.stepSizeOnePercent);
- final TextView stepSizeFivePercentText = rootView.findViewById(R.id.stepSizeFivePercent);
- final TextView stepSizeTenPercentText = rootView.findViewById(R.id.stepSizeTenPercent);
- final TextView stepSizeTwentyFivePercentText = rootView
- .findViewById(R.id.stepSizeTwentyFivePercent);
- final TextView stepSizeOneHundredPercentText = rootView
- .findViewById(R.id.stepSizeOneHundredPercent);
+ checkBox.setChecked(prefValue);
- if (stepSizeOnePercentText != null) {
- stepSizeOnePercentText.setText(getPercentString(STEP_ONE_PERCENT_VALUE));
- stepSizeOnePercentText
- .setOnClickListener(view -> setStepSize(STEP_ONE_PERCENT_VALUE));
- }
+ onInitialValueOrValueChange.accept(prefValue);
- if (stepSizeFivePercentText != null) {
- stepSizeFivePercentText.setText(getPercentString(STEP_FIVE_PERCENT_VALUE));
- stepSizeFivePercentText
- .setOnClickListener(view -> setStepSize(STEP_FIVE_PERCENT_VALUE));
- }
+ checkBox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
+ // save whether pitch and tempo are unhooked or not
+ PreferenceManager.getDefaultSharedPreferences(requireContext())
+ .edit()
+ .putBoolean(getString(resId), isChecked)
+ .apply();
- if (stepSizeTenPercentText != null) {
- stepSizeTenPercentText.setText(getPercentString(STEP_TEN_PERCENT_VALUE));
- stepSizeTenPercentText
- .setOnClickListener(view -> setStepSize(STEP_TEN_PERCENT_VALUE));
- }
-
- if (stepSizeTwentyFivePercentText != null) {
- stepSizeTwentyFivePercentText
- .setText(getPercentString(STEP_TWENTY_FIVE_PERCENT_VALUE));
- stepSizeTwentyFivePercentText
- .setOnClickListener(view -> setStepSize(STEP_TWENTY_FIVE_PERCENT_VALUE));
- }
-
- if (stepSizeOneHundredPercentText != null) {
- stepSizeOneHundredPercentText
- .setText(getPercentString(STEP_ONE_HUNDRED_PERCENT_VALUE));
- stepSizeOneHundredPercentText
- .setOnClickListener(view -> setStepSize(STEP_ONE_HUNDRED_PERCENT_VALUE));
- }
+ onInitialValueOrValueChange.accept(isChecked);
+ });
}
- private void setupTempoStepSizeSelector(@NonNull final View rootView) {
- final TextView playbackStepTypeText = rootView.findViewById(R.id.playback_step_type);
- if (playbackStepTypeText != null) {
- playbackStepTypeText.setText(R.string.playback_tempo_step);
- }
- setupStepSizeSelector(rootView);
- }
-
- private void setupCombinedStepSizeSelector(@NonNull final View rootView) {
- final TextView playbackStepTypeText = rootView.findViewById(R.id.playback_step_type);
- if (playbackStepTypeText != null) {
- playbackStepTypeText.setText(R.string.playback_step);
- }
- setupStepSizeSelector(rootView);
- }
-
- private void setStepSize(final double stepSize) {
- PreferenceManager.getDefaultSharedPreferences(requireContext())
- .edit()
- .putFloat(getString(R.string.adjustment_step_key), (float) stepSize)
- .apply();
-
- if (tempoStepUpText != null) {
- tempoStepUpText.setText(getStepUpPercentString(stepSize));
- tempoStepUpText.setOnClickListener(view -> {
- onTempoSliderUpdated(getCurrentTempo() + stepSize);
- setCurrentPlaybackParameters();
- });
- }
-
- if (tempoStepDownText != null) {
- tempoStepDownText.setText(getStepDownPercentString(stepSize));
- tempoStepDownText.setOnClickListener(view -> {
- onTempoSliderUpdated(getCurrentTempo() - stepSize);
- setCurrentPlaybackParameters();
- });
- }
-
- if (pitchStepUpText != null) {
- pitchStepUpText.setText(getStepUpPercentString(stepSize));
- pitchStepUpText.setOnClickListener(view -> {
- onPitchSliderUpdated(getCurrentPitch() + stepSize);
- setCurrentPlaybackParameters();
- });
- }
-
- if (pitchStepDownText != null) {
- pitchStepDownText.setText(getStepDownPercentString(stepSize));
- pitchStepDownText.setOnClickListener(view -> {
- onPitchSliderUpdated(getCurrentPitch() - stepSize);
- setCurrentPlaybackParameters();
- });
- }
-
- if (semitoneStepDownText != null) {
- semitoneStepDownText.setOnClickListener(view -> {
- onSemitoneSliderUpdated(getCurrentSemitones() - 1);
- setCurrentPlaybackParameters();
- });
- }
-
- if (semitoneStepUpText != null) {
- semitoneStepUpText.setOnClickListener(view -> {
- onSemitoneSliderUpdated(getCurrentSemitones() + 1);
- setCurrentPlaybackParameters();
- });
+ /**
+ * Ensures that the slider hook is valid and if not sets and updates the sliders accordingly.
+ *
+ * You have to ensure by yourself that the hooking is active.
+ */
+ private void ensureHookIsValidAndUpdateCallBack() {
+ if (tempo != pitchPercent) {
+ setSliders(Math.min(tempo, pitchPercent));
+ updateCallback();
}
}
@@ -496,166 +493,106 @@ public class PlaybackParameterDialog extends DialogFragment {
// Sliders
//////////////////////////////////////////////////////////////////////////*/
- private SimpleOnSeekBarChangeListener getOnTempoChangedListener() {
+ private SeekBar.OnSeekBarChangeListener getTempoOrPitchSeekbarChangeListener(
+ final SliderStrategy sliderStrategy,
+ final DoubleConsumer newValueConsumer
+ ) {
return new SimpleOnSeekBarChangeListener() {
@Override
- public void onProgressChanged(@NonNull final SeekBar seekBar, final int progress,
+ public void onProgressChanged(@NonNull final SeekBar seekBar,
+ final int progress,
final boolean fromUser) {
- final double currentTempo = strategy.valueOf(progress);
- if (fromUser) {
- onTempoSliderUpdated(currentTempo);
- setCurrentPlaybackParameters();
- }
- }
- };
- }
-
- private SimpleOnSeekBarChangeListener getOnPitchChangedListener() {
- return new SimpleOnSeekBarChangeListener() {
- @Override
- public void onProgressChanged(@NonNull final SeekBar seekBar, final int progress,
- final boolean fromUser) {
- final double currentPitch = strategy.valueOf(progress);
- if (fromUser) { // this change is first in chain
- onPitchSliderUpdated(currentPitch);
- setCurrentPlaybackParameters();
- }
- }
- };
- }
-
- private SimpleOnSeekBarChangeListener getOnSemitoneChangedListener() {
- return new SimpleOnSeekBarChangeListener() {
- @Override
- public void onProgressChanged(@NonNull final SeekBar seekBar, final int progress,
- final boolean fromUser) {
- // semitone slider supplies values 0 to 24, subtraction by 12 is required
- final int currentSemitones = progress - 12;
- if (fromUser) { // this change is first in chain
- onSemitoneSliderUpdated(currentSemitones);
- // line below also saves semitones as pitch percentages
- onPitchSliderUpdated(semitonesToPercent(currentSemitones));
- setCurrentPlaybackParameters();
+ if (fromUser) { // ensure that the user triggered the change
+ newValueConsumer.accept(sliderStrategy.valueOf(progress));
+ updateCallback();
}
}
};
}
private void onTempoSliderUpdated(final double newTempo) {
- if (!unhookingCheckbox.isChecked()) {
+ if (!binding.unhookCheckbox.isChecked()) {
setSliders(newTempo);
} else {
- setTempoSlider(newTempo);
+ setAndUpdateTempo(newTempo);
}
}
- private void onPitchSliderUpdated(final double newPitch) {
- if (!unhookingCheckbox.isChecked()) {
+ private void onPitchPercentSliderUpdated(final double newPitch) {
+ if (!binding.unhookCheckbox.isChecked()) {
setSliders(newPitch);
} else {
- setPitchSlider(newPitch);
+ setAndUpdatePitch(newPitch);
}
}
- private void onSemitoneSliderUpdated(final int newSemitone) {
- setSemitoneSlider(newSemitone);
- }
-
private void setSliders(final double newValue) {
- setTempoSlider(newValue);
- setPitchSlider(newValue);
+ setAndUpdateTempo(newValue);
+ setAndUpdatePitch(newValue);
}
- private void setTempoSlider(final double newTempo) {
- if (tempoSlider == null) {
- return;
- }
- tempoSlider.setProgress(strategy.progressOf(newTempo));
+ private void setAndUpdateTempo(final double newTempo) {
+ this.tempo = calcValidTempo(newTempo);
+
+ binding.tempoSeekbar.setProgress(QUADRATIC_STRATEGY.progressOf(tempo));
+ setText(binding.tempoCurrentText, PlayerHelper::formatSpeed, tempo);
}
- private void setPitchSlider(final double newPitch) {
- if (pitchSlider == null) {
- return;
- }
- pitchSlider.setProgress(strategy.progressOf(newPitch));
+ private void setAndUpdatePitch(final double newPitch) {
+ this.pitchPercent = calcValidPitch(newPitch);
+
+ binding.pitchPercentSeekbar.setProgress(QUADRATIC_STRATEGY.progressOf(pitchPercent));
+ binding.pitchSemitoneSeekbar.setProgress(SEMITONE_STRATEGY.progressOf(pitchPercent));
+ setText(binding.pitchPercentCurrentText,
+ PlayerHelper::formatPitch,
+ pitchPercent);
+ setText(binding.pitchSemitoneCurrentText,
+ PlayerSemitoneHelper::formatPitchSemitones,
+ pitchPercent);
}
- private void setSemitoneSlider(final int newSemitone) {
- if (semitoneSlider == null) {
- return;
+ private double calcValidTempo(final double newTempo) {
+ return Math.max(MIN_PITCH_OR_SPEED, Math.min(MAX_PITCH_OR_SPEED, newTempo));
+ }
+
+ private double calcValidPitch(final double newPitch) {
+ final double calcPitch =
+ Math.max(MIN_PITCH_OR_SPEED, Math.min(MAX_PITCH_OR_SPEED, newPitch));
+
+ if (!isCurrentPitchControlModeSemitone()) {
+ return calcPitch;
}
- semitoneSlider.setProgress(newSemitone + 12);
+
+ return PlayerSemitoneHelper.semitonesToPercent(
+ PlayerSemitoneHelper.percentToSemitones(calcPitch));
}
/*//////////////////////////////////////////////////////////////////////////
// Helper
//////////////////////////////////////////////////////////////////////////*/
- private void setCurrentPlaybackParameters() {
- if (getCurrentAdjustBySemitones()) {
- setPlaybackParameters(
- getCurrentTempo(),
- semitonesToPercent(getCurrentSemitones()),
- getCurrentSemitones(),
- getCurrentSkipSilence()
- );
- } else {
- setPlaybackParameters(
- getCurrentTempo(),
- getCurrentPitch(),
- percentToSemitones(getCurrentPitch()),
- getCurrentSkipSilence()
+ private void updateCallback() {
+ if (callback == null) {
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Updating callback: "
+ + "tempo = " + tempo + ", "
+ + "pitchPercent = " + pitchPercent + ", "
+ + "skipSilence = " + skipSilence
);
}
- }
-
- private void setPlaybackParameters(final double newTempo, final double newPitch,
- final int newSemitones, final boolean skipSilence) {
- if (callback != null && tempoCurrentText != null
- && pitchCurrentText != null && semitoneCurrentText != null) {
- if (DEBUG) {
- Log.d(TAG, "Setting playback parameters to "
- + "tempo=[" + newTempo + "], "
- + "pitch=[" + newPitch + "], "
- + "semitones=[" + newSemitones + "]");
- }
-
- tempoCurrentText.setText(PlayerHelper.formatSpeed(newTempo));
- pitchCurrentText.setText(PlayerHelper.formatPitch(newPitch));
- semitoneCurrentText.setText(getSignedSemitonesString(newSemitones));
- callback.onPlaybackParameterChanged((float) newTempo, (float) newPitch, skipSilence);
- }
- }
-
- private double getCurrentTempo() {
- return tempoSlider == null ? tempo : strategy.valueOf(tempoSlider.getProgress());
- }
-
- private double getCurrentPitch() {
- return pitchSlider == null ? pitch : strategy.valueOf(pitchSlider.getProgress());
- }
-
- private int getCurrentSemitones() {
- // semitoneSlider is absolute, that's why - 12
- return semitoneSlider == null ? semitones : semitoneSlider.getProgress() - 12;
- }
-
- private boolean getCurrentSkipSilence() {
- return skipSilenceCheckbox != null && skipSilenceCheckbox.isChecked();
- }
-
- private boolean getCurrentAdjustBySemitones() {
- return adjustBySemitonesCheckbox != null && adjustBySemitonesCheckbox.isChecked();
+ callback.onPlaybackParameterChanged((float) tempo, (float) pitchPercent, skipSilence);
}
@NonNull
private static String getStepUpPercentString(final double percent) {
- return STEP_UP_SIGN + getPercentString(percent);
+ return '+' + getPercentString(percent);
}
@NonNull
private static String getStepDownPercentString(final double percent) {
- return STEP_DOWN_SIGN + getPercentString(percent);
+ return '-' + getPercentString(percent);
}
@NonNull
@@ -663,21 +600,8 @@ public class PlaybackParameterDialog extends DialogFragment {
return PlayerHelper.formatPitch(percent);
}
- @NonNull
- private static String getSignedSemitonesString(final int semitones) {
- return semitones > 0 ? "+" + semitones : "" + semitones;
- }
-
public interface Callback {
void onPlaybackParameterChanged(float playbackTempo, float playbackPitch,
boolean playbackSkipSilence);
}
-
- public double semitonesToPercent(final int inSemitones) {
- return Math.pow(2, inSemitones / 12.0);
- }
-
- public int percentToSemitones(final double inPercent) {
- return (int) Math.round(12 * Math.log(inPercent) / Math.log(2));
- }
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java
index 405f6fd37..88f25e194 100644
--- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java
+++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java
@@ -1,7 +1,13 @@
package org.schabi.newpipe.player.helper;
-import android.content.Context;
+import static org.schabi.newpipe.MainActivity.DEBUG;
+import android.content.Context;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.google.android.exoplayer2.database.StandaloneDatabaseProvider;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.source.SingleSampleMediaSource;
import com.google.android.exoplayer2.source.dash.DashMediaSource;
@@ -13,12 +19,21 @@ import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSource;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
-import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.TransferListener;
+import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor;
+import com.google.android.exoplayer2.upstream.cache.SimpleCache;
-import androidx.annotation.NonNull;
+import org.schabi.newpipe.DownloaderImpl;
+import org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeOtfDashManifestCreator;
+import org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubePostLiveStreamDvrDashManifestCreator;
+import org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeProgressiveDashManifestCreator;
+import org.schabi.newpipe.player.datasource.NonUriHlsDataSourceFactory;
+import org.schabi.newpipe.player.datasource.YoutubeHttpDataSource;
+
+import java.io.File;
public class PlayerDataSource {
+ public static final String TAG = PlayerDataSource.class.getSimpleName();
public static final int LIVE_STREAM_EDGE_GAP_MILLIS = 10000;
@@ -29,79 +44,174 @@ public class PlayerDataSource {
* early.
*/
private static final double PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT = 15;
- private static final int MANIFEST_MINIMUM_RETRY = 5;
- private static final int EXTRACTOR_MINIMUM_RETRY = Integer.MAX_VALUE;
- private final int continueLoadingCheckIntervalBytes;
- private final DataSource.Factory cacheDataSourceFactory;
+ /**
+ * The maximum number of generated manifests per cache, in
+ * {@link YoutubeProgressiveDashManifestCreator}, {@link YoutubeOtfDashManifestCreator} and
+ * {@link YoutubePostLiveStreamDvrDashManifestCreator}.
+ */
+ private static final int MAX_MANIFEST_CACHE_SIZE = 500;
+
+ /**
+ * The folder name in which the ExoPlayer cache will be written.
+ */
+ private static final String CACHE_FOLDER_NAME = "exoplayer";
+
+ /**
+ * The {@link SimpleCache} instance which will be used to build
+ * {@link com.google.android.exoplayer2.upstream.cache.CacheDataSource}s instances (with
+ * {@link CacheFactory}).
+ */
+ private static SimpleCache cache;
+
+
+ private final int progressiveLoadIntervalBytes;
+
+ // Generic Data Source Factories (without or with cache)
private final DataSource.Factory cachelessDataSourceFactory;
+ private final CacheFactory cacheDataSourceFactory;
- public PlayerDataSource(@NonNull final Context context,
- @NonNull final String userAgent,
- @NonNull final TransferListener transferListener) {
- continueLoadingCheckIntervalBytes = PlayerHelper.getProgressiveLoadIntervalBytes(context);
- cacheDataSourceFactory = new CacheFactory(context, userAgent, transferListener);
- cachelessDataSourceFactory = new DefaultDataSource
- .Factory(context, new DefaultHttpDataSource.Factory().setUserAgent(userAgent))
+ // YouTube-specific Data Source Factories (with cache)
+ // They use YoutubeHttpDataSource.Factory, with different parameters each
+ private final CacheFactory ytHlsCacheDataSourceFactory;
+ private final CacheFactory ytDashCacheDataSourceFactory;
+ private final CacheFactory ytProgressiveDashCacheDataSourceFactory;
+
+
+ public PlayerDataSource(final Context context,
+ final TransferListener transferListener) {
+
+ progressiveLoadIntervalBytes = PlayerHelper.getProgressiveLoadIntervalBytes(context);
+
+ // make sure the static cache was created: needed by CacheFactories below
+ instantiateCacheIfNeeded(context);
+
+ // generic data source factories use DefaultHttpDataSource.Factory
+ cachelessDataSourceFactory = new DefaultDataSource.Factory(context,
+ new DefaultHttpDataSource.Factory().setUserAgent(DownloaderImpl.USER_AGENT))
.setTransferListener(transferListener);
+ cacheDataSourceFactory = new CacheFactory(context, transferListener, cache,
+ new DefaultHttpDataSource.Factory().setUserAgent(DownloaderImpl.USER_AGENT));
+
+ // YouTube-specific data source factories use getYoutubeHttpDataSourceFactory()
+ ytHlsCacheDataSourceFactory = new CacheFactory(context, transferListener, cache,
+ getYoutubeHttpDataSourceFactory(false, false));
+ ytDashCacheDataSourceFactory = new CacheFactory(context, transferListener, cache,
+ getYoutubeHttpDataSourceFactory(true, true));
+ ytProgressiveDashCacheDataSourceFactory = new CacheFactory(context, transferListener, cache,
+ getYoutubeHttpDataSourceFactory(false, true));
+
+ // set the maximum size to manifest creators
+ YoutubeProgressiveDashManifestCreator.getCache().setMaximumSize(MAX_MANIFEST_CACHE_SIZE);
+ YoutubeOtfDashManifestCreator.getCache().setMaximumSize(MAX_MANIFEST_CACHE_SIZE);
+ YoutubePostLiveStreamDvrDashManifestCreator.getCache().setMaximumSize(
+ MAX_MANIFEST_CACHE_SIZE);
}
+
+ //region Live media source factories
public SsMediaSource.Factory getLiveSsMediaSourceFactory() {
- return new SsMediaSource.Factory(
- new DefaultSsChunkSource.Factory(cachelessDataSourceFactory),
- cachelessDataSourceFactory
- )
- .setLoadErrorHandlingPolicy(
- new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY))
- .setLivePresentationDelayMs(LIVE_STREAM_EDGE_GAP_MILLIS);
+ return getSSMediaSourceFactory().setLivePresentationDelayMs(LIVE_STREAM_EDGE_GAP_MILLIS);
}
public HlsMediaSource.Factory getLiveHlsMediaSourceFactory() {
return new HlsMediaSource.Factory(cachelessDataSourceFactory)
.setAllowChunklessPreparation(true)
- .setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(
- MANIFEST_MINIMUM_RETRY))
.setPlaylistTrackerFactory((dataSourceFactory, loadErrorHandlingPolicy,
playlistParserFactory) ->
new DefaultHlsPlaylistTracker(dataSourceFactory, loadErrorHandlingPolicy,
- playlistParserFactory, PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT)
- );
+ playlistParserFactory,
+ PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT));
}
public DashMediaSource.Factory getLiveDashMediaSourceFactory() {
return new DashMediaSource.Factory(
getDefaultDashChunkSourceFactory(cachelessDataSourceFactory),
- cachelessDataSourceFactory
- )
- .setLoadErrorHandlingPolicy(
- new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY));
+ cachelessDataSourceFactory);
}
+ //endregion
- private DefaultDashChunkSource.Factory getDefaultDashChunkSourceFactory(
- final DataSource.Factory dataSourceFactory
- ) {
- return new DefaultDashChunkSource.Factory(dataSourceFactory);
- }
- public HlsMediaSource.Factory getHlsMediaSourceFactory() {
+ //region Generic media source factories
+ public HlsMediaSource.Factory getHlsMediaSourceFactory(
+ @Nullable final NonUriHlsDataSourceFactory.Builder hlsDataSourceFactoryBuilder) {
+ if (hlsDataSourceFactoryBuilder != null) {
+ hlsDataSourceFactoryBuilder.setDataSourceFactory(cacheDataSourceFactory);
+ return new HlsMediaSource.Factory(hlsDataSourceFactoryBuilder.build());
+ }
+
return new HlsMediaSource.Factory(cacheDataSourceFactory);
}
public DashMediaSource.Factory getDashMediaSourceFactory() {
return new DashMediaSource.Factory(
getDefaultDashChunkSourceFactory(cacheDataSourceFactory),
- cacheDataSourceFactory
- );
+ cacheDataSourceFactory);
}
- public ProgressiveMediaSource.Factory getExtractorMediaSourceFactory() {
+ public ProgressiveMediaSource.Factory getProgressiveMediaSourceFactory() {
return new ProgressiveMediaSource.Factory(cacheDataSourceFactory)
- .setContinueLoadingCheckIntervalBytes(continueLoadingCheckIntervalBytes)
- .setLoadErrorHandlingPolicy(
- new DefaultLoadErrorHandlingPolicy(EXTRACTOR_MINIMUM_RETRY));
+ .setContinueLoadingCheckIntervalBytes(progressiveLoadIntervalBytes);
}
- public SingleSampleMediaSource.Factory getSampleMediaSourceFactory() {
+ public SsMediaSource.Factory getSSMediaSourceFactory() {
+ return new SsMediaSource.Factory(
+ new DefaultSsChunkSource.Factory(cachelessDataSourceFactory),
+ cachelessDataSourceFactory);
+ }
+
+ public SingleSampleMediaSource.Factory getSingleSampleMediaSourceFactory() {
return new SingleSampleMediaSource.Factory(cacheDataSourceFactory);
}
+ //endregion
+
+
+ //region YouTube media source factories
+ public HlsMediaSource.Factory getYoutubeHlsMediaSourceFactory() {
+ return new HlsMediaSource.Factory(ytHlsCacheDataSourceFactory);
+ }
+
+ public DashMediaSource.Factory getYoutubeDashMediaSourceFactory() {
+ return new DashMediaSource.Factory(
+ getDefaultDashChunkSourceFactory(ytDashCacheDataSourceFactory),
+ ytDashCacheDataSourceFactory);
+ }
+
+ public ProgressiveMediaSource.Factory getYoutubeProgressiveMediaSourceFactory() {
+ return new ProgressiveMediaSource.Factory(ytProgressiveDashCacheDataSourceFactory)
+ .setContinueLoadingCheckIntervalBytes(progressiveLoadIntervalBytes);
+ }
+ //endregion
+
+
+ //region Static methods
+ private static DefaultDashChunkSource.Factory getDefaultDashChunkSourceFactory(
+ final DataSource.Factory dataSourceFactory) {
+ return new DefaultDashChunkSource.Factory(dataSourceFactory);
+ }
+
+ private static YoutubeHttpDataSource.Factory getYoutubeHttpDataSourceFactory(
+ final boolean rangeParameterEnabled,
+ final boolean rnParameterEnabled) {
+ return new YoutubeHttpDataSource.Factory()
+ .setRangeParameterEnabled(rangeParameterEnabled)
+ .setRnParameterEnabled(rnParameterEnabled);
+ }
+
+ private static void instantiateCacheIfNeeded(final Context context) {
+ if (cache == null) {
+ final File cacheDir = new File(context.getExternalCacheDir(), CACHE_FOLDER_NAME);
+ if (DEBUG) {
+ Log.d(TAG, "instantiateCacheIfNeeded: cacheDir = " + cacheDir.getAbsolutePath());
+ }
+ if (!cacheDir.exists() && !cacheDir.mkdir()) {
+ Log.w(TAG, "instantiateCacheIfNeeded: could not create cache dir");
+ }
+
+ final LeastRecentlyUsedCacheEvictor evictor
+ = new LeastRecentlyUsedCacheEvictor(PlayerHelper.getPreferredCacheSize());
+ cache = new SimpleCache(cacheDir, evictor, new StandaloneDatabaseProvider(context));
+ }
+ }
+ //endregion
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java
index b73c6cf7f..2131861bf 100644
--- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java
@@ -45,11 +45,9 @@ import com.google.android.exoplayer2.util.MimeTypes;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.MediaFormat;
-import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
-import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.extractor.utils.Utils;
import org.schabi.newpipe.player.MainPlayer;
import org.schabi.newpipe.player.Player;
@@ -110,12 +108,14 @@ public final class PlayerHelper {
int MINIMIZE_ON_EXIT_MODE_POPUP = 2;
}
- private PlayerHelper() { }
+ private PlayerHelper() {
+ }
////////////////////////////////////////////////////////////////////////////
// Exposed helpers
////////////////////////////////////////////////////////////////////////////
+ @NonNull
public static String getTimeString(final int milliSeconds) {
final int seconds = (milliSeconds % 60000) / 1000;
final int minutes = (milliSeconds % 3600000) / 60000;
@@ -131,15 +131,18 @@ public final class PlayerHelper {
).toString();
}
+ @NonNull
public static String formatSpeed(final double speed) {
return SPEED_FORMATTER.format(speed);
}
+ @NonNull
public static String formatPitch(final double pitch) {
return PITCH_FORMATTER.format(pitch);
}
- public static String subtitleMimeTypesOf(final MediaFormat format) {
+ @NonNull
+ public static String subtitleMimeTypesOf(@NonNull final MediaFormat format) {
switch (format) {
case VTT:
return MimeTypes.TEXT_VTT;
@@ -190,18 +193,6 @@ public final class PlayerHelper {
}
}
- @NonNull
- public static String cacheKeyOf(@NonNull final StreamInfo info,
- @NonNull final VideoStream video) {
- return info.getUrl() + video.getResolution() + video.getFormat().getName();
- }
-
- @NonNull
- public static String cacheKeyOf(@NonNull final StreamInfo info,
- @NonNull final AudioStream audio) {
- return info.getUrl() + audio.getAverageBitrate() + audio.getFormat().getName();
- }
-
/**
* Given a {@link StreamInfo} and the existing queue items,
* provide the {@link SinglePlayQueue} consisting of the next video for auto queueing.
@@ -233,7 +224,7 @@ public final class PlayerHelper {
return null;
}
- if (relatedItems.get(0) != null && relatedItems.get(0) instanceof StreamInfoItem
+ if (relatedItems.get(0) instanceof StreamInfoItem
&& !urls.contains(relatedItems.get(0).getUrl())) {
return getAutoQueuedSinglePlayQueue((StreamInfoItem) relatedItems.get(0));
}
@@ -335,6 +326,7 @@ public final class PlayerHelper {
return 2 * 1024 * 1024L; // ExoPlayer CacheDataSink.MIN_RECOMMENDED_FRAGMENT_SIZE
}
+ @NonNull
public static ExoTrackSelection.Factory getQualitySelector() {
return new AdaptiveTrackSelection.Factory(
1000,
@@ -389,7 +381,7 @@ public final class PlayerHelper {
/**
* @param context the Android context
* @return the screen brightness to use. A value less than 0 (the default) means to use the
- * preferred screen brightness
+ * preferred screen brightness
*/
public static float getScreenBrightness(@NonNull final Context context) {
final SharedPreferences sp = getPreferences(context);
@@ -480,7 +472,8 @@ public final class PlayerHelper {
return REPEAT_MODE_ONE;
case REPEAT_MODE_ONE:
return REPEAT_MODE_ALL;
- case REPEAT_MODE_ALL: default:
+ case REPEAT_MODE_ALL:
+ default:
return REPEAT_MODE_OFF;
}
}
@@ -548,7 +541,7 @@ public final class PlayerHelper {
player.getContext().getResources().getDimension(R.dimen.popup_default_width);
final float popupWidth = popupRememberSizeAndPos
? player.getPrefs().getFloat(player.getContext().getString(
- R.string.popup_saved_width_key), defaultSize)
+ R.string.popup_saved_width_key), defaultSize)
: defaultSize;
final float popupHeight = getMinimumVideoHeight(popupWidth);
@@ -564,10 +557,10 @@ public final class PlayerHelper {
final int centerY = (int) (player.getScreenHeight() / 2f - popupHeight / 2f);
popupLayoutParams.x = popupRememberSizeAndPos
? player.getPrefs().getInt(player.getContext().getString(
- R.string.popup_saved_x_key), centerX) : centerX;
+ R.string.popup_saved_x_key), centerX) : centerX;
popupLayoutParams.y = popupRememberSizeAndPos
? player.getPrefs().getInt(player.getContext().getString(
- R.string.popup_saved_y_key), centerY) : centerY;
+ R.string.popup_saved_y_key), centerY) : centerY;
return popupLayoutParams;
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java
new file mode 100644
index 000000000..f3a71d7cd
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java
@@ -0,0 +1,38 @@
+package org.schabi.newpipe.player.helper;
+
+/**
+ * Converts between percent and 12-tone equal temperament semitones.
+ *
+ * @see
+ *
+ * Wikipedia: Equal temperament#Twelve-tone equal temperament
+ *
+ */
+public final class PlayerSemitoneHelper {
+ public static final int SEMITONE_COUNT = 12;
+
+ private PlayerSemitoneHelper() {
+ // No impl
+ }
+
+ public static String formatPitchSemitones(final double percent) {
+ return formatPitchSemitones(percentToSemitones(percent));
+ }
+
+ public static String formatPitchSemitones(final int semitones) {
+ return semitones > 0 ? "+" + semitones : "" + semitones;
+ }
+
+ public static double semitonesToPercent(final int semitones) {
+ return Math.pow(2, ensureSemitonesInRange(semitones) / (double) SEMITONE_COUNT);
+ }
+
+ public static int percentToSemitones(final double percent) {
+ return ensureSemitonesInRange(
+ (int) Math.round(SEMITONE_COUNT * Math.log(percent) / Math.log(2)));
+ }
+
+ private static int ensureSemitonesInRange(final int semitones) {
+ return Math.max(-SEMITONE_COUNT, Math.min(SEMITONE_COUNT, semitones));
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/player/listeners/view/QualityClickListener.kt b/app/src/main/java/org/schabi/newpipe/player/listeners/view/QualityClickListener.kt
index b103ac0e6..43e8288e6 100644
--- a/app/src/main/java/org/schabi/newpipe/player/listeners/view/QualityClickListener.kt
+++ b/app/src/main/java/org/schabi/newpipe/player/listeners/view/QualityClickListener.kt
@@ -32,7 +32,7 @@ class QualityClickListener(
val videoStream = player.selectedVideoStream
if (videoStream != null) {
player.binding.qualityTextView.text =
- MediaFormat.getNameById(videoStream.formatId) + " " + videoStream.resolution
+ MediaFormat.getNameById(videoStream.formatId) + " " + videoStream.getResolution()
}
player.saveWasPlaying()
diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java
index f2e98d866..e7aeb9638 100644
--- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java
+++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java
@@ -5,9 +5,9 @@ import android.text.TextUtils;
import android.view.MotionEvent;
import android.view.View;
-import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.PicassoHelper;
+import org.schabi.newpipe.util.ServiceHelper;
public class PlayQueueItemBuilder {
private static final String TAG = PlayQueueItemBuilder.class.toString();
@@ -25,7 +25,7 @@ public class PlayQueueItemBuilder {
holder.itemVideoTitleView.setText(item.getTitle());
}
holder.itemAdditionalDetailsView.setText(Localization.concatenateStrings(item.getUploader(),
- NewPipe.getNameOfService(item.getServiceId())));
+ ServiceHelper.getNameOfServiceById(item.getServiceId())));
if (item.getDuration() > 0) {
holder.itemDurationView.setText(Localization.getDurationString(item.getDuration()));
diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java b/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java
index 9bded9331..934beba19 100644
--- a/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java
+++ b/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java
@@ -1,22 +1,27 @@
package org.schabi.newpipe.player.resolver;
+import static org.schabi.newpipe.util.ListHelper.getNonTorrentStreams;
+
import android.content.Context;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.source.MediaSource;
-import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.player.helper.PlayerDataSource;
-import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.mediaitem.MediaItemTag;
import org.schabi.newpipe.player.mediaitem.StreamInfoTag;
import org.schabi.newpipe.util.ListHelper;
+import java.util.List;
+
public class AudioPlaybackResolver implements PlaybackResolver {
+ private static final String TAG = AudioPlaybackResolver.class.getSimpleName();
+
@NonNull
private final Context context;
@NonNull
@@ -31,19 +36,27 @@ public class AudioPlaybackResolver implements PlaybackResolver {
@Override
@Nullable
public MediaSource resolve(@NonNull final StreamInfo info) {
- final MediaSource liveSource = maybeBuildLiveMediaSource(dataSource, info);
+ final MediaSource liveSource = PlaybackResolver.maybeBuildLiveMediaSource(dataSource, info);
if (liveSource != null) {
return liveSource;
}
- final int index = ListHelper.getDefaultAudioFormat(context, info.getAudioStreams());
+ final List audioStreams = getNonTorrentStreams(info.getAudioStreams());
+
+ final int index = ListHelper.getDefaultAudioFormat(context, audioStreams);
if (index < 0 || index >= info.getAudioStreams().size()) {
return null;
}
final AudioStream audio = info.getAudioStreams().get(index);
final MediaItemTag tag = StreamInfoTag.of(info);
- return buildMediaSource(dataSource, audio.getUrl(), PlayerHelper.cacheKeyOf(info, audio),
- MediaFormat.getSuffixById(audio.getFormatId()), tag);
+
+ try {
+ return PlaybackResolver.buildMediaSource(
+ dataSource, audio, info, PlaybackResolver.cacheKeyOf(info, audio), tag);
+ } catch (final ResolverException e) {
+ Log.e(TAG, "Unable to create audio source", e);
+ return null;
+ }
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/PlaybackResolver.java b/app/src/main/java/org/schabi/newpipe/player/resolver/PlaybackResolver.java
index 90b38ed51..34e7e9bd1 100644
--- a/app/src/main/java/org/schabi/newpipe/player/resolver/PlaybackResolver.java
+++ b/app/src/main/java/org/schabi/newpipe/player/resolver/PlaybackResolver.java
@@ -1,50 +1,193 @@
package org.schabi.newpipe.player.resolver;
+import static org.schabi.newpipe.extractor.stream.AudioStream.UNKNOWN_BITRATE;
+import static org.schabi.newpipe.extractor.stream.VideoStream.RESOLUTION_UNKNOWN;
+import static org.schabi.newpipe.player.helper.PlayerDataSource.LIVE_STREAM_EDGE_GAP_MILLIS;
+
import android.net.Uri;
-import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.source.MediaSource;
-import com.google.android.exoplayer2.util.Util;
+import com.google.android.exoplayer2.source.ProgressiveMediaSource;
+import com.google.android.exoplayer2.source.dash.DashMediaSource;
+import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
+import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser;
+import com.google.android.exoplayer2.source.hls.HlsMediaSource;
+import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
+import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
+import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser;
+import org.schabi.newpipe.extractor.MediaFormat;
+import org.schabi.newpipe.extractor.ServiceList;
+import org.schabi.newpipe.extractor.services.youtube.ItagItem;
+import org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.CreationException;
+import org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeOtfDashManifestCreator;
+import org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubePostLiveStreamDvrDashManifestCreator;
+import org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeProgressiveDashManifestCreator;
+import org.schabi.newpipe.extractor.stream.AudioStream;
+import org.schabi.newpipe.extractor.stream.DeliveryMethod;
+import org.schabi.newpipe.extractor.stream.Stream;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamType;
+import org.schabi.newpipe.extractor.stream.VideoStream;
+import org.schabi.newpipe.player.datasource.NonUriHlsDataSourceFactory;
import org.schabi.newpipe.player.helper.PlayerDataSource;
import org.schabi.newpipe.player.mediaitem.MediaItemTag;
import org.schabi.newpipe.player.mediaitem.StreamInfoTag;
import org.schabi.newpipe.util.StreamTypeUtil;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import static org.schabi.newpipe.player.helper.PlayerDataSource.LIVE_STREAM_EDGE_GAP_MILLIS;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Objects;
+/**
+ * This interface is just a shorthand for {@link Resolver} with {@link StreamInfo} as source and
+ * {@link MediaSource} as product. It contains many static methods that can be used by classes
+ * implementing this interface, and nothing else.
+ */
public interface PlaybackResolver extends Resolver {
+ String TAG = PlaybackResolver.class.getSimpleName();
+
+ //region Cache key generation
+ private static StringBuilder commonCacheKeyOf(final StreamInfo info,
+ final Stream stream,
+ final boolean resolutionOrBitrateUnknown) {
+ // stream info service id
+ final StringBuilder cacheKey = new StringBuilder(info.getServiceId());
+
+ // stream info id
+ cacheKey.append(" ");
+ cacheKey.append(info.getId());
+
+ // stream id (even if unknown)
+ cacheKey.append(" ");
+ cacheKey.append(stream.getId());
+
+ // mediaFormat (if not null)
+ final MediaFormat mediaFormat = stream.getFormat();
+ if (mediaFormat != null) {
+ cacheKey.append(" ");
+ cacheKey.append(mediaFormat.getName());
+ }
+
+ // content (only if other information is missing)
+ // If the media format and the resolution/bitrate are both missing, then we don't have
+ // enough information to distinguish this stream from other streams.
+ // So, only in that case, we use the content (i.e. url or manifest) to differentiate
+ // between streams.
+ // Note that if the content were used even when other information is present, then two
+ // streams with the same stats but with different contents (e.g. because the url was
+ // refreshed) will be considered different (i.e. with a different cacheKey), making the
+ // cache useless.
+ if (resolutionOrBitrateUnknown && mediaFormat == null) {
+ cacheKey.append(" ");
+ cacheKey.append(Objects.hash(stream.getContent(), stream.getManifestUrl()));
+ }
+
+ return cacheKey;
+ }
+
+ /**
+ * Builds the cache key of a {@link VideoStream video stream}.
+ *
+ *
+ * A cache key is unique to the features of the provided video stream, and when possible
+ * independent of transient parameters (such as the URL of the stream).
+ * This ensures that there are no conflicts, but also that the cache is used as much as
+ * possible: the same cache should be used for two streams which have the same features but
+ * e.g. a different URL, since the URL might have been reloaded in the meantime, but the stream
+ * actually referenced by the URL is still the same.
+ *
+ *
+ * @param info the {@link StreamInfo stream info}, to distinguish between streams with
+ * the same features but coming from different stream infos
+ * @param videoStream the {@link VideoStream video stream} for which the cache key should be
+ * created
+ * @return a key to be used to store the cache of the provided {@link VideoStream video stream}
+ */
+ static String cacheKeyOf(final StreamInfo info, final VideoStream videoStream) {
+ final boolean resolutionUnknown = videoStream.getResolution().equals(RESOLUTION_UNKNOWN);
+ final StringBuilder cacheKey = commonCacheKeyOf(info, videoStream, resolutionUnknown);
+
+ // resolution (if known)
+ if (!resolutionUnknown) {
+ cacheKey.append(" ");
+ cacheKey.append(videoStream.getResolution());
+ }
+
+ // isVideoOnly
+ cacheKey.append(" ");
+ cacheKey.append(videoStream.isVideoOnly());
+
+ return cacheKey.toString();
+ }
+
+ /**
+ * Builds the cache key of an audio stream.
+ *
+ *
+ * A cache key is unique to the features of the provided {@link AudioStream audio stream}, and
+ * when possible independent of transient parameters (such as the URL of the stream).
+ * This ensures that there are no conflicts, but also that the cache is used as much as
+ * possible: the same cache should be used for two streams which have the same features but
+ * e.g. a different URL, since the URL might have been reloaded in the meantime, but the stream
+ * actually referenced by the URL is still the same.
+ *
+ *
+ * @param info the {@link StreamInfo stream info}, to distinguish between streams with
+ * the same features but coming from different stream infos
+ * @param audioStream the {@link AudioStream audio stream} for which the cache key should be
+ * created
+ * @return a key to be used to store the cache of the provided {@link AudioStream audio stream}
+ */
+ static String cacheKeyOf(final StreamInfo info, final AudioStream audioStream) {
+ final boolean averageBitrateUnknown = audioStream.getAverageBitrate() == UNKNOWN_BITRATE;
+ final StringBuilder cacheKey = commonCacheKeyOf(info, audioStream, averageBitrateUnknown);
+
+ // averageBitrate (if known)
+ if (!averageBitrateUnknown) {
+ cacheKey.append(" ");
+ cacheKey.append(audioStream.getAverageBitrate());
+ }
+
+ return cacheKey.toString();
+ }
+ //endregion
+
+
+ //region Live media sources
@Nullable
- default MediaSource maybeBuildLiveMediaSource(@NonNull final PlayerDataSource dataSource,
- @NonNull final StreamInfo info) {
- final StreamType streamType = info.getStreamType();
- if (!StreamTypeUtil.isLiveStream(streamType)) {
+ static MediaSource maybeBuildLiveMediaSource(final PlayerDataSource dataSource,
+ final StreamInfo info) {
+ if (!StreamTypeUtil.isLiveStream(info.getStreamType())) {
return null;
}
- final StreamInfoTag tag = StreamInfoTag.of(info);
- if (!info.getHlsUrl().isEmpty()) {
- return buildLiveMediaSource(dataSource, info.getHlsUrl(), C.TYPE_HLS, tag);
- } else if (!info.getDashMpdUrl().isEmpty()) {
- return buildLiveMediaSource(dataSource, info.getDashMpdUrl(), C.TYPE_DASH, tag);
+ try {
+ final StreamInfoTag tag = StreamInfoTag.of(info);
+ if (!info.getHlsUrl().isEmpty()) {
+ return buildLiveMediaSource(dataSource, info.getHlsUrl(), C.TYPE_HLS, tag);
+ } else if (!info.getDashMpdUrl().isEmpty()) {
+ return buildLiveMediaSource(dataSource, info.getDashMpdUrl(), C.TYPE_DASH, tag);
+ }
+ } catch (final Exception e) {
+ Log.w(TAG, "Error when generating live media source, falling back to standard sources",
+ e);
}
return null;
}
- @NonNull
- default MediaSource buildLiveMediaSource(@NonNull final PlayerDataSource dataSource,
- @NonNull final String sourceUrl,
- @C.ContentType final int type,
- @NonNull final MediaItemTag metadata) {
+ static MediaSource buildLiveMediaSource(final PlayerDataSource dataSource,
+ final String sourceUrl,
+ @C.ContentType final int type,
+ final MediaItemTag metadata) throws ResolverException {
final MediaSource.Factory factory;
switch (type) {
case C.TYPE_SS:
@@ -56,8 +199,10 @@ public interface PlaybackResolver extends Resolver {
case C.TYPE_HLS:
factory = dataSource.getLiveHlsMediaSourceFactory();
break;
+ case C.TYPE_OTHER:
+ case C.TYPE_RTSP:
default:
- throw new IllegalStateException("Unsupported type: " + type);
+ throw new ResolverException("Unsupported type: " + type);
}
return factory.createMediaSource(
@@ -67,46 +212,317 @@ public interface PlaybackResolver extends Resolver {
.setLiveConfiguration(
new MediaItem.LiveConfiguration.Builder()
.setTargetOffsetMs(LIVE_STREAM_EDGE_GAP_MILLIS)
- .build()
- )
- .build()
- );
+ .build())
+ .build());
}
+ //endregion
- @NonNull
- default MediaSource buildMediaSource(@NonNull final PlayerDataSource dataSource,
- @NonNull final String sourceUrl,
- @NonNull final String cacheKey,
- @NonNull final String overrideExtension,
- @NonNull final MediaItemTag metadata) {
- final Uri uri = Uri.parse(sourceUrl);
- @C.ContentType final int type = TextUtils.isEmpty(overrideExtension)
- ? Util.inferContentType(uri) : Util.inferContentType("." + overrideExtension);
- final MediaSource.Factory factory;
- switch (type) {
- case C.TYPE_SS:
- factory = dataSource.getLiveSsMediaSourceFactory();
- break;
- case C.TYPE_DASH:
- factory = dataSource.getDashMediaSourceFactory();
- break;
- case C.TYPE_HLS:
- factory = dataSource.getHlsMediaSourceFactory();
- break;
- case C.TYPE_OTHER:
- factory = dataSource.getExtractorMediaSourceFactory();
- break;
- default:
- throw new IllegalStateException("Unsupported type: " + type);
+ //region Generic media sources
+ static MediaSource buildMediaSource(final PlayerDataSource dataSource,
+ final Stream stream,
+ final StreamInfo streamInfo,
+ final String cacheKey,
+ final MediaItemTag metadata) throws ResolverException {
+ if (streamInfo.getService() == ServiceList.YouTube) {
+ return createYoutubeMediaSource(stream, streamInfo, dataSource, cacheKey, metadata);
}
- return factory.createMediaSource(
- new MediaItem.Builder()
- .setTag(metadata)
- .setUri(uri)
- .setCustomCacheKey(cacheKey)
- .build()
- );
+ final DeliveryMethod deliveryMethod = stream.getDeliveryMethod();
+ switch (deliveryMethod) {
+ case PROGRESSIVE_HTTP:
+ return buildProgressiveMediaSource(dataSource, stream, cacheKey, metadata);
+ case DASH:
+ return buildDashMediaSource(dataSource, stream, cacheKey, metadata);
+ case HLS:
+ return buildHlsMediaSource(dataSource, stream, cacheKey, metadata);
+ case SS:
+ return buildSSMediaSource(dataSource, stream, cacheKey, metadata);
+ // Torrent streams are not supported by ExoPlayer
+ default:
+ throw new ResolverException("Unsupported delivery type: " + deliveryMethod);
+ }
}
+
+ private static ProgressiveMediaSource buildProgressiveMediaSource(
+ final PlayerDataSource dataSource,
+ final Stream stream,
+ final String cacheKey,
+ final MediaItemTag metadata) throws ResolverException {
+ if (!stream.isUrl()) {
+ throw new ResolverException("Non URI progressive contents are not supported");
+ }
+ throwResolverExceptionIfUrlNullOrEmpty(stream.getContent());
+ return dataSource.getProgressiveMediaSourceFactory().createMediaSource(
+ new MediaItem.Builder()
+ .setTag(metadata)
+ .setUri(Uri.parse(stream.getContent()))
+ .setCustomCacheKey(cacheKey)
+ .build());
+ }
+
+ private static DashMediaSource buildDashMediaSource(final PlayerDataSource dataSource,
+ final Stream stream,
+ final String cacheKey,
+ final MediaItemTag metadata)
+ throws ResolverException {
+
+ if (stream.isUrl()) {
+ throwResolverExceptionIfUrlNullOrEmpty(stream.getContent());
+ return dataSource.getDashMediaSourceFactory().createMediaSource(
+ new MediaItem.Builder()
+ .setTag(metadata)
+ .setUri(Uri.parse(stream.getContent()))
+ .setCustomCacheKey(cacheKey)
+ .build());
+ }
+
+ try {
+ return dataSource.getDashMediaSourceFactory().createMediaSource(
+ createDashManifest(stream.getContent(), stream),
+ new MediaItem.Builder()
+ .setTag(metadata)
+ .setUri(manifestUrlToUri(stream.getManifestUrl()))
+ .setCustomCacheKey(cacheKey)
+ .build());
+ } catch (final IOException e) {
+ throw new ResolverException(
+ "Could not create a DASH media source/manifest from the manifest text", e);
+ }
+ }
+
+ private static DashManifest createDashManifest(final String manifestContent,
+ final Stream stream) throws IOException {
+ return new DashManifestParser().parse(manifestUrlToUri(stream.getManifestUrl()),
+ new ByteArrayInputStream(manifestContent.getBytes(StandardCharsets.UTF_8)));
+ }
+
+ private static HlsMediaSource buildHlsMediaSource(final PlayerDataSource dataSource,
+ final Stream stream,
+ final String cacheKey,
+ final MediaItemTag metadata)
+ throws ResolverException {
+ if (stream.isUrl()) {
+ throwResolverExceptionIfUrlNullOrEmpty(stream.getContent());
+ return dataSource.getHlsMediaSourceFactory(null).createMediaSource(
+ new MediaItem.Builder()
+ .setTag(metadata)
+ .setUri(Uri.parse(stream.getContent()))
+ .setCustomCacheKey(cacheKey)
+ .build());
+ }
+
+ final NonUriHlsDataSourceFactory.Builder hlsDataSourceFactoryBuilder =
+ new NonUriHlsDataSourceFactory.Builder();
+ hlsDataSourceFactoryBuilder.setPlaylistString(stream.getContent());
+
+ return dataSource.getHlsMediaSourceFactory(hlsDataSourceFactoryBuilder)
+ .createMediaSource(new MediaItem.Builder()
+ .setTag(metadata)
+ .setUri(manifestUrlToUri(stream.getManifestUrl()))
+ .setCustomCacheKey(cacheKey)
+ .build());
+ }
+
+ private static SsMediaSource buildSSMediaSource(final PlayerDataSource dataSource,
+ final Stream stream,
+ final String cacheKey,
+ final MediaItemTag metadata)
+ throws ResolverException {
+ if (stream.isUrl()) {
+ throwResolverExceptionIfUrlNullOrEmpty(stream.getContent());
+ return dataSource.getSSMediaSourceFactory().createMediaSource(
+ new MediaItem.Builder()
+ .setTag(metadata)
+ .setUri(Uri.parse(stream.getContent()))
+ .setCustomCacheKey(cacheKey)
+ .build());
+ }
+
+ final Uri manifestUri = manifestUrlToUri(stream.getManifestUrl());
+
+ final SsManifest smoothStreamingManifest;
+ try {
+ final ByteArrayInputStream smoothStreamingManifestInput = new ByteArrayInputStream(
+ stream.getContent().getBytes(StandardCharsets.UTF_8));
+ smoothStreamingManifest = new SsManifestParser().parse(manifestUri,
+ smoothStreamingManifestInput);
+ } catch (final IOException e) {
+ throw new ResolverException("Error when parsing manual SS manifest", e);
+ }
+
+ return dataSource.getSSMediaSourceFactory().createMediaSource(
+ smoothStreamingManifest,
+ new MediaItem.Builder()
+ .setTag(metadata)
+ .setUri(manifestUri)
+ .setCustomCacheKey(cacheKey)
+ .build());
+ }
+ //endregion
+
+
+ //region YouTube media sources
+ private static MediaSource createYoutubeMediaSource(final Stream stream,
+ final StreamInfo streamInfo,
+ final PlayerDataSource dataSource,
+ final String cacheKey,
+ final MediaItemTag metadata)
+ throws ResolverException {
+ if (!(stream instanceof AudioStream || stream instanceof VideoStream)) {
+ throw new ResolverException("Generation of YouTube DASH manifest for "
+ + stream.getClass().getSimpleName() + " is not supported");
+ }
+
+ final StreamType streamType = streamInfo.getStreamType();
+ if (streamType == StreamType.VIDEO_STREAM) {
+ return createYoutubeMediaSourceOfVideoStreamType(dataSource, stream, streamInfo,
+ cacheKey, metadata);
+ } else if (streamType == StreamType.POST_LIVE_STREAM) {
+ // If the content is not an URL, uses the DASH delivery method and if the stream type
+ // of the stream is a post live stream, it means that the content is an ended
+ // livestream so we need to generate the manifest corresponding to the content
+ // (which is the last segment of the stream)
+
+ try {
+ final ItagItem itagItem = Objects.requireNonNull(stream.getItagItem());
+ final String manifestString = YoutubePostLiveStreamDvrDashManifestCreator
+ .fromPostLiveStreamDvrStreamingUrl(stream.getContent(),
+ itagItem,
+ itagItem.getTargetDurationSec(),
+ streamInfo.getDuration());
+ return buildYoutubeManualDashMediaSource(dataSource,
+ createDashManifest(manifestString, stream), stream, cacheKey,
+ metadata);
+ } catch (final CreationException | IOException | NullPointerException e) {
+ throw new ResolverException(
+ "Error when generating the DASH manifest of YouTube ended live stream", e);
+ }
+ } else {
+ throw new ResolverException(
+ "DASH manifest generation of YouTube livestreams is not supported");
+ }
+ }
+
+ private static MediaSource createYoutubeMediaSourceOfVideoStreamType(
+ final PlayerDataSource dataSource,
+ final Stream stream,
+ final StreamInfo streamInfo,
+ final String cacheKey,
+ final MediaItemTag metadata) throws ResolverException {
+ final DeliveryMethod deliveryMethod = stream.getDeliveryMethod();
+ switch (deliveryMethod) {
+ case PROGRESSIVE_HTTP:
+ if ((stream instanceof VideoStream && ((VideoStream) stream).isVideoOnly())
+ || stream instanceof AudioStream) {
+ try {
+ final String manifestString = YoutubeProgressiveDashManifestCreator
+ .fromProgressiveStreamingUrl(stream.getContent(),
+ Objects.requireNonNull(stream.getItagItem()),
+ streamInfo.getDuration());
+ return buildYoutubeManualDashMediaSource(dataSource,
+ createDashManifest(manifestString, stream), stream, cacheKey,
+ metadata);
+ } catch (final CreationException | IOException | NullPointerException e) {
+ Log.w(TAG, "Error when generating or parsing DASH manifest of "
+ + "YouTube progressive stream, falling back to a "
+ + "ProgressiveMediaSource.", e);
+ return buildYoutubeProgressiveMediaSource(dataSource, stream, cacheKey,
+ metadata);
+ }
+ } else {
+ // Legacy progressive streams, subtitles are handled by
+ // VideoPlaybackResolver
+ return buildYoutubeProgressiveMediaSource(dataSource, stream, cacheKey,
+ metadata);
+ }
+ case DASH:
+ // If the content is not a URL, uses the DASH delivery method and if the stream
+ // type of the stream is a video stream, it means the content is an OTF stream
+ // so we need to generate the manifest corresponding to the content (which is
+ // the base URL of the OTF stream).
+
+ try {
+ final String manifestString = YoutubeOtfDashManifestCreator
+ .fromOtfStreamingUrl(stream.getContent(),
+ Objects.requireNonNull(stream.getItagItem()),
+ streamInfo.getDuration());
+ return buildYoutubeManualDashMediaSource(dataSource,
+ createDashManifest(manifestString, stream), stream, cacheKey,
+ metadata);
+ } catch (final CreationException | IOException | NullPointerException e) {
+ Log.e(TAG,
+ "Error when generating the DASH manifest of YouTube OTF stream", e);
+ throw new ResolverException(
+ "Error when generating the DASH manifest of YouTube OTF stream", e);
+ }
+ case HLS:
+ return dataSource.getYoutubeHlsMediaSourceFactory().createMediaSource(
+ new MediaItem.Builder()
+ .setTag(metadata)
+ .setUri(Uri.parse(stream.getContent()))
+ .setCustomCacheKey(cacheKey)
+ .build());
+ default:
+ throw new ResolverException("Unsupported delivery method for YouTube contents: "
+ + deliveryMethod);
+ }
+ }
+
+ private static DashMediaSource buildYoutubeManualDashMediaSource(
+ final PlayerDataSource dataSource,
+ final DashManifest dashManifest,
+ final Stream stream,
+ final String cacheKey,
+ final MediaItemTag metadata) {
+ return dataSource.getYoutubeDashMediaSourceFactory().createMediaSource(dashManifest,
+ new MediaItem.Builder()
+ .setTag(metadata)
+ .setUri(Uri.parse(stream.getContent()))
+ .setCustomCacheKey(cacheKey)
+ .build());
+ }
+
+ private static ProgressiveMediaSource buildYoutubeProgressiveMediaSource(
+ final PlayerDataSource dataSource,
+ final Stream stream,
+ final String cacheKey,
+ final MediaItemTag metadata) {
+ return dataSource.getYoutubeProgressiveMediaSourceFactory()
+ .createMediaSource(new MediaItem.Builder()
+ .setTag(metadata)
+ .setUri(Uri.parse(stream.getContent()))
+ .setCustomCacheKey(cacheKey)
+ .build());
+ }
+ //endregion
+
+
+ //region Utils
+ private static Uri manifestUrlToUri(final String manifestUrl) {
+ return Uri.parse(Objects.requireNonNullElse(manifestUrl, ""));
+ }
+
+ private static void throwResolverExceptionIfUrlNullOrEmpty(@Nullable final String url)
+ throws ResolverException {
+ if (url == null) {
+ throw new ResolverException("Null stream URL");
+ } else if (url.isEmpty()) {
+ throw new ResolverException("Empty stream URL");
+ }
+ }
+ //endregion
+
+
+ //region Resolver exception
+ final class ResolverException extends Exception {
+ public ResolverException(final String message) {
+ super(message);
+ }
+
+ public ResolverException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+ }
+ //endregion
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java b/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java
index 1aa7a5a18..cf7d73558 100644
--- a/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java
+++ b/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java
@@ -2,6 +2,7 @@ package org.schabi.newpipe.player.resolver;
import android.content.Context;
import android.net.Uri;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -27,8 +28,12 @@ import java.util.List;
import java.util.Optional;
import static com.google.android.exoplayer2.C.TIME_UNSET;
+import static org.schabi.newpipe.util.ListHelper.getUrlAndNonTorrentStreams;
+import static org.schabi.newpipe.util.ListHelper.getNonTorrentStreams;
public class VideoPlaybackResolver implements PlaybackResolver {
+ private static final String TAG = VideoPlaybackResolver.class.getSimpleName();
+
@NonNull
private final Context context;
@NonNull
@@ -57,7 +62,7 @@ public class VideoPlaybackResolver implements PlaybackResolver {
@Override
@Nullable
public MediaSource resolve(@NonNull final StreamInfo info) {
- final MediaSource liveSource = maybeBuildLiveMediaSource(dataSource, info);
+ final MediaSource liveSource = PlaybackResolver.maybeBuildLiveMediaSource(dataSource, info);
if (liveSource != null) {
streamSourceType = SourceType.LIVE_STREAM;
return liveSource;
@@ -66,40 +71,51 @@ public class VideoPlaybackResolver implements PlaybackResolver {
final List mediaSources = new ArrayList<>();
// Create video stream source
- final List videos = ListHelper.getSortedStreamVideosList(context,
- info.getVideoStreams(), info.getVideoOnlyStreams(), false, true);
+ final List videoStreamsList = ListHelper.getSortedStreamVideosList(context,
+ getNonTorrentStreams(info.getVideoStreams()),
+ getNonTorrentStreams(info.getVideoOnlyStreams()), false, true);
final int index;
- if (videos.isEmpty()) {
+ if (videoStreamsList.isEmpty()) {
index = -1;
} else if (playbackQuality == null) {
- index = qualityResolver.getDefaultResolutionIndex(videos);
+ index = qualityResolver.getDefaultResolutionIndex(videoStreamsList);
} else {
- index = qualityResolver.getOverrideResolutionIndex(videos, getPlaybackQuality());
+ index = qualityResolver.getOverrideResolutionIndex(videoStreamsList,
+ getPlaybackQuality());
}
- final MediaItemTag tag = StreamInfoTag.of(info, videos, index);
+ final MediaItemTag tag = StreamInfoTag.of(info, videoStreamsList, index);
@Nullable final VideoStream video = tag.getMaybeQuality()
.map(MediaItemTag.Quality::getSelectedVideoStream)
.orElse(null);
if (video != null) {
- final MediaSource streamSource = buildMediaSource(dataSource, video.getUrl(),
- PlayerHelper.cacheKeyOf(info, video),
- MediaFormat.getSuffixById(video.getFormatId()), tag);
- mediaSources.add(streamSource);
+ try {
+ final MediaSource streamSource = PlaybackResolver.buildMediaSource(
+ dataSource, video, info, PlaybackResolver.cacheKeyOf(info, video), tag);
+ mediaSources.add(streamSource);
+ } catch (final ResolverException e) {
+ Log.e(TAG, "Unable to create video source", e);
+ return null;
+ }
}
// Create optional audio stream source
- final List audioStreams = info.getAudioStreams();
+ final List audioStreams = getNonTorrentStreams(info.getAudioStreams());
final AudioStream audio = audioStreams.isEmpty() ? null : audioStreams.get(
ListHelper.getDefaultAudioFormat(context, audioStreams));
+
// Use the audio stream if there is no video stream, or
- // Merge with audio stream in case if video does not contain audio
- if (audio != null && (video == null || video.isVideoOnly)) {
- final MediaSource audioSource = buildMediaSource(dataSource, audio.getUrl(),
- PlayerHelper.cacheKeyOf(info, audio),
- MediaFormat.getSuffixById(audio.getFormatId()), tag);
- mediaSources.add(audioSource);
- streamSourceType = SourceType.VIDEO_WITH_SEPARATED_AUDIO;
+ // merge with audio stream in case if video does not contain audio
+ if (audio != null && (video == null || video.isVideoOnly())) {
+ try {
+ final MediaSource audioSource = PlaybackResolver.buildMediaSource(
+ dataSource, audio, info, PlaybackResolver.cacheKeyOf(info, audio), tag);
+ mediaSources.add(audioSource);
+ streamSourceType = SourceType.VIDEO_WITH_SEPARATED_AUDIO;
+ } catch (final ResolverException e) {
+ Log.e(TAG, "Unable to create audio source", e);
+ return null;
+ }
} else {
streamSourceType = SourceType.VIDEO_WITH_AUDIO_OR_AUDIO_ONLY;
}
@@ -108,36 +124,39 @@ public class VideoPlaybackResolver implements PlaybackResolver {
if (mediaSources.isEmpty()) {
return null;
}
+
// Below are auxiliary media sources
// Create subtitle sources
- if (info.getSubtitles() != null) {
- for (final SubtitlesStream subtitle : info.getSubtitles()) {
- final String mimeType = PlayerHelper.subtitleMimeTypesOf(subtitle.getFormat());
- if (mimeType == null) {
- continue;
+ final List subtitlesStreams = info.getSubtitles();
+ if (subtitlesStreams != null) {
+ // Torrent and non URL subtitles are not supported by ExoPlayer
+ final List nonTorrentAndUrlStreams = getUrlAndNonTorrentStreams(
+ subtitlesStreams);
+ for (final SubtitlesStream subtitle : nonTorrentAndUrlStreams) {
+ final MediaFormat mediaFormat = subtitle.getFormat();
+ if (mediaFormat != null) {
+ @C.RoleFlags final int textRoleFlag = subtitle.isAutoGenerated()
+ ? C.ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND
+ : C.ROLE_FLAG_CAPTION;
+ final MediaItem.SubtitleConfiguration textMediaItem =
+ new MediaItem.SubtitleConfiguration.Builder(
+ Uri.parse(subtitle.getContent()))
+ .setMimeType(mediaFormat.getMimeType())
+ .setRoleFlags(textRoleFlag)
+ .setLanguage(PlayerHelper.captionLanguageOf(context, subtitle))
+ .build();
+ final MediaSource textSource = dataSource.getSingleSampleMediaSourceFactory()
+ .createMediaSource(textMediaItem, TIME_UNSET);
+ mediaSources.add(textSource);
}
- final @C.RoleFlags int textRoleFlag = subtitle.isAutoGenerated()
- ? C.ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND
- : C.ROLE_FLAG_CAPTION;
- final MediaItem.SubtitleConfiguration textMediaItem =
- new MediaItem.SubtitleConfiguration.Builder(Uri.parse(subtitle.getUrl()))
- .setMimeType(mimeType)
- .setRoleFlags(textRoleFlag)
- .setLanguage(PlayerHelper.captionLanguageOf(context, subtitle))
- .build();
- final MediaSource textSource = dataSource
- .getSampleMediaSourceFactory()
- .createMediaSource(textMediaItem, TIME_UNSET);
- mediaSources.add(textSource);
}
}
if (mediaSources.size() == 1) {
return mediaSources.get(0);
} else {
- return new MergingMediaSource(mediaSources.toArray(
- new MediaSource[0]));
+ return new MergingMediaSource(true, mediaSources.toArray(new MediaSource[0]));
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java
index c7eb0be40..1ff7947fd 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java
@@ -207,7 +207,7 @@ public class PeertubeInstanceListFragment extends Fragment {
new AlertDialog.Builder(c)
.setTitle(R.string.peertube_instance_add_title)
- .setIcon(R.drawable.place_holder_peertube)
+ .setIcon(R.drawable.ic_placeholder_peertube)
.setView(dialogBinding.getRoot())
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.ok, (dialog1, which) -> {
@@ -411,7 +411,7 @@ public class PeertubeInstanceListFragment extends Fragment {
lastChecked = instanceRB;
}
});
- instanceIconView.setImageResource(R.drawable.place_holder_peertube);
+ instanceIconView.setImageResource(R.drawable.ic_placeholder_peertube);
}
@SuppressLint("ClickableViewAccessibility")
diff --git a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java
index 62455d682..798d299c0 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java
@@ -10,7 +10,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView;
@@ -21,11 +20,12 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.graphics.drawable.DrawableCompat;
-import androidx.core.widget.TextViewCompat;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import org.schabi.newpipe.R;
+import org.schabi.newpipe.databinding.ListRadioIconItemBinding;
+import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding;
import org.schabi.newpipe.player.MainPlayer;
import org.schabi.newpipe.player.NotificationConstants;
import org.schabi.newpipe.util.DeviceUtils;
@@ -190,13 +190,12 @@ public class NotificationActionsPreference extends Preference {
void openActionChooserDialog() {
final LayoutInflater inflater = LayoutInflater.from(getContext());
- final LinearLayout rootLayout = (LinearLayout) inflater.inflate(
- R.layout.single_choice_dialog_view, null, false);
- final RadioGroup radioGroup = rootLayout.findViewById(android.R.id.list);
+ final SingleChoiceDialogViewBinding binding =
+ SingleChoiceDialogViewBinding.inflate(inflater);
final AlertDialog alertDialog = new AlertDialog.Builder(getContext())
.setTitle(SLOT_TITLES[i])
- .setView(radioGroup)
+ .setView(binding.getRoot())
.setCancelable(true)
.create();
@@ -208,8 +207,8 @@ public class NotificationActionsPreference extends Preference {
for (int id = 0; id < NotificationConstants.SLOT_ALLOWED_ACTIONS[i].length; ++id) {
final int action = NotificationConstants.SLOT_ALLOWED_ACTIONS[i][id];
- final RadioButton radioButton
- = (RadioButton) inflater.inflate(R.layout.list_radio_icon_item, null);
+ final RadioButton radioButton = ListRadioIconItemBinding.inflate(inflater)
+ .getRoot();
// if present set action icon with correct color
if (NotificationConstants.ACTION_ICONS[action] != 0) {
@@ -220,8 +219,8 @@ public class NotificationActionsPreference extends Preference {
android.R.attr.textColorPrimary);
drawable = DrawableCompat.wrap(drawable).mutate();
DrawableCompat.setTint(drawable, color);
- TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(radioButton,
- null, null, drawable, null);
+ radioButton.setCompoundDrawablesRelativeWithIntrinsicBounds(null,
+ null, drawable, null);
}
}
@@ -231,7 +230,7 @@ public class NotificationActionsPreference extends Preference {
radioButton.setLayoutParams(new RadioGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
radioButton.setOnClickListener(radioButtonsClickListener);
- radioGroup.addView(radioButton);
+ binding.list.addView(radioButton);
}
alertDialog.show();
diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java
index 73aec4a7b..289c824ba 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java
@@ -1,5 +1,8 @@
package org.schabi.newpipe.settings.tabs;
+import static org.schabi.newpipe.settings.tabs.Tab.typeFrom;
+import static org.schabi.newpipe.util.ServiceHelper.getNameOfServiceById;
+
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.content.Context;
@@ -28,7 +31,6 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
-import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.settings.SelectChannelFragment;
import org.schabi.newpipe.settings.SelectKioskFragment;
import org.schabi.newpipe.settings.SelectPlaylistFragment;
@@ -39,8 +41,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import static org.schabi.newpipe.settings.tabs.Tab.typeFrom;
-
public class ChooseTabsFragment extends Fragment {
private TabsManager tabsManager;
@@ -374,36 +374,31 @@ public class ChooseTabsFragment extends Fragment {
return;
}
- final String tabName;
+ tabNameView.setText(getTabName(type, tab));
+ tabIconView.setImageResource(tab.getTabIconRes(requireContext()));
+ }
+
+ private String getTabName(@NonNull final Tab.Type type, @NonNull final Tab tab) {
switch (type) {
case BLANK:
- tabName = getString(R.string.blank_page_summary);
- break;
+ return getString(R.string.blank_page_summary);
case DEFAULT_KIOSK:
- tabName = getString(R.string.default_kiosk_page_summary);
- break;
+ return getString(R.string.default_kiosk_page_summary);
case KIOSK:
- tabName = NewPipe.getNameOfService(((Tab.KioskTab) tab)
- .getKioskServiceId()) + "/" + tab.getTabName(requireContext());
- break;
+ return getNameOfServiceById(((Tab.KioskTab) tab).getKioskServiceId())
+ + "/" + tab.getTabName(requireContext());
case CHANNEL:
- tabName = NewPipe.getNameOfService(((Tab.ChannelTab) tab)
- .getChannelServiceId()) + "/" + tab.getTabName(requireContext());
- break;
+ return getNameOfServiceById(((Tab.ChannelTab) tab).getChannelServiceId())
+ + "/" + tab.getTabName(requireContext());
case PLAYLIST:
final int serviceId = ((Tab.PlaylistTab) tab).getPlaylistServiceId();
final String serviceName = serviceId == -1
? getString(R.string.local)
- : NewPipe.getNameOfService(serviceId);
- tabName = serviceName + "/" + tab.getTabName(requireContext());
- break;
+ : getNameOfServiceById(serviceId);
+ return serviceName + "/" + tab.getTabName(requireContext());
default:
- tabName = tab.getTabName(requireContext());
- break;
+ return tab.getTabName(requireContext());
}
-
- tabNameView.setText(tabName);
- tabIconView.setImageResource(tab.getTabIconRes(requireContext()));
}
@SuppressLint("ClickableViewAccessibility")
diff --git a/app/src/main/java/org/schabi/newpipe/util/KeyboardUtil.java b/app/src/main/java/org/schabi/newpipe/util/KeyboardUtil.java
index 71c0d3944..a709dc32e 100644
--- a/app/src/main/java/org/schabi/newpipe/util/KeyboardUtil.java
+++ b/app/src/main/java/org/schabi/newpipe/util/KeyboardUtil.java
@@ -24,7 +24,19 @@ public final class KeyboardUtil {
if (editText.requestFocus()) {
final InputMethodManager imm = ContextCompat.getSystemService(activity,
InputMethodManager.class);
- imm.showSoftInput(editText, InputMethodManager.SHOW_FORCED);
+ if (!imm.showSoftInput(editText, InputMethodManager.SHOW_FORCED)) {
+ /*
+ * Sometimes the keyboard can't be shown because Android's ImeFocusController is in
+ * a incorrect state e.g. when animations are disabled or the unfocus event of the
+ * previous view arrives in the wrong moment (see #7647 for details).
+ * The invalid state can be fixed by to re-focusing the editText.
+ */
+ editText.clearFocus();
+ editText.requestFocus();
+
+ // Try again
+ imm.showSoftInput(editText, InputMethodManager.SHOW_FORCED);
+ }
}
}
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 c3ccef87c..eabac8330 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java
@@ -13,6 +13,8 @@ import androidx.preference.PreferenceManager;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream.AudioStream;
+import org.schabi.newpipe.extractor.stream.DeliveryMethod;
+import org.schabi.newpipe.extractor.stream.Stream;
import org.schabi.newpipe.extractor.stream.VideoStream;
import java.util.ArrayList;
@@ -24,6 +26,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
public final class ListHelper {
@@ -37,10 +40,9 @@ public final class ListHelper {
// Audio format in order of efficiency. 0=most efficient, n=least efficient
private static final List AUDIO_FORMAT_EFFICIENCY_RANKING =
Arrays.asList(MediaFormat.WEBMA, MediaFormat.M4A, MediaFormat.MP3);
-
- private static final Set HIGH_RESOLUTION_LIST
- // Uses a HashSet for better performance
- = new HashSet<>(Arrays.asList("1440p", "2160p", "1440p60", "2160p60"));
+ // Use a HashSet for better performance
+ private static final Set HIGH_RESOLUTION_LIST = new HashSet<>(
+ Arrays.asList("1440p", "2160p"));
private ListHelper() { }
@@ -110,6 +112,51 @@ public final class ListHelper {
}
}
+ /**
+ * Return a {@link Stream} list which uses the given delivery method from a {@link Stream}
+ * list.
+ *
+ * @param streamList the original {@link Stream stream} list
+ * @param deliveryMethod the {@link DeliveryMethod delivery method}
+ * @param the item type's class that extends {@link Stream}
+ * @return a {@link Stream stream} list which uses the given delivery method
+ */
+ @NonNull
+ public static List getStreamsOfSpecifiedDelivery(
+ final List streamList,
+ final DeliveryMethod deliveryMethod) {
+ return getFilteredStreamList(streamList,
+ stream -> stream.getDeliveryMethod() == deliveryMethod);
+ }
+
+ /**
+ * Return a {@link Stream} list which only contains URL streams and non-torrent streams.
+ *
+ * @param streamList the original stream list
+ * @param the item type's class that extends {@link Stream}
+ * @return a stream list which only contains URL streams and non-torrent streams
+ */
+ @NonNull
+ public static List getUrlAndNonTorrentStreams(
+ final List streamList) {
+ return getFilteredStreamList(streamList,
+ stream -> stream.isUrl() && stream.getDeliveryMethod() != DeliveryMethod.TORRENT);
+ }
+
+ /**
+ * Return a {@link Stream} list which only contains non-torrent streams.
+ *
+ * @param streamList the original stream list
+ * @param the item type's class that extends {@link Stream}
+ * @return a stream list which only contains non-torrent streams
+ */
+ @NonNull
+ public static List getNonTorrentStreams(
+ final List streamList) {
+ return getFilteredStreamList(streamList,
+ stream -> stream.getDeliveryMethod() != DeliveryMethod.TORRENT);
+ }
+
/**
* Join the two lists of video streams (video_only and normal videos),
* and sort them according with default format chosen by the user.
@@ -145,6 +192,26 @@ public final class ListHelper {
// Utils
//////////////////////////////////////////////////////////////////////////*/
+ /**
+ * Get a filtered stream list, by using Java 8 Stream's API and the given predicate.
+ *
+ * @param streamList the stream list to filter
+ * @param streamListPredicate the predicate which will be used to filter streams
+ * @param the item type's class that extends {@link Stream}
+ * @return a new stream list filtered using the given predicate
+ */
+ private static List getFilteredStreamList(
+ final List streamList,
+ final Predicate streamListPredicate) {
+ if (streamList == null) {
+ return Collections.emptyList();
+ }
+
+ return streamList.stream()
+ .filter(streamListPredicate)
+ .collect(Collectors.toList());
+ }
+
private static String computeDefaultResolution(final Context context, final int key,
final int value) {
final SharedPreferences preferences
@@ -177,7 +244,7 @@ public final class ListHelper {
static int getDefaultResolutionIndex(final String defaultResolution,
final String bestResolutionKey,
final MediaFormat defaultFormat,
- final List videoStreams) {
+ @Nullable final List videoStreams) {
if (videoStreams == null || videoStreams.isEmpty()) {
return -1;
}
@@ -233,7 +300,9 @@ public final class ListHelper {
.flatMap(List::stream)
// Filter out higher resolutions (or not if high resolutions should always be shown)
.filter(stream -> showHigherResolutions
- || !HIGH_RESOLUTION_LIST.contains(stream.getResolution()))
+ || !HIGH_RESOLUTION_LIST.contains(stream.getResolution()
+ // Replace any frame rate with nothing
+ .replaceAll("p\\d+$", "p")))
.collect(Collectors.toList());
final HashMap hashMap = new HashMap<>();
@@ -366,8 +435,9 @@ public final class ListHelper {
* @param videoStreams the available video streams
* @return the index of the preferred video stream
*/
- static int getVideoStreamIndex(final String targetResolution, final MediaFormat targetFormat,
- final List videoStreams) {
+ static int getVideoStreamIndex(@NonNull final String targetResolution,
+ final MediaFormat targetFormat,
+ @NonNull final List videoStreams) {
int fullMatchIndex = -1;
int fullMatchNoRefreshIndex = -1;
int resMatchOnlyIndex = -1;
@@ -428,7 +498,7 @@ public final class ListHelper {
* @param videoStreams the list of video streams to check
* @return the index of the preferred video stream
*/
- private static int getDefaultResolutionWithDefaultFormat(final Context context,
+ private static int getDefaultResolutionWithDefaultFormat(@NonNull final Context context,
final String defaultResolution,
final List videoStreams) {
final MediaFormat defaultFormat = getDefaultFormat(context,
@@ -437,7 +507,7 @@ public final class ListHelper {
context.getString(R.string.best_resolution_key), defaultFormat, videoStreams);
}
- private static MediaFormat getDefaultFormat(final Context context,
+ private static MediaFormat getDefaultFormat(@NonNull final Context context,
@StringRes final int defaultFormatKey,
@StringRes final int defaultFormatValueKey) {
final SharedPreferences preferences
@@ -457,8 +527,8 @@ public final class ListHelper {
return defaultMediaFormat;
}
- private static MediaFormat getMediaFormatFromKey(final Context context,
- final String formatKey) {
+ private static MediaFormat getMediaFormatFromKey(@NonNull final Context context,
+ @NonNull final String formatKey) {
MediaFormat format = null;
if (formatKey.equals(context.getString(R.string.video_webm_key))) {
format = MediaFormat.WEBM;
@@ -496,12 +566,20 @@ public final class ListHelper {
- formatRanking.indexOf(streamB.getFormat());
}
- private static int compareVideoStreamResolution(final String r1, final String r2) {
- final int res1 = Integer.parseInt(r1.replaceAll("0p\\d+$", "1")
- .replaceAll("[^\\d.]", ""));
- final int res2 = Integer.parseInt(r2.replaceAll("0p\\d+$", "1")
- .replaceAll("[^\\d.]", ""));
- return res1 - res2;
+ private static int compareVideoStreamResolution(@NonNull final String r1,
+ @NonNull final String r2) {
+ try {
+ final int res1 = Integer.parseInt(r1.replaceAll("0p\\d+$", "1")
+ .replaceAll("[^\\d.]", ""));
+ final int res2 = Integer.parseInt(r2.replaceAll("0p\\d+$", "1")
+ .replaceAll("[^\\d.]", ""));
+ return res1 - res2;
+ } catch (final NumberFormatException e) {
+ // Consider the first one greater because we don't know if the two streams are
+ // different or not (a NumberFormatException was thrown so we don't know the resolution
+ // of one stream or of all streams)
+ return 1;
+ }
}
// Compares the quality of two video streams.
@@ -536,7 +614,7 @@ public final class ListHelper {
* @param context App context
* @return maximum resolution allowed or null if there is no maximum
*/
- private static String getResolutionLimit(final Context context) {
+ private static String getResolutionLimit(@NonNull final Context context) {
String resolutionLimit = null;
if (isMeteredNetwork(context)) {
final SharedPreferences preferences
@@ -555,7 +633,7 @@ public final class ListHelper {
* @param context App context
* @return {@code true} if connected to a metered network
*/
- public static boolean isMeteredNetwork(final Context context) {
+ public static boolean isMeteredNetwork(@NonNull final Context context) {
final ConnectivityManager manager
= ContextCompat.getSystemService(context, ConnectivityManager.class);
if (manager == null || manager.getActiveNetworkInfo() == null) {
diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
index e55114a2d..c40b1a430 100644
--- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
@@ -33,6 +33,7 @@ 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.DeliveryMethod;
import org.schabi.newpipe.extractor.stream.Stream;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
@@ -60,7 +61,9 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.settings.SettingsActivity;
import org.schabi.newpipe.util.external_communication.ShareUtils;
-import java.util.ArrayList;
+import java.util.List;
+
+import static org.schabi.newpipe.util.ListHelper.getUrlAndNonTorrentStreams;
public final class NavigationHelper {
public static final String MAIN_FRAGMENT_TAG = "main_fragment_tag";
@@ -217,30 +220,47 @@ public final class NavigationHelper {
public static void playOnExternalAudioPlayer(@NonNull final Context context,
@NonNull final StreamInfo info) {
- final int index = ListHelper.getDefaultAudioFormat(context, info.getAudioStreams());
-
- if (index == -1) {
+ final List audioStreams = info.getAudioStreams();
+ if (audioStreams == null || audioStreams.isEmpty()) {
Toast.makeText(context, R.string.audio_streams_empty, Toast.LENGTH_SHORT).show();
return;
}
- final AudioStream audioStream = info.getAudioStreams().get(index);
+ final List audioStreamsForExternalPlayers =
+ getUrlAndNonTorrentStreams(audioStreams);
+ if (audioStreamsForExternalPlayers.isEmpty()) {
+ Toast.makeText(context, R.string.no_audio_streams_available_for_external_players,
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ final int index = ListHelper.getDefaultAudioFormat(context, audioStreamsForExternalPlayers);
+ final AudioStream audioStream = audioStreamsForExternalPlayers.get(index);
+
playOnExternalPlayer(context, info.getName(), info.getUploaderName(), audioStream);
}
- public static void playOnExternalVideoPlayer(@NonNull final Context context,
+ public static void playOnExternalVideoPlayer(final Context context,
@NonNull final StreamInfo info) {
- final ArrayList videoStreamsList = new ArrayList<>(
- ListHelper.getSortedStreamVideosList(context, info.getVideoStreams(), null, false,
- false));
- final int index = ListHelper.getDefaultResolutionIndex(context, videoStreamsList);
-
- if (index == -1) {
+ final List videoStreams = info.getVideoStreams();
+ if (videoStreams == null || videoStreams.isEmpty()) {
Toast.makeText(context, R.string.video_streams_empty, Toast.LENGTH_SHORT).show();
return;
}
- final VideoStream videoStream = videoStreamsList.get(index);
+ final List videoStreamsForExternalPlayers =
+ ListHelper.getSortedStreamVideosList(context,
+ getUrlAndNonTorrentStreams(videoStreams), null, false, false);
+ if (videoStreamsForExternalPlayers.isEmpty()) {
+ Toast.makeText(context, R.string.no_video_streams_available_for_external_players,
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ final int index = ListHelper.getDefaultResolutionIndex(context,
+ videoStreamsForExternalPlayers);
+
+ final VideoStream videoStream = videoStreamsForExternalPlayers.get(index);
playOnExternalPlayer(context, info.getName(), info.getUploaderName(), videoStream);
}
@@ -248,9 +268,48 @@ public final class NavigationHelper {
@Nullable final String name,
@Nullable final String artist,
@NonNull final Stream stream) {
+ final DeliveryMethod deliveryMethod = stream.getDeliveryMethod();
+ final String mimeType;
+
+ if (!stream.isUrl() || deliveryMethod == DeliveryMethod.TORRENT) {
+ Toast.makeText(context, R.string.selected_stream_external_player_not_supported,
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ switch (deliveryMethod) {
+ case PROGRESSIVE_HTTP:
+ if (stream.getFormat() == null) {
+ if (stream instanceof AudioStream) {
+ mimeType = "audio/*";
+ } else if (stream instanceof VideoStream) {
+ mimeType = "video/*";
+ } else {
+ // This should never be reached, because subtitles are not opened in
+ // external players
+ return;
+ }
+ } else {
+ mimeType = stream.getFormat().getMimeType();
+ }
+ break;
+ case HLS:
+ mimeType = "application/x-mpegURL";
+ break;
+ case DASH:
+ mimeType = "application/dash+xml";
+ break;
+ case SS:
+ mimeType = "application/vnd.ms-sstr+xml";
+ break;
+ default:
+ // Torrent streams are not exposed to external players
+ mimeType = "";
+ }
+
final Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
- intent.setDataAndType(Uri.parse(stream.getUrl()), stream.getFormat().getMimeType());
+ intent.setDataAndType(Uri.parse(stream.getContent()), mimeType);
intent.putExtra(Intent.EXTRA_TITLE, name);
intent.putExtra("title", name);
intent.putExtra("artist", artist);
diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java
index da86ab1a4..aabc459d0 100644
--- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java
@@ -7,6 +7,8 @@ import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
+import androidx.annotation.Nullable;
+
import com.squareup.picasso.Cache;
import com.squareup.picasso.LruCache;
import com.squareup.picasso.OkHttp3Downloader;
@@ -158,6 +160,11 @@ public final class PicassoHelper {
});
}
+ @Nullable
+ public static Bitmap getImageFromCacheIfPresent(final String imageUrl) {
+ // URLs in the internal cache finish with \n so we need to add \n to image URLs
+ return picassoCache.get(imageUrl + "\n");
+ }
public static void loadNotificationIcon(final String url,
final Consumer bitmapConsumer) {
diff --git a/app/src/main/java/org/schabi/newpipe/util/SecondaryStreamHelper.java b/app/src/main/java/org/schabi/newpipe/util/SecondaryStreamHelper.java
index 8c697d327..e7fd2d4a4 100644
--- a/app/src/main/java/org/schabi/newpipe/util/SecondaryStreamHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/SecondaryStreamHelper.java
@@ -1,6 +1,7 @@
package org.schabi.newpipe.util;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream.AudioStream;
@@ -14,7 +15,8 @@ public class SecondaryStreamHelper {
private final int position;
private final StreamSizeWrapper streams;
- public SecondaryStreamHelper(final StreamSizeWrapper streams, final T selectedStream) {
+ public SecondaryStreamHelper(@NonNull final StreamSizeWrapper streams,
+ final T selectedStream) {
this.streams = streams;
this.position = streams.getStreamsList().indexOf(selectedStream);
if (this.position < 0) {
@@ -29,9 +31,15 @@ public class SecondaryStreamHelper {
* @param videoStream desired video ONLY stream
* @return selected audio stream or null if a candidate was not found
*/
+ @Nullable
public static AudioStream getAudioStreamFor(@NonNull final List audioStreams,
@NonNull final VideoStream videoStream) {
- switch (videoStream.getFormat()) {
+ final MediaFormat mediaFormat = videoStream.getFormat();
+ if (mediaFormat == null) {
+ return null;
+ }
+
+ switch (mediaFormat) {
case WEBM:
case MPEG_4:// ¿is mpeg-4 DASH?
break;
@@ -39,7 +47,7 @@ public class SecondaryStreamHelper {
return null;
}
- final boolean m4v = videoStream.getFormat() == MediaFormat.MPEG_4;
+ final boolean m4v = (mediaFormat == MediaFormat.MPEG_4);
for (final AudioStream audio : audioStreams) {
if (audio.getFormat() == (m4v ? MediaFormat.M4A : MediaFormat.WEBMA)) {
diff --git a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java
index d41493a7f..b13ae4a97 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java
@@ -1,9 +1,13 @@
package org.schabi.newpipe.util;
+import static org.schabi.newpipe.extractor.ServiceList.SoundCloud;
+
import android.content.Context;
import android.content.SharedPreferences;
import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.preference.PreferenceManager;
@@ -18,10 +22,9 @@ import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance;
+import java.util.Optional;
import java.util.concurrent.TimeUnit;
-import static org.schabi.newpipe.extractor.ServiceList.SoundCloud;
-
public final class ServiceHelper {
private static final StreamingService DEFAULT_FALLBACK_SERVICE = ServiceList.YouTube;
@@ -31,17 +34,17 @@ public final class ServiceHelper {
public static int getIcon(final int serviceId) {
switch (serviceId) {
case 0:
- return R.drawable.place_holder_youtube;
+ return R.drawable.ic_smart_display;
case 1:
- return R.drawable.place_holder_cloud;
+ return R.drawable.ic_cloud;
case 2:
- return R.drawable.place_holder_gadse;
+ return R.drawable.ic_placeholder_media_ccc;
case 3:
- return R.drawable.place_holder_peertube;
+ return R.drawable.ic_placeholder_peertube;
case 4:
- return R.drawable.place_holder_bandcamp;
+ return R.drawable.ic_placeholder_bandcamp;
default:
- return R.drawable.place_holder_circle;
+ return R.drawable.ic_circle;
}
}
@@ -113,18 +116,32 @@ public final class ServiceHelper {
}
public static int getSelectedServiceId(final Context context) {
+ return Optional.ofNullable(getSelectedService(context))
+ .orElse(DEFAULT_FALLBACK_SERVICE)
+ .getServiceId();
+ }
+
+ @Nullable
+ public static StreamingService getSelectedService(final Context context) {
final String serviceName = PreferenceManager.getDefaultSharedPreferences(context)
.getString(context.getString(R.string.current_service_key),
context.getString(R.string.default_service_value));
- int serviceId;
try {
- serviceId = NewPipe.getService(serviceName).getServiceId();
+ return NewPipe.getService(serviceName);
} catch (final ExtractionException e) {
- serviceId = DEFAULT_FALLBACK_SERVICE.getServiceId();
+ return null;
}
+ }
- return serviceId;
+ @NonNull
+ public static String getNameOfServiceById(final int serviceId) {
+ return ServiceList.all().stream()
+ .filter(s -> s.getServiceId() == serviceId)
+ .findFirst()
+ .map(StreamingService::getServiceInfo)
+ .map(StreamingService.ServiceInfo::getName)
+ .orElse("");
}
public static void setSelectedServiceId(final Context context, final int serviceId) {
@@ -138,16 +155,6 @@ public final class ServiceHelper {
setSelectedServicePreferences(context, serviceName);
}
- public static void setSelectedServiceId(final Context context, final String serviceName) {
- final int serviceId = NewPipe.getIdOfService(serviceName);
- if (serviceId == -1) {
- setSelectedServicePreferences(context,
- DEFAULT_FALLBACK_SERVICE.getServiceInfo().getName());
- } else {
- setSelectedServicePreferences(context, serviceName);
- }
- }
-
private static void setSelectedServicePreferences(final Context context,
final String serviceName) {
PreferenceManager.getDefaultSharedPreferences(context).edit().
diff --git a/app/src/main/java/org/schabi/newpipe/util/SparseItemUtil.java b/app/src/main/java/org/schabi/newpipe/util/SparseItemUtil.java
index b8cd4ef69..0c5f418b2 100644
--- a/app/src/main/java/org/schabi/newpipe/util/SparseItemUtil.java
+++ b/app/src/main/java/org/schabi/newpipe/util/SparseItemUtil.java
@@ -1,7 +1,5 @@
package org.schabi.newpipe.util;
-import static org.schabi.newpipe.extractor.stream.StreamType.AUDIO_LIVE_STREAM;
-import static org.schabi.newpipe.extractor.stream.StreamType.LIVE_STREAM;
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
import android.content.Context;
@@ -49,8 +47,8 @@ public final class SparseItemUtil {
public static void fetchItemInfoIfSparse(@NonNull final Context context,
@NonNull final StreamInfoItem item,
@NonNull final Consumer callback) {
- if (((item.getStreamType() == LIVE_STREAM || item.getStreamType() == AUDIO_LIVE_STREAM)
- || item.getDuration() >= 0) && !isNullOrEmpty(item.getUploaderUrl())) {
+ if ((StreamTypeUtil.isLiveStream(item.getStreamType()) || item.getDuration() >= 0)
+ && !isNullOrEmpty(item.getUploaderUrl())) {
// if the duration is >= 0 (provided that the item is not a livestream) and there is an
// uploader url, probably all info is already there, so there is no need to fetch it
callback.accept(new SinglePlayQueue(item));
diff --git a/app/src/main/java/org/schabi/newpipe/util/StreamItemAdapter.java b/app/src/main/java/org/schabi/newpipe/util/StreamItemAdapter.java
index 03342a497..4b5e675c9 100644
--- a/app/src/main/java/org/schabi/newpipe/util/StreamItemAdapter.java
+++ b/app/src/main/java/org/schabi/newpipe/util/StreamItemAdapter.java
@@ -10,6 +10,8 @@ import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+
import org.schabi.newpipe.DownloaderImpl;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.MediaFormat;
@@ -87,7 +89,8 @@ public class StreamItemAdapter extends BaseA
}
@Override
- public View getDropDownView(final int position, final View convertView,
+ public View getDropDownView(final int position,
+ final View convertView,
final ViewGroup parent) {
return getCustomView(position, convertView, parent, true);
}
@@ -98,7 +101,10 @@ public class StreamItemAdapter extends BaseA
convertView, parent, false);
}
- private View getCustomView(final int position, final View view, final ViewGroup parent,
+ @NonNull
+ private View getCustomView(final int position,
+ final View view,
+ final ViewGroup parent,
final boolean isDropdownItem) {
View convertView = view;
if (convertView == null) {
@@ -112,6 +118,7 @@ public class StreamItemAdapter extends BaseA
final TextView sizeView = convertView.findViewById(R.id.stream_size);
final T stream = getItem(position);
+ final MediaFormat mediaFormat = stream.getFormat();
int woSoundIconVisibility = View.GONE;
String qualityString;
@@ -135,24 +142,32 @@ public class StreamItemAdapter extends BaseA
}
} else if (stream instanceof AudioStream) {
final AudioStream audioStream = ((AudioStream) stream);
- qualityString = audioStream.getAverageBitrate() > 0
- ? audioStream.getAverageBitrate() + "kbps"
- : audioStream.getFormat().getName();
+ if (audioStream.getAverageBitrate() > 0) {
+ qualityString = audioStream.getAverageBitrate() + "kbps";
+ } else if (mediaFormat != null) {
+ qualityString = mediaFormat.getName();
+ } else {
+ qualityString = context.getString(R.string.unknown_quality);
+ }
} else if (stream instanceof SubtitlesStream) {
qualityString = ((SubtitlesStream) stream).getDisplayLanguageName();
if (((SubtitlesStream) stream).isAutoGenerated()) {
qualityString += " (" + context.getString(R.string.caption_auto_generated) + ")";
}
} else {
- qualityString = stream.getFormat().getSuffix();
+ if (mediaFormat == null) {
+ qualityString = context.getString(R.string.unknown_quality);
+ } else {
+ qualityString = mediaFormat.getSuffix();
+ }
}
if (streamsWrapper.getSizeInBytes(position) > 0) {
final SecondaryStreamHelper secondary = secondaryStreams == null ? null
: secondaryStreams.get(position);
if (secondary != null) {
- final long size
- = secondary.getSizeInBytes() + streamsWrapper.getSizeInBytes(position);
+ final long size = secondary.getSizeInBytes()
+ + streamsWrapper.getSizeInBytes(position);
sizeView.setText(Utility.formatBytes(size));
} else {
sizeView.setText(streamsWrapper.getFormattedSize(position));
@@ -164,11 +179,15 @@ public class StreamItemAdapter extends BaseA
if (stream instanceof SubtitlesStream) {
formatNameView.setText(((SubtitlesStream) stream).getLanguageTag());
- } else if (stream.getFormat() == MediaFormat.WEBMA_OPUS) {
- // noinspection AndroidLintSetTextI18n
- formatNameView.setText("opus");
} else {
- formatNameView.setText(stream.getFormat().getName());
+ if (mediaFormat == null) {
+ formatNameView.setText(context.getString(R.string.unknown_format));
+ } else if (mediaFormat == MediaFormat.WEBMA_OPUS) {
+ // noinspection AndroidLintSetTextI18n
+ formatNameView.setText("opus");
+ } else {
+ formatNameView.setText(mediaFormat.getName());
+ }
}
qualityView.setText(qualityString);
@@ -233,6 +252,7 @@ public class StreamItemAdapter extends BaseA
* @param streamsWrapper the wrapper
* @return a {@link Single} that returns a boolean indicating if any elements were changed
*/
+ @NonNull
public static Single fetchSizeForWrapper(
final StreamSizeWrapper streamsWrapper) {
final Callable fetchAndSet = () -> {
@@ -243,7 +263,7 @@ public class StreamItemAdapter extends BaseA
}
final long contentLength = DownloaderImpl.getInstance().getContentLength(
- stream.getUrl());
+ stream.getContent());
streamsWrapper.setSize(stream, contentLength);
hasChanged = true;
}
diff --git a/app/src/main/java/org/schabi/newpipe/util/StreamTypeUtil.java b/app/src/main/java/org/schabi/newpipe/util/StreamTypeUtil.java
index 87b3eed4f..0cc0ecf1f 100644
--- a/app/src/main/java/org/schabi/newpipe/util/StreamTypeUtil.java
+++ b/app/src/main/java/org/schabi/newpipe/util/StreamTypeUtil.java
@@ -3,7 +3,7 @@ package org.schabi.newpipe.util;
import org.schabi.newpipe.extractor.stream.StreamType;
/**
- * Utility class for {@link org.schabi.newpipe.extractor.stream.StreamType}.
+ * Utility class for {@link StreamType}.
*/
public final class StreamTypeUtil {
private StreamTypeUtil() {
@@ -11,11 +11,37 @@ public final class StreamTypeUtil {
}
/**
- * Checks if the streamType is a livestream.
+ * Check if the {@link StreamType} of a stream is a livestream.
*
- * @param streamType
- * @return true when the streamType is a
- * {@link StreamType#LIVE_STREAM} or {@link StreamType#AUDIO_LIVE_STREAM}
+ * @param streamType the stream type of the stream
+ * @return whether the stream type is {@link StreamType#AUDIO_STREAM},
+ * {@link StreamType#AUDIO_LIVE_STREAM} or {@link StreamType#POST_LIVE_AUDIO_STREAM}
+ */
+ public static boolean isAudio(final StreamType streamType) {
+ return streamType == StreamType.AUDIO_STREAM
+ || streamType == StreamType.AUDIO_LIVE_STREAM
+ || streamType == StreamType.POST_LIVE_AUDIO_STREAM;
+ }
+
+ /**
+ * Check if the {@link StreamType} of a stream is a livestream.
+ *
+ * @param streamType the stream type of the stream
+ * @return whether the stream type is {@link StreamType#VIDEO_STREAM},
+ * {@link StreamType#LIVE_STREAM} or {@link StreamType#POST_LIVE_STREAM}
+ */
+ public static boolean isVideo(final StreamType streamType) {
+ return streamType == StreamType.VIDEO_STREAM
+ || streamType == StreamType.LIVE_STREAM
+ || streamType == StreamType.POST_LIVE_STREAM;
+ }
+
+ /**
+ * Check if the {@link StreamType} of a stream is a livestream.
+ *
+ * @param streamType the stream type of the stream
+ * @return whether the stream type is {@link StreamType#LIVE_STREAM} or
+ * {@link StreamType#AUDIO_LIVE_STREAM}
*/
public static boolean isLiveStream(final StreamType streamType) {
return streamType == StreamType.LIVE_STREAM
diff --git a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java
index 7c47d387f..b8e3a86ed 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java
@@ -23,14 +23,17 @@ import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
import android.util.TypedValue;
import androidx.annotation.AttrRes;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StyleRes;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
+import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.content.ContextCompat;
import androidx.preference.PreferenceManager;
@@ -227,6 +230,20 @@ public final class ThemeHelper {
return value.data;
}
+ /**
+ * Resolves a {@link Drawable} by it's id.
+ *
+ * @param context Context
+ * @param attrResId Resource id
+ * @return the {@link Drawable}
+ */
+ public static Drawable resolveDrawable(@NonNull final Context context,
+ @AttrRes final int attrResId) {
+ final TypedValue typedValue = new TypedValue();
+ context.getTheme().resolveAttribute(attrResId, typedValue, true);
+ return AppCompatResources.getDrawable(context, typedValue.resourceId);
+ }
+
private static String getSelectedThemeKey(final Context context) {
final String themeKey = context.getString(R.string.theme_key);
final String defaultTheme = context.getResources().getString(R.string.default_theme_value);
diff --git a/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java b/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java
index c4f1675cf..8324146fe 100644
--- a/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java
+++ b/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java
@@ -1,5 +1,7 @@
package org.schabi.newpipe.util.external_communication;
+import static org.schabi.newpipe.MainActivity.DEBUG;
+
import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.ClipboardManager;
@@ -7,17 +9,28 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;
+import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
+import androidx.core.content.FileProvider;
+import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.R;
+import org.schabi.newpipe.util.PicassoHelper;
+
+import java.io.File;
+import java.io.FileOutputStream;
public final class ShareUtils {
+ private static final String TAG = ShareUtils.class.getSimpleName();
+
private ShareUtils() {
}
@@ -231,9 +244,11 @@ public final class ShareUtils {
/**
* Open the android share sheet to share a content.
*
+ *
* For Android 10+ users, a content preview is shown, which includes the title of the shared
- * content.
- * Support sharing the image of the content needs to done, if possible.
+ * content and an image preview the content, if its URL is not null or empty and its
+ * corresponding image is in the image cache.
+ *
*
* @param context the context to use
* @param title the title of the content
@@ -252,13 +267,20 @@ public final class ShareUtils {
shareIntent.putExtra(Intent.EXTRA_SUBJECT, title);
}
- /* TODO: add the image of the content to Android share sheet with setClipData after
- generating a content URI of this image, then use ClipData.newUri(the content resolver,
- null, the content URI) and set the ClipData to the share intent with
- shareIntent.setClipData(generated ClipData).
- if (!imagePreviewUrl.isEmpty()) {
- //shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- }*/
+ // Content preview in the share sheet has been added in Android 10, so it's not needed to
+ // set a content preview which will be never displayed
+ // See https://developer.android.com/training/sharing/send#adding-rich-content-previews
+ // If loading of images has been disabled, don't try to generate a content preview
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
+ && !TextUtils.isEmpty(imagePreviewUrl)
+ && PicassoHelper.getShouldLoadImages()) {
+
+ final ClipData clipData = generateClipDataForImagePreview(context, imagePreviewUrl);
+ if (clipData != null) {
+ shareIntent.setClipData(clipData);
+ shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ }
+ }
openAppChooser(context, shareIntent, false);
}
@@ -266,11 +288,11 @@ public final class ShareUtils {
/**
* Open the android share sheet to share a content.
*
- * For Android 10+ users, a content preview is shown, which includes the title of the shared
- * content.
*
* This calls {@link #shareText(Context, String, String, String)} with an empty string for the
- * imagePreviewUrl parameter.
+ * {@code imagePreviewUrl} parameter. This method should be used when the shared content has no
+ * preview thumbnail.
+ *
*
* @param context the context to use
* @param title the title of the content
@@ -301,4 +323,81 @@ public final class ShareUtils {
clipboardManager.setPrimaryClip(ClipData.newPlainText(null, text));
Toast.makeText(context, R.string.msg_copied, Toast.LENGTH_SHORT).show();
}
+
+ /**
+ * Generate a {@link ClipData} with the image of the content shared, if it's in the app cache.
+ *
+ *
+ * In order not to worry about network issues (timeouts, DNS issues, low connection speed, ...)
+ * when sharing a content, only images in the {@link com.squareup.picasso.LruCache LruCache}
+ * used by the Picasso library inside {@link PicassoHelper} are used as preview images. If the
+ * thumbnail image is not in the cache, no {@link ClipData} will be generated and {@code null}
+ * will be returned.
+ *
+ *
+ *
+ * In order to display the image in the content preview of the Android share sheet, an URI of
+ * the content, accessible and readable by other apps has to be generated, so a new file inside
+ * the application cache will be generated, named {@code android_share_sheet_image_preview.jpg}
+ * (if a file under this name already exists, it will be overwritten). The thumbnail will be
+ * compressed in JPEG format, with a {@code 90} compression level.
+ *
+ *
+ *
+ * Note that if an exception occurs when generating the {@link ClipData}, {@code null} is
+ * returned.
+ *
+ *
+ *
+ * This method will call {@link PicassoHelper#getImageFromCacheIfPresent(String)} to get the
+ * thumbnail of the content in the {@link com.squareup.picasso.LruCache LruCache} used by
+ * the Picasso library inside {@link PicassoHelper}.
+ *
+ *
+ *
+ * Using the result of this method when sharing has only an effect on the system share sheet (if
+ * OEMs didn't change Android system standard behavior) on Android API 29 and higher.
+ *
+ *
+ * @param context the context to use
+ * @param thumbnailUrl the URL of the content thumbnail
+ * @return a {@link ClipData} of the content thumbnail, or {@code null}
+ */
+ @Nullable
+ private static ClipData generateClipDataForImagePreview(
+ @NonNull final Context context,
+ @NonNull final String thumbnailUrl) {
+ try {
+ final Bitmap bitmap = PicassoHelper.getImageFromCacheIfPresent(thumbnailUrl);
+ if (bitmap == null) {
+ return null;
+ }
+
+ // Save the image in memory to the application's cache because we need a URI to the
+ // image to generate a ClipData which will show the share sheet, and so an image file
+ final Context applicationContext = context.getApplicationContext();
+ final String appFolder = applicationContext.getCacheDir().getAbsolutePath();
+ final File thumbnailPreviewFile = new File(appFolder
+ + "/android_share_sheet_image_preview.jpg");
+
+ // Any existing file will be overwritten with FileOutputStream
+ final FileOutputStream fileOutputStream = new FileOutputStream(thumbnailPreviewFile);
+ bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fileOutputStream);
+ fileOutputStream.close();
+
+ final ClipData clipData = ClipData.newUri(applicationContext.getContentResolver(), "",
+ FileProvider.getUriForFile(applicationContext,
+ BuildConfig.APPLICATION_ID + ".provider",
+ thumbnailPreviewFile));
+
+ if (DEBUG) {
+ Log.d(TAG, "ClipData successfully generated for Android share sheet: " + clipData);
+ }
+ return clipData;
+
+ } catch (final Exception e) {
+ Log.w(TAG, "Error when setting preview image for share sheet", e);
+ return null;
+ }
+ }
}
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 90886b63c..e001c6f3f 100644
--- a/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java
+++ b/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java
@@ -6,6 +6,7 @@ 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.DeliveryMethod;
import org.schabi.newpipe.extractor.stream.StreamExtractor;
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
import org.schabi.newpipe.extractor.stream.VideoStream;
@@ -131,31 +132,38 @@ public class DownloadMissionRecover extends Thread {
switch (mRecovery.getKind()) {
case 'a':
- for (AudioStream audio : mExtractor.getAudioStreams()) {
- if (audio.getAverageBitrate() == mRecovery.getDesiredBitrate() && audio.getFormat() == mRecovery.getFormat()) {
- url = audio.getUrl();
+ for (final AudioStream audio : mExtractor.getAudioStreams()) {
+ if (audio.getAverageBitrate() == mRecovery.getDesiredBitrate()
+ && audio.getFormat() == mRecovery.getFormat()
+ && audio.getDeliveryMethod() == DeliveryMethod.PROGRESSIVE_HTTP) {
+ url = audio.getContent();
break;
}
}
break;
case 'v':
- List videoStreams;
+ final 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();
+ for (final VideoStream video : videoStreams) {
+ if (video.getResolution().equals(mRecovery.getDesired())
+ && video.getFormat() == mRecovery.getFormat()
+ && video.getDeliveryMethod() == DeliveryMethod.PROGRESSIVE_HTTP) {
+ url = video.getContent();
break;
}
}
break;
case 's':
- for (SubtitlesStream subtitles : mExtractor.getSubtitles(mRecovery.getFormat())) {
+ for (final SubtitlesStream subtitles : mExtractor.getSubtitles(mRecovery
+ .getFormat())) {
String tag = subtitles.getLanguageTag();
- if (tag.equals(mRecovery.getDesired()) && subtitles.isAutoGenerated() == mRecovery.isDesired2()) {
- url = subtitles.getUrl();
+ if (tag.equals(mRecovery.getDesired())
+ && subtitles.isAutoGenerated() == mRecovery.isDesired2()
+ && subtitles.getDeliveryMethod() == DeliveryMethod.PROGRESSIVE_HTTP) {
+ url = subtitles.getContent();
break;
}
}
diff --git a/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.kt b/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.kt
index 11293a610..c2f9dc9b2 100644
--- a/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.kt
+++ b/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.kt
@@ -11,23 +11,23 @@ import java.io.Serializable
@Parcelize
class MissionRecoveryInfo(
- var format: MediaFormat,
+ var format: MediaFormat?,
var desired: String? = null,
var isDesired2: Boolean = false,
var desiredBitrate: Int = 0,
var kind: Char = Char.MIN_VALUE,
var validateCondition: String? = null
) : Serializable, Parcelable {
- constructor(stream: Stream) : this(format = stream.getFormat()!!) {
+ constructor(stream: Stream) : this(format = stream.format) {
when (stream) {
is AudioStream -> {
- desiredBitrate = stream.averageBitrate
+ desiredBitrate = stream.getAverageBitrate()
isDesired2 = false
kind = 'a'
}
is VideoStream -> {
- desired = stream.resolution
- isDesired2 = stream.isVideoOnly
+ desired = stream.getResolution()
+ isDesired2 = stream.isVideoOnly()
kind = 'v'
}
is SubtitlesStream -> {
@@ -62,7 +62,7 @@ class MissionRecoveryInfo(
}
}
str.append(" format=")
- .append(format.getName())
+ .append(format?.getName())
.append(' ')
.append(info)
.append('}')
diff --git a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java
index 39bdefbe0..961c45bc5 100644
--- a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java
+++ b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java
@@ -29,7 +29,6 @@ import androidx.appcompat.app.AlertDialog;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
-import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.Adapter;
@@ -871,7 +870,7 @@ public class MissionAdapter extends Adapter implements Handler.Callb
super(view);
progress = new ProgressDrawable();
- ViewCompat.setBackground(itemView.findViewById(R.id.item_bkg), progress);
+ itemView.findViewById(R.id.item_bkg).setBackground(progress);
status = itemView.findViewById(R.id.item_status);
name = itemView.findViewById(R.id.item_name);
diff --git a/app/src/main/res/drawable-nodpi/place_holder_bandcamp.png b/app/src/main/res/drawable-nodpi/place_holder_bandcamp.png
deleted file mode 100644
index 13c44b649..000000000
Binary files a/app/src/main/res/drawable-nodpi/place_holder_bandcamp.png and /dev/null differ
diff --git a/app/src/main/res/drawable-nodpi/place_holder_circle.png b/app/src/main/res/drawable-nodpi/place_holder_circle.png
deleted file mode 100644
index 630d0454e..000000000
Binary files a/app/src/main/res/drawable-nodpi/place_holder_circle.png and /dev/null differ
diff --git a/app/src/main/res/drawable-nodpi/place_holder_cloud.png b/app/src/main/res/drawable-nodpi/place_holder_cloud.png
deleted file mode 100644
index c4ba2a6f4..000000000
Binary files a/app/src/main/res/drawable-nodpi/place_holder_cloud.png and /dev/null differ
diff --git a/app/src/main/res/drawable-nodpi/place_holder_gadse.png b/app/src/main/res/drawable-nodpi/place_holder_gadse.png
deleted file mode 100644
index 9b479ed4f..000000000
Binary files a/app/src/main/res/drawable-nodpi/place_holder_gadse.png and /dev/null differ
diff --git a/app/src/main/res/drawable-nodpi/place_holder_peertube.png b/app/src/main/res/drawable-nodpi/place_holder_peertube.png
deleted file mode 100644
index 81dfdb8cc..000000000
Binary files a/app/src/main/res/drawable-nodpi/place_holder_peertube.png and /dev/null differ
diff --git a/app/src/main/res/drawable-nodpi/place_holder_youtube.png b/app/src/main/res/drawable-nodpi/place_holder_youtube.png
deleted file mode 100644
index d147c6643..000000000
Binary files a/app/src/main/res/drawable-nodpi/place_holder_youtube.png and /dev/null differ
diff --git a/app/src/main/res/drawable/ic_circle.xml b/app/src/main/res/drawable/ic_circle.xml
new file mode 100644
index 000000000..dc0a218b8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_circle.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_cloud.xml b/app/src/main/res/drawable/ic_cloud.xml
new file mode 100644
index 000000000..15a682b76
--- /dev/null
+++ b/app/src/main/res/drawable/ic_cloud.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_placeholder_bandcamp.xml b/app/src/main/res/drawable/ic_placeholder_bandcamp.xml
new file mode 100644
index 000000000..411e69854
--- /dev/null
+++ b/app/src/main/res/drawable/ic_placeholder_bandcamp.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_placeholder_media_ccc.xml b/app/src/main/res/drawable/ic_placeholder_media_ccc.xml
new file mode 100644
index 000000000..cdc743cb2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_placeholder_media_ccc.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_placeholder_peertube.xml b/app/src/main/res/drawable/ic_placeholder_peertube.xml
new file mode 100644
index 000000000..263d92d70
--- /dev/null
+++ b/app/src/main/res/drawable/ic_placeholder_peertube.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_smart_display.xml b/app/src/main/res/drawable/ic_smart_display.xml
new file mode 100644
index 000000000..d666a3b37
--- /dev/null
+++ b/app/src/main/res/drawable/ic_smart_display.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/layout/dialog_playback_parameter.xml b/app/src/main/res/layout/dialog_playback_parameter.xml
index 862b2ea67..e402f4fb1 100644
--- a/app/src/main/res/layout/dialog_playback_parameter.xml
+++ b/app/src/main/res/layout/dialog_playback_parameter.xml
@@ -1,5 +1,6 @@
@@ -117,10 +111,8 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
- android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginEnd="4dp"
- android:layout_marginRight="4dp"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
@@ -138,9 +130,9 @@
android:layout_height="1dp"
android:layout_below="@id/tempoControl"
android:layout_marginStart="12dp"
- android:layout_marginTop="6dp"
- android:layout_marginEnd="6dp"
- android:layout_marginBottom="6dp"
+ android:layout_marginTop="5dp"
+ android:layout_marginEnd="12dp"
+ android:layout_marginBottom="5dp"
android:background="?attr/separator_color" />
-
+
+
+ android:layout_marginStart="22dp"
+ android:layout_marginEnd="22dp"
+ android:orientation="horizontal"
+ android:visibility="gone"
+ tools:visibility="visible">
+ android:text="@string/percent"
+ android:textColor="?attr/colorAccent" />
+
+
+
+
+
+
-
-
+ tools:text="-5%" />
+
+
+
+
+
+
+
+
+
+
+
-
-
+ tools:text="+5%" />
-
-
-
-
-
-
-
+ android:orientation="horizontal"
+ tools:visibility="gone">
-
-
-
-
-
-
+ android:layout_height="match_parent"
+ android:layout_marginLeft="4dp"
+ android:layout_marginRight="4dp"
+ android:layout_toStartOf="@+id/pitchSemitoneStepUp"
+ android:layout_toEndOf="@+id/pitchSemitoneStepDown"
+ android:orientation="horizontal">
-
+
+
+
+
+
+
+
+
+
+
+
@@ -403,7 +431,8 @@
android:clickable="true"
android:focusable="true"
android:gravity="center"
- android:textColor="?attr/colorAccent" />
+ android:textColor="?attr/colorAccent"
+ tools:text="1%" />
+ android:textColor="?attr/colorAccent"
+ tools:text="5%" />
+ android:textColor="?attr/colorAccent"
+ tools:text="10%" />
+ android:textColor="?attr/colorAccent"
+ tools:text="25%" />
-
+ android:textColor="?attr/colorAccent"
+ tools:text="100%" />
-
diff --git a/app/src/main/res/layout/download_dialog.xml b/app/src/main/res/layout/download_dialog.xml
index 33e18c64a..37bbf2b03 100644
--- a/app/src/main/res/layout/download_dialog.xml
+++ b/app/src/main/res/layout/download_dialog.xml
@@ -82,13 +82,14 @@
android:text="@string/msg_threads" />
+ android:layout_marginBottom="12dp"
+ android:orientation="horizontal">
+
+
+
diff --git a/app/src/main/res/layout/drawer_header.xml b/app/src/main/res/layout/drawer_header.xml
index d2e936870..94e045863 100644
--- a/app/src/main/res/layout/drawer_header.xml
+++ b/app/src/main/res/layout/drawer_header.xml
@@ -86,7 +86,7 @@
android:scaleType="fitCenter"
app:tint="@color/drawer_header_font_color"
tools:ignore="ContentDescription"
- tools:srcCompat="@drawable/place_holder_youtube" />
+ tools:srcCompat="@drawable/ic_smart_display" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/item_instance.xml b/app/src/main/res/layout/item_instance.xml
index 1a96c5bb2..dd5b4156f 100644
--- a/app/src/main/res/layout/item_instance.xml
+++ b/app/src/main/res/layout/item_instance.xml
@@ -26,7 +26,7 @@
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"
tools:ignore="ContentDescription,RtlHardcoded"
- tools:src="@drawable/place_holder_peertube" />
+ tools:src="@drawable/ic_placeholder_peertube" />
-
+
+ android:layout_height="match_parent"
+ android:fadeScrollbars="false">
+
+
+
+
+
diff --git a/app/src/main/res/layout/subscription_import_export_item.xml b/app/src/main/res/layout/subscription_import_export_item.xml
deleted file mode 100644
index 8aadf5d8c..000000000
--- a/app/src/main/res/layout/subscription_import_export_item.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
-
-
-
diff --git a/app/src/main/res/menu/menu_playlist.xml b/app/src/main/res/menu/menu_playlist.xml
index 8e3ea1559..91ec1bc94 100644
--- a/app/src/main/res/menu/menu_playlist.xml
+++ b/app/src/main/res/menu/menu_playlist.xml
@@ -28,4 +28,10 @@
android:orderInCategory="2"
android:title="@string/open_in_browser"
app:showAsAction="never" />
+
+
diff --git a/app/src/main/res/values-ang/strings.xml b/app/src/main/res/values-ang/strings.xml
new file mode 100644
index 000000000..a6b3daec9
--- /dev/null
+++ b/app/src/main/res/values-ang/strings.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index 3bb304848..4e9606f48 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -23,7 +23,7 @@
فاتح
خطأ في الشبكة
لم يتم العثور على مشغل بث. تثبيت VLC؟
- افتح في المتصفح
+ فتح في المتصفح
الصوت
تشغيل بواسطة كودي
البحث
@@ -730,11 +730,8 @@
إظهار خطأ snackbar
لم يتم العثور على مدير ملفات مناسب لهذا الإجراء.
\nالرجاء تثبيت مدير ملفات متوافق مع Storage Access Framework.
- يتم تشغيله في الخلفية
تعليق مثبت
LeakCanary غير متوفر
- ضبط الصوت من خلال النغمات الموسيقية النصفية
- خطوة الإيقاع
الافتراضي ExoPlayer
تغيير حجم الفاصل الزمني للتحميل (حاليا %s). قد تؤدي القيمة الأقل إلى تسريع تحميل الفيديو الأولي. تتطلب التغييرات إعادة تشغيل المشغل.
تكوين إشعار مشغل البث الحالي
@@ -763,4 +760,14 @@
- %s دفق جديد
- %s دفق جديد
+ النسبة المئوية
+ سيميتون
+ لا يتم عرض التدفقات التي لم يدعمها برنامج التنزيل بعد
+ الدفق المحدد غير مدعوم من قبل المشغلون الخارجيون
+ لا توجد تدفقات صوتية متاحة للمشغلات الخارجية
+ لا تتوفر تدفقات فيديو للاعبين الخارجيين
+ حدد الجودة للمشغلين الخارجيين
+ تنسيق غير معروف
+ جودة غير معروفة
+ حجم الفاصل الزمني لتحميل التشغيل
\ No newline at end of file
diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml
index 51683fb89..95b7b76f4 100644
--- a/app/src/main/res/values-az/strings.xml
+++ b/app/src/main/res/values-az/strings.xml
@@ -2,145 +2,145 @@
Başlamaq üçün \"Axtarış\" bölməsinə toxunun.
%1$s tarixində yayımlanıb
- Axın pleyeri tapılmadı. \"VLC\" yüklənilsin\?
- Axın pleyeri tapılmadı (baxmaq üçün \"VLC\"ni yükləyə bilərsiniz).
- Quraşdır
+ Yayım oynadıcı tapılmadı. \"VLC\" yüklənilsin\?
+ Yayım oynadıcı tapılmadı (baxmaq üçün \"VLC\"\'ni yükləyə bilərsiniz).
+ Yükləyin
İmtina
- Brauzerdə aç
- Paylaş
- Yüklə
- Axın faylını endirin
- Axtarış
- Ayarlar
+ Brauzerdə açın
+ Paylaşın
+ Endirin
+ Yayım faylını endirin
+ Axtarın
+ Tənzimləmələr
Bunu demək istədiniz: \"%1$s\"\?
- Paylaş
- Kənar video oynadıcı istifadə et
- Bəzi görüntü keyfiyyətlərində səs itir
- Kənar audio oynadıcı istifadə et
- Abunə ol
+ ...ilə paylaşın
+ Xarici video oynadıcı istifadə edin
+ Bəzi keyfiyyət seçimlərində səsi silir
+ Xarici səs oynadıcı istifadə edin
+ Abunə Olun
Abunə olundu
Kanal abunəliyindən çıxıldı
Məlumat göstər
Abunəliklər
- Saxlanmış Oxutma Siyahıları
- Yeni nə var
- Fon
- Video yükləmə qovluğu
- Yüklənmiş videolar burada saxlanılır
- Video faylları üçün yükləmə qovluğunu seçin
- Audio yükləmə qovluğu
- Yüklənmiş audio faylları burada saxlanılır
- Audio faylları üçün yükləmə qovluğunu seçin
- Standard görüntü keyfiyyəti
- Daha böyük ölçüləri göstər
- \"Kodi\" ilə oxut
+ Əlfəcinlənmiş Pleylistlər
+ Yeniliklər
+ Arxa Fon
+ Video endirmə qovluğu
+ Endirilmiş video fayllar burada saxlanılır
+ Video faylları üçün endirmə qovluğunu seçin
+ Səs endirmə qovluğu
+ Endirilmiş səs faylları burada saxlanılır
+ Səs faylları üçün endirmə qovluğunu seçin
+ Defolt keyfiyyət
+ Daha böyük keyfiyyət seçimləri göstər
+ \"Kodi\" ilə Oynat
Çatışmayan \"Kore\" tətbiqi yüklənilsin\?
- \"Kodi ilə oxut\" seçimini göstər
- Videonu Kodi media center ilə oynatmaq üçün seçim göstər
- Audio
- Standart səs formatı
- Standart video formatı
+ \"Kodi ilə Oynat\" seçimini göstər
+ Videonu Kodi media mərkəzi ilə oynatmaq üçün seçim göstər
+ Səs
+ Defolt səs formatı
+ Defolt video formatı
Mövzu
- Açıq
+ İşıqlı
Qaranlıq
Qara
- Abunəlikdən çıx
- Ani pəncərə rejimində aç
- Avto-oxutma
- Yüklə
- Kəsintilərdən sonra (məs. telefon zəngi) oxutmağa davam et
- Oxutmağa davam et
- İzlənmiş videoları qeyd et
- Verilənləri təmizlə
- Oxutma siyahılarındakı oxutma mövqeyi göstəricisini nümayiş et
+ Abunəlikdən çıxın
+ Ani pəncərə rejimində açın
+ Avto-oynatma
+ Endirin
+ Fasilələrdən sonra (məsələn, telefon zəngləri) oynatmağa davam etdirin
+ Oynatmanı davam etdir
+ Baxılmış videoların saxlanılması
+ Məlumat təmizləmə
+ Siyahılarda oynatma mövqelərini göstərin
Siyahılardakı mövqelər
- Video oxutmanı axırıncı qaldığı yerdən bərpa et
- Oxutmanı davam etdir
- İzləmə tarixçəsi
- Axtarış sorğularını lokal olaraq saxlayın
+ Son oynatma mövqeyinə qaytarın
+ Oynatmanı davam etdir
+ Baxış tarixçəsi
+ Axtarış sorğularını yerli olaraq saxlayın
Axtarış tarixçəsi
Axtarış edərkən göstəriləcək təklifləri seçin
Axtarış təklifləri
- Pleyerin parlaqlığını idarə etmək üçün jestlərdən istifadə edin
- Parlaqlığın jestlə idarə edilməsi
- Pleyerin səsini idarə etmək üçün jestlərdən istifadə edin
- Səsin jestlə idarə edilməsi
+ Oynadıcının parlaqlığını nizamlamaq üçün jestlərdən istifadə edin
+ Parlaqlığı jestlə nizamlama
+ Oynadıcının səsini nizamlamaq üçün jestlərdən istifadə edin
+ Səsi jestlə nizamlama
Avto-növbələmə
- Növbəti axını avtomatik olaraq növbəyə əlavə et
- Metadata keşi silindi
- Keşlənmiş bütün veb-səhifə verilənlərini sil
- Keşlənmiş metadatanı təmizlə
+ Növbəti Yayımı Avto-növbələmə
+ Üst məlumat keşi silindi
+ Keşlənmiş bütün veb-səhifə məlumatlarını silin
+ Keşlənmiş üst məlumatı təmizləyin
Şəkil keşi silindi
Şərhləri gizlətmək üçün söndürün
Şərhləri göstər
- Aktiv pleyerin növbəsi dəyişdiriləcək
- Bir pleyerdən digərinə keçmək növbənizi dəyişdirə bilər
- Növbəni təmizləmədən öncə təsdiq üçün soruş
- Qeyri-dəqiq axtarış (videonu irəli/geri çəkmə) istifadə edin
- Qeyri-dəqiq axtarış oynadıcıya azaldılmış həssaslıqla mövqeləri daha sürətlə axtarmağa imkan verir. 5, 15 və ya 25 saniyəlik axtarış bununla işləmir
- Cəld irəli/geri çəkmə müddəti
+ Aktiv oynadıcının növbəsi dəyişdiriləcək
+ Bir oynadıcıdan digərinə keçid növbənizi dəyişdirə bilər
+ Növbəni təmizləməzdən əvvəl təsdiq üçün soruş
+ Sürətli qeyri-dəqiq axtarışdan istifadə edin
+ Qeyri-dəqiq axtarış oynadıcıya azaldılmış dəqiqliklə mövqeləri daha sürətli axtarmağa imkan verir. 5, 15 və ya 25 saniyəlik axtarış bununla işləmir
+ Sürətli irəli/geri çəkmə axtarış müddəti
Heç nə
- Buferizasiya olunur
+ Buferlənir
Qarışdır
Təkrarla
- Beşinci hərəkət düyməsi
- Dördüncü hərəkət düyməsi
- Üçüncü hərəkət düyməsi
- İkinci hərəkət düyməsi
- Birinci hərəkət düyməsi
- Yalnız bəzi cihazlar 2K/4K videoları oxuda bilir
- Ani pəncərədə standart görüntü keyfiyyəti
- Əlavə et
- Ani pəncərə (popup)
- Tab-vərəqəni Seçin
- Abunəlik yenilənmədi
- Abunəlik dəyişdirilmədi
+ Beşinci fəaliyyət düyməsi
+ Dördüncü fəaliyyət düyməsi
+ Üçüncü fəaliyyət düyməsi
+ İkinci fəaliyyət düyməsi
+ Birinci fəaliyyət düyməsi
+ Yalnız bəzi cihazlar 2K/4K videoları oynada bilir
+ Defolt ani pəncərə keyfiyyəti
+ Əlavə Edin
+ Ani Pəncərə
+ Tabı Seçin
+ Abunəliyi yeniləmək alınmadı
+ Abunəliyi dəyişdirmək alınmadı
Nəticələr göstərilir: %s
Kanallar
%s tərəfindən
- \"Youtube\"un \"Məhdudiyyətli Rejimi\"ni aktivləşdir
- Yaş limiti olduğuna görə (məs. 18+) böyük ehtimal uşaqlar üçün uyğun olmayan məzmunu göstər
+ YouTube\'un \"Məhdud Rejimi\"ni açın
+ Yaş həddi səbəbiylə (məsələn, 18+) uşaqlar üçün uyğun olmayan məzmunu göstərin
Yaş məhdudiyyətli məzmunu göstər
Məzmun
- Ani pəncərədə oxudulur
- Fonda oxudulur
+ Ani pəncərədə oynadılır
+ Arxa fonda oynadılır
Yeniləmələr
Sazlama
Görünüş
- Tarix və keş
+ Tarixçə və keş
Video və səs
Davranış
- Pleyer
- İlkin məzmun dili
- Məzmun üçün ilkin ölkə
- URL tanınmadı. Başqa bir tətbiq ilə açılsın\?
- Dəstəklənməyən URL
- \"Əlavə etmək üçün basılı tutun\" məsləhətini göstər
+ Oynadıcı
+ Defolt məzmun dili
+ Defolt məzmun ölkəsi
+ URL\'i tanımaq olmadı. Başqa tətbiqlə açılsın\?
+ Dəstəklənməyən URL\'i
+ \"Növbəyə əlavə etmək üçün basılı saxla\" ipucusun göstər
\"Növbəti\" və \"Bənzər\" videoları göstər
- Tarixçəni, abunəlikləri və oxutma siyahılarını ixrac et
- "Cari tarixçə, abunəliklər, pleylistlər və (istəyə görə) ayarlarınızın üzərinə yazır"
- reCAPTCHA çərəzləri təmizləndi
- reCAPTCHA çərəzlərini təmizlə
- Məlumat bazasını ixrac et
- Məlumat bazasını idxal et
- Əsas Görünüşə Keç
- Ani Pəncərəyə Keç
- Fona Keç
- [Bilinməyən]
+ Tarixçəni, abunəlikləri, pleylistləri və tənzimləmələri ixrac edin
+ Cari tarixçənizi, abunəliklərinizi, pleylistlərinizi və (istəyə görə) tənzimləmələrinizi etibarsız edir
+ reCAPTCHA kukiləri təmizləndi
+ reCAPTCHA kukilərini təmizləyin
+ Məlumat bazasını ixrac edin
+ Məlumat bazasını idxal edin
+ Əsas Görünüşə Keçid
+ Ani Pəncərəyə Keçid
+ Arxa Fona Keçid
+ [Naməlum]
Yeni \"NewPipe\" versiyası üçün bildirişlər
- Tətbiq Yeniləmə Bildirişi
- Fon və ani pəncərə pleyerləri üçün \"NewPipe\" bildirişləri
+ Tətbiq yeniləmə bildirişi
+ NewPipe oynadıcısı üçün bildirişlər
Hamısı
Xəta hesabatı
- Yükləmələr
- Yükləmələr
+ Endirmələr
+ Endirmələr
Canlı
Bu video yaş məhdudiyyətlidir.
\n
-\nGörmək istəyirsinizsə, ayarlarda \"%1$s\" özəlliyini yandırın.
- \"YouTube\" böyüklər üçün potensial məzmunu gizləyən \"Məhdud Rejim\" təmin edir.
- \"PeerTube\" nümunələri
- Kiçik təsvirləri yüklə
+\nOnu görmək istəyirsinizsə, tənzimləmələrdə \"%1$s\" seçimini aktivləşdirin.
+ \"YouTube\" böyüklər üçün potensial məzmunu gizləyən \"Məhdud Rejim\" təmin edir
+ \"PeerTube\" serverləri
+ Miniatürləri yükləyin
Siz yığcam bildirişdə göstərilməsi üçün ən çoxu üç fəaliyyət seçə bilərsiniz!
Həmişə yenilə
Axın
@@ -152,75 +152,75 @@
- %d seçildi
- %d seçildi
- Abunəlik seçilmədi
+ Abunəlik seçilməyib
Abunəlikləri seçin
Axın emal edilir…
Axın yüklənir…
Yüklənmədi: %d
- Oxutma siyahısı səhifəsi
- \"Newpipe\" Bildirişi
+ Pleylist səhifəsi
+ \"Newpipe\" bildirişi
Fayl
Yalnız Bir Dəfə
Həmişə
- Hamısını Oxut
+ Hamısını Oynat
Fayl silindi
Geri qaytar
- Ən yaxşı görüntü keyfiyyəti
- Təmizlə
- Deaktiv edilib
- İfaçılar
+ Ən yaxşı keyfiyyət
+ Təmizləyin
+ Qeyri-aktivdir
+ Rəssamlar
Albomlar
Mahnılar
Hadisələr
İstifadəçilər
Treklər
Videolar
- Oxutma siyahıları
+ Pleylistlər
Xəta
Kömək
- Axtarış tarixçəsi silindi.
+ Axtarış tarixçəsi silindi
Bütün axtarış tarixçəsi silinsin\?
- Axtarışda işlədilmiş açar sözlərin tarixçəsini siləcək
- Axtarış tarixçəsini təmizlə
- Oxutma mövqeləri silindi.
- Bütün oxutma mövqeləri silinsin\?
- Bütün oxutma mövqelərini siləcək
- Oxutma mövqelərini sil
- Baxış tarixçəsi silindi.
+ Axtarışdakı açar sözlərin tarixçəsini silir
+ Axtarış tarixçəsini silin
+ Oynatma mövqeləri silindi
+ Bütün oynatma mövqeləri silinsin\?
+ Bütün oynatma mövqelərini siləcək
+ Oynatma mövqelərini silin
+ Baxış tarixçəsi silindi
Bütün baxış tarixçəsi silinsin\?
Baxış tarixçəsini təmizlə
- \"ReCAPTCHA\" həll edilərkən \"NewPipe\"ın saxladığı çərəzləri təmizlə
+ reCAPTCHA həll edərkən NewPipe\'ın saxladığı kukiləri silin
%s tərəfindən yaradıldı
Yaxınlaşdır
Doldur
Dart
- Altyazı yoxdur
- Sil
- Hələ ki kanal abunəliyi yoxdur
+ Altyazı Yoxdur
+ Silin
+ Hələ ki, kanal abunəliyi yoxdur
Kanal seçin
Kanal Səhifəsi
İlkin Köşk
Köşk Səhifəsi
Boş Səhifə
- Ana səhifədə hansı tab-vərəqələr göstərilir
+ Ana səhifədə hansı tablar göstərilir
Ana səhifənin məzmunu
- Yeni versiya mövcud olanda tətbiqi yeniləməyi xatırlatmaq üçün bildiriş göstər
+ Yeni versiya mövcud olduqda tətbiq yeniləməsini xatırlatmaq üçün bildiriş göstər
Yeniləmələr
Mobil internet istifadə edərkən görüntü keyfiyyətini məhdudlaşdırın
Limitsiz
1 element silindi.
- Nümunə əlavə et
- Sevimli \"PeerTube\" nümunələrinizi seçin
- Yüklənmiş faylları sil
- Yükləmə tarixçənizi təmizləmək və ya yüklənmiş bütün faylları silmək istəyirsiniz\?
- Yükləmə tarixçəsini təmizlə
- Yükləmələrə başla
- Yükləmələrə fasilə ver
- Haraya yüklənəcəyini soruş
- Hər yükləmədə harada saxlanılacağı soruşulacaq
- \'Saxlanca Müraciət Çərçivəsi\' xarici SD karta yükləməyə imkan verir.
-\nBəzi cihazlar uyğun deyil
- Sistem öncülü
+ Server əlavə edin
+ Sevimli \"PeerTube\" serverlərinizi seçin
+ Endirilmiş faylları silin
+ Endirmə tarixçənizi təmizləmək və ya endirilmiş bütün faylları silmək istəyirsiniz\?
+ Endirmə tarixçəsini təmizlə
+ Endirmələrə başla
+ Endirmələrə fasilə verin
+ Haraya endiriləcəyini soruş
+ Sizdən hər endirmənin harada saxlanacağı soruşulacaq.
+\nXarici SD karta yükləmək istəyirsinizsə, sistem qovluğu seçicisini (SAF) aktiv edin
+ \'Yaddaş Giriş Çərçivəsi \' xarici SD karta endirməyə imkan verir
+ Sistem defoltu
Tətbiq dili
- %d gün
@@ -238,11 +238,11 @@
- %d saniyə
- %d saniyə
- Axın sonuncu dəfə güncəlləndi: %s
- Axın güncəlləmə astanası
- Sürətli rejimi aktiv et
+ Axın sonuncu dəfə yeniləndi: %s
+ Axın yeniləmə astanası
+ Sürətli rejimi aktivləşdir
Sürətli rejimi deaktiv et
- Axının çox yavaş yükləndiyini düşünürsünüz\? Əgər elədirsə, sürətli yükləməni işə salmağı sınayın (ayarlardan dəyişə və ya aşağıdakı düyməni basa bilərsiniz).
+ Axının çox yavaş yükləndiyini düşünürsünüz\? Əgər elədirsə, sürətli yükləməni işə salmağı sınayın (tənzimləmələrdən dəyişə və ya aşağıdakı düyməni basa bilərsiniz).
\n
\nNewPipe axını yükləmək üçün 2 metod təklif edir:
\n• Bütün abunəlik kanallarını gətirtmək, bu yavaş olsa da tamdır;
@@ -250,46 +250,472 @@
\n
\nBu ikisi arasında fərq odur ki, sürətlisində, adətən elementin müddəti və növü kimi bəzi məlumatlar çatışmır (canlı video ilə adisini ayırd edə bilmir) və daha az element gətirir.
\n
-\nYouTube öz RSS axını ilə bu sürətli metodu təklif edən xidmətlərdən biridir.
+\nYouTube öz RSS axını ilə bu sürətli metodu təklif edən xidmətlərdən biridir.
\n
\nBeləliklə, seçim sizin nəyə üstünlük verməyinizdən asılıdır: sürət yoxsa dəqiq məlumat.
- Bu axını oxutmaq olmadı
+ Bu yayımı oynatmaq alınmadı
Tətbiq/UI çökdü
- Yükləmə menyusu qurulmadı
+ Endirmə menyusunu qurmaq mümkün olmadı
Məzmun əlçatmazdır
- Kiçik təsvirlərin hamısı yüklənmədi
+ Bütün miniatürləri yükləmək alınmadı
Şəbəkə xətası
- Xarici SD karta yükləmək mümkün deyil. Yükləmə qovluğu üçün təyin edilmiş yer sıfırlansın\?
- Xarici saxlanc əlçatmazdır
- Oxutma axının tarixçəsini və oxutma mövqelərini siləcək
- Üst bilgini göstər
- Video təsvirini və əlavə bilgiləri gizlətmək üçün söndürün
- Təsviri göstər
- Bildirişləri rənglə
+ Xarici SD karta endirmək mümkün deyil. Endirmə qovluğunun yeri sıfırlansın\?
+ Xarici yaddaş əlçatan deyil
+ Oynadılmış yayımların tarixçəsini və oynatma mövqelərini silir
+ Üst məlumatı göstər
+ Video açıqlamasını və əlavə məlumatı gizlətmək üçün söndürün
+ Açıqlamanı göstər
+ Bildirişi rəngləyin
Belə qovluq yoxdur
- Əsas oynadıcını (pleyeri) tam ekranda başlat
+ Əsas oynadıcını tam ekranda başlat
Xarici oynadıcılar bu cür linkləri dəstəkləmir
Yerli axtarış təklifləri
Video
- Əlaqəli videolar
- İzlənmiş kimi işarələ
- Aşağıdakılardan biri ilə aç
+ Əlaqədar yayımlar
+ Baxılmış kimi işarələ
+ ...ilə açın
Gecə Mövzusu
- Ani açılan pəncərə (popup) xüsusiyyətlərini xatırla
- Ani açılan pəncərənin son ölçüsü və mövqeyini xatırla
+ Ani pəncərə xüsusiyyətlərini xatırla
+ Ani pəncərənin son ölçüsü və mövqeyini xatırla
Video yayımı tapılmadı
Şərhlər
- Təsvir
-
+ Açıqlama
+ Burada kriketlərdən başqa heç nə yoxdur
Nəticə yoxdur
- İlkin ayarları qaytar
+ İlkin tənzimləmələri qaytarın
Fayl köçürüldü və ya silindi
- Oynadıcı xətası düzəldilir
- Bərpa oluna bilməyən oynadıcı xətası baş verdi
+ Oynadıcı xətası bərpa edilir
+ Bərpa olunmayan oynatma xətası baş verdi
Oldu
- Bu video yaş məhdudiyyətlidir.
-\n\"YouTube\"un yeni yaş məhdudiyyətli videolar siyasətinə görə \"NewPipe\" bu cür videoların yayımını əldə edə bilmir, beləliklə, videonu oynatmaq mümkün deyil.
+ Bu video yaş məhdudiyyətidir.
+\nYaş məhdudiyyəti olan videolarla bağlı yeni YouTube siyasətlərinə görə, NewPipe bu cür video yayımlara daxil ola və oynada bilməz.
Səs yayımı tapılmadı
- Başqa proqramların üzərində göstərmə icazəsi ver
- İlkin ayarları qaytarmaq istəyirsiniz\?
+ Digər tətbiqlərin üzərində göstərməyə icazə verin
+ İlkin tənzimləmələri qaytarmaq istəyirsiniz\?
+ Miniatürlərin yüklənməsini, dataya qənaət etmək və yaddaşdan istifadəni azaltmaq üçün söndürün. Dəyişikliklər həm yaddaşdaxili, həm də diskdə olan təsvir keşini təmizləyir
+ Növbətini sıraya salın
+ Yenidən Cəhd Edin
+ Cari oynatma yayımı bildirişini konfiqurasiya edin
+ Bildirişlər
+ Video fayl xülasəsi bildirişi
+ Abunəliklər üçün yeni yayımlar haqqında bildirişlər
+ Xəta hesabatları üçün bildirişlər
+ Fayl adı boş ola bilməz
+ Yadda saxlanmış tabları oxumaq mümkün olmadı, buna görə defolt tablardan istifadə edin
+ NewPipe xəta ilə qarşılaşdı, bildirmək üçün toxunun
+ Bağışlayın, belə olmamalı idi.
+ Bu xətanı e-poçt vasitəsilə bildirin
+ GitHub\'da Hesabat Verin
+ Zəhmət olmasa, xətanızı müzakirə edən məsələnin mövcud olub-olmadığını yoxlayın. Dublikat biletləri yaradarkən, bizdən faktiki səhvi düzəltməyə sərf edəcəyimiz vaxt alırsınız.
+ Hesabat Bildirin
+ Məlumat:
+ Nə baş verdi:
+ Yükləyənin avatar miniatürü
+ Bəyəni
+ Bəyənməmə
+ Yenidən sıralamaq üçün sürüşdürün
+ m
+ M
+ M
+ Xidməti dəyişin,hazırda seçilmiş:
+ Abunəçi yoxdur
+ Baxış yoxdur
+ Heç kim izləmir
+ Heç kim dinləmir
+ Video yoxdur
+ Şərhlər qeyri-aktivdir
+ Başladın
+ Dayandırın
+ Təsdiqləmə
+ İmtina
+ Xəta
+ Detallar üçün toxunun
+ Zəhmət olmasa, gözləyin…
+ Hələ endirmə qovluğu təyin edilməyib, indi defolt endirmə qovluğunu seçin
+ reCAPTCHA çağırışı
+ reCAPTCHA sorğusu göndərildi
+ Bitdi
+ Etibarsız simvollar bu dəyərlə əvəz olunur
+ Əvəzedici xarakter
+ Ən xüsusi simvollar
+ Üçüncü Tərəf Lisenziyaları
+ Haqqında
+ Töhfə Verin
+ Fikirlərinizin olub-olmaması, tərcümə, dizayn dəyişiklikləri, kodun təmizlənməsi və ya real ağırlıqlı kod dəyişiklikləri və.s kömək həmişə xoşdur. Nə qədər çox edilsə, bir o qədər yaxşı olar!
+ İanə Edin
+ Veb sayt
+ Əlavə məlumat və xəbərlər üçün NewPipe Veb saytına daxil olun.
+ NewPipe\'ın Məxfilik Siyasəti
+ NewPipe layihəsi məxfiliyinizə çox ciddi yanaşır. Buna görə də, tətbiq sizin razılığınız olmadan heç bir məlumat toplamır.
+\nNewPipe\'ın məxfilik siyasəti qəza hesabatı göndərdiyiniz zaman hansı məlumatların göndərildiyini və saxlandığını ətraflı izah edir.
+ Məxfilik siyasətini oxuyun
+ NewPipe\'ın Lisenziyası
+ Tarixçə
+ Bu elementi axtarış tarixçəsindən silmək istəyirsiniz\?
+ Son Oynadılan
+ Ən çox oynadılan
+ Köşk seçin
+ İdxal edildi
+ Etibarlı ZIP faylı yoxdur
+ Xəbərdarlıq: Bütün faylları idxal etmək mümkün olmadı.
+ Tənzimləmələri də idxal etmək istəyirsiniz\?
+ Tətbiq yenidən başladıldıqdan sonra dil dəyişəcəkdir
+ Ən yaxşı 50
+ Yeni və populyar
+ Yerli
+ Son əlavə edilən
+ Konfranslar
+ Oynatma növbəsi
+ Detallar
+ Kanal təfərrüatlarını göstərin
+ Ani pəncərədə oynatmağa başlayın
+ \"Açıq\" fəaliyyətə üstünlük verilir
+ Arxa Fon oynadıcı
+ Həmişə soruşun
+ Tələb olunan məzmun yüklənir
+ Yeni Pleylist
+ Adını dəyişdirin
+ Pleylistə əlavə edin
+ Emal edilir... Bir az vaxt ala bilər
+ Səsi aç
+ Pleylisti Əlfəcinlə
+ Əlfəcini Silin
+ Bu pleylist silinsin\?
+ Pleylist yaradıldı
+ Pleylist miniatürü dəyişdirildi.
+ Avtomatik yaradıldı (heç bir yükləyici tapılmadı)
+ Avtomatik yaradıldı
+ Altyazılar
+ LeakCanary yoxdur
+ Yaddaş sızmasının monitorinqi yığın boşaltma zamanı tətbiqin cavab verməməsinə səbəb ola bilər
+ Yaddaş sızmalarını göstərin
+ Utilizasiyadan sonra fraqment və ya fəaliyyətin yaşam dövründən kənarda çatdırıla bilməyən Rx istisnaları barədə hesabat verməyə məcbur edin
+ Xidmətlərdən alınmış orijinal mətnlər yayım elementlərində görünəcək
+ Yeni yayımları yoxlayın
+ URL və ya ID\'nizi daxil etməklə SoundCloud profilini idxal edin:
+\n
+\n1. Veb-brauzerdə \"iş masası rejimini\" aktiv edin (sayt mobil cihazlar üçün mövcud deyil)
+\n2. Bu URL\'ə keçin: %1$s
+\n3. Soruşulduqda daxil olun
+\n4. Yönləndirildiyiniz profilin URL\'sini kopyalayın.
+ ID\'niz, soundcloud.com/ID\'niz
+ Unutmayın ki, bu əməliyyat şəbəkəyə ağır yük ola bilər.
+\n
+\nDavam etmək istəyirsiniz\?
+ Sükut zamanı sürətlə irəlilə
+ Yeni yayım bildirişləri
+ Abunəliklərdən yeni yayımlar haqqında bildiriş göndər
+ Tezliyin yoxlanılması
+ Tələb olunan şəbəkə bağlantısı
+ İstənilən şəbəkə
+ Tətbiq keçidində kiçildin
+ Arxa Fon oynadıcısına kiçildin
+ Ani-pəncərə oynadıcısına kiçildin
+ Oynatmağa avtomatik başlayın — %s
+ Aşağı keyfiyyətli (daha kiçik)
+ Göstərməyin
+ Endirmə uğursuz oldu
+ Server çox iş parçalı endirmələri qəbul etmir, @string/msg_threads = 1 ilə yenidən cəhd edin
+ Bütün endirilmiş fayllar diskdən silinsin\?
+ Maksimum təkrar cəhdlər
+ Pleylistə əlavə olunandan əvvəl və sonra baxılmış videolar silinəcək.
+\nSiz əminsiniz\? Bu geri qaytarıla bilməz!
+ Kanal qrupları
+ Yeni axın elementləri
+ Abunəlik köhnəlmiş hesab edilənə qədərki son yeniləmədən sonrakı vaxt — %s
+ Axın yükləmə xətası
+ Bu məzmun hələ NewPipe tərəfindən dəstəklənmir.
+\n
+\nÜmid edirik ki, gələcək versiyada dəstəklənəcək.
+ Həm kilid ekranı fonu, həm də bildirişlər üçün miniatürdən istifadə edin
+ Ən Yeni
+ Bu məzmun ölkənizdə mövcud deyil.
+ Bu məzmun yalnız ödəniş etmiş istifadəçilər üçün əlçatandır, ona görə də NewPipe tərəfindən yayımlana və ya endirilə bilməz.
+ Avtomatik (cihaz mövzusu)
+ Sevimli gecə mövzusunu seçin — %s
+ Sabitlənmiş şərh
+ Bildirişlər deaktiv edilib
+ Bildiriş alın
+ Artıq bu kanala abunə oldunuz
+ ,
+ Hamısını dəyişdirin
+ Fayl adı
+ Həll edin
+ Abunəlikləri ixrac etmək mümkün olmadı
+
+ - %s izləyici
+ - %s izləyici
+
+ Yeni versiyaları əlinizlə yoxlayın
+
+ - %s dinləyici
+ - %s dinləyici
+
+
+ - %s video
+ - %s video
+
+ Yeniləmələri yoxlayın
+ Axtarış çubuğunun miniatür önizləməsi
+ Əməliyyat sistem tərəfindən ləğv edildi
+ Avto
+ Tapılmadı
+ Server məlumat göndərmir
+ Bu endirməni bərpa etmək mümkün deyil
+ Sizdən hər endirmənin harada saxlanacağı soruşulacaq
+ \'Yaddaş Giriş Çərçivəsi\' Android KitKat və ondan aşağı versiyalarda dəstəklənmir
+ \"Yaddaş Giriş Çərçivəsi\"yalnız Android 10\'dan başlayaraq dəstəklənir
+ Kanalın avatar miniatürü
+ Sevdiyiniz gecə mövzusunu aşağıda seçə bilərsiniz
+ Android\'in bildiriş rəngini miniatürdəki əsas rəngə uyğun fərdiləşdirməsini təmin edin(qeyd edək ki, bu, bütün cihazlarda mövcud deyil)
+ GitHub\'da Baxın
+ İanə Edin
+ NewPipe, sizə ən yaxşı istifadəçi təcrübəsini göstərmək üçün boş vaxtlarını sərf edən könüllülər tərəfindən hazırlanmışdır. Tərtibatçılara bir fincan qəhvə içərkən NewPipe\'ı daha da yaxşılaşdırmağa kömək etmək üçün kömək edin.
+ Ən çox bəyənildi
+ Növbəyə salındı
+ Məzmunu açarkən defolt hərəkət — %s
+ Ad
+ Pleylist miniatürü kimi təyin edin
+ Yalnız Wi-Fi\'da
+ Heç vaxt
+ Siyahı görünüş rejimi
+ Siyahı
+ Tor
+ Yüksək keyfiyyətli (daha böyük)
+ Gözlənilir
+ növbədə
+ son proseslər tətbiq olunur
+ Yeniləmələr yoxlanılır…
+ NewPipe yeniləməsi mövcuddur!
+ Lisenziya
+ Müəllifin hesabı bağlanıb.
+\nNewPipe gələcəkdə bu axını yükləyə bilməyəcək.
+\nBu kanala abunəlikdən çıxmaq istəyirsiniz\?
+ Baxılmış elementləri göstərin
+ Seçilmiş
+ Çəkməcəni Bağlayın
+ Video oynadıcı
+ Video fayl xülasəsi prosesi üçün bildirişlər
+ Açın
+ Kiçik şəkili 1:1 aspekt nisbətinə ölçün
+ Yükləmə intervalının həcmini dəyişdirin (hazırda %s). Daha aşağı dəyər ilkin video yükləməni sürətləndirə bilər. Dəyişikliklər oynadıcının yenidən başladılmasını tələb edir.
+ Yayım yaradıcısı, məzmunu və ya axtarış sorğusu haqqında əlavə məlumat olan üst məlumat qutularını gizlətmək üçün söndürün
+ Əlaqədar yayımı əlavə etməklə (təkrar etməyən) sonlanacaq oynatma sırasını davam etdir
+ Kənar axtarış təklifləri
+ Server artıq mövcuddur
+ Videoları mini oynadıcıda başlatma, avtomatik fırlatma kilidlidirsə, birbaşa tam ekran rejiminə keçid. Siz hələ də tam ekran rejimindən çıxmaqla mini pleyerə daxil ola bilərsiniz
+ 100+ video
+ ∞ video
+ Şərhlər yoxdur
+ Həll edildikdə \"Bitdi\" düyməsini basın
+ Pleylist seçin
+ Şərhləri yükləmək mümkün olmadı
+ Populyar
+ Səs Tənzimləmələri
+ Məlumat əldə edilir…
+ Elementlərdə əvvəlki vaxtı göstərin
+ Yaşam dövrəsi xaricindəki xətaları bildirin
+ Şəkil göstəricilərini göstərin
+ Şəkillərin üzərində mənbəsini göstərən Pikasso rəngli lentləri göstərin: şəbəkə üçün qırmızı, disk üçün mavi və yaddaş üçün yaşıl
+ Bəzi endirmələri dayandırmaq mümkün olmasa da, mobil dataya keçərkən faydalıdır
+ Bağla
+ Fayl silindiyi üçün irəliləyiş itirildi
+ Endirməni ləğv etməzdən əvvəl ümumi cəhdlərin sayı
+ Ölçülmüş şəbəkələrdə dayandır
+ Endirmə növbəsini məhdudlaşdırın
+ Eyni vaxtda ancaq bir endirmə həyata keçiriləcək
+ Hesab ləğv edildi
+ %s bu səbəbi təmin edir:
+ Endirmə başladı
+ Açıqlamadakı mətni seçməyi deaktiv edin
+ Kateqoriya
+ Daxili
+ Açıqlamadakı mətni seçməyi aktivləşdirin
+ Teqlər
+ Planşet rejimi
+ Bağlayın
+ Yaradıcısından ürəkləndi
+ Veb saytı açın
+
+ - %s baxış
+ - %s baxış
+
+ Addım
+
+ - %s yeni yayım
+ - %s yeni yayım
+
+ Sıfırlayın
+ Faiz
+ Yarımton
+
+ - Endirmə tamamlandı
+ - %s endirmələr tamamlandı
+
+ Defolt ExoPlayer
+ Mövcud olduqda xüsusi axından alın
+ Baxılmış videolar silinsin\?
+ İzlənilmişi silin
+ Sistem qovluğu seçicisini (SAF) istifadə edin
+ Bağlantı fasiləsi
+ Cihazda yer qalmayıb
+ Fayl üzərində işləyərkən NewPipe bağlandı
+ Emaldan sonra uğursuz oldu
+ Serverə qoşulmaq mümkün deyil
+ Serveri tapmaq olmadı
+ Təhlükəsiz əlaqə qurmaq olmadı
+ Fayl yaradıla bilməz
+ Bu adla bir endirmə davam edir
+ faylın üzərinə yazıla bilməz
+ Bu adda endirilmiş fayl artıq mövcuddur
+ Üzərinə yazın
+ Növbəyə qoyun
+ bərpa olunur
+ dayandırıldı
+ Bitdi
+ Endirmək üçün toxunun
+ Heç biri
+ Əsas video oynadıcıdan digər tətbiqə keçid zamanı hərəkət — %s
+ İmtina
+ Razıyam
+ Sürət
+ İdxal
+ Əvvəlki ixrac
+ İxrac edilir…
+ İdxal edilir…
+ Pleylistə salındı
+ Səsi bağla
+ Ani pəncərə oynadıcı
+ Çəkməcəni Açın
+ Növbəyə saxlamaq üçün basılı tutun
+ Silin
+ Android\'də pulsuz yüngül yayımlayıcı.
+ © %1$s, %2$s tərəfindən %3$s altında
+ Bu faylı oynatmaq üçün heç bir tətbiq quraşdırılmayıb
+ Endirmə
+ Bu icazə, ani pəncərə rejimində
+\naçmaq üçün lazımdır
+ Buferə kopyalandı
+ Parçalar
+ Adını dəyişdir
+ Yaradın
+
+ - %s abunəçi
+ - %s abunəçi
+
+ Səs
+ Təfərrüatlar:
+ Nə:\\nTələb:\\nMəzmun Dili:\\nMəzmun Ölkəsi:\\nTətbiq Dili:\\nXidmət:\\nGMT Saatı:\\nPaket:\\nVersiya:\\nƏS versiyası:
+ Bağışlayın, nəsə xəta baş verdi.
+ Formatlanmış hesabatı kopyalayın
+ Server URL\'sini daxil edin
+ Serveri doğrulamaq mümkün olmadı
+ %s-də bəyəndiyiniz serverləri tapın
+ Video \"Təfsilatlar:\"səhifəsində arxa fon və ya ani pəncərə düyməsini basarkən ipucu göstər
+ Oynadıcı altyazı miqyasını və arxa fon üslublarını dəyişdirin. Effektiv olması üçün tətbiqin yenidən başladılması tələb olunur
+ Xəta baş verdi: %1$s
+ Fayl mövcud deyil, yaxud oxumaq və ya yazmaq icazəsi yoxdur
+ Veb saytı təhlil etmək alınmadı
+ Səs ucalığı
+ Radio
+ \"Oynadıcını çökdür\" Göstərin
+ Oynadıcıdan istifadə edərkən çökdürmə seçimini göstərin
+ Xəta balonu göstər
+ Xəta bildirişi yaradın
+ Burdan idxal edin
+ Bura ixrac edin
+ Faylı idxal edin
+ Abunəlikləri idxal etmək mümkün olmadı
+ Avropa Ümumi Məlumat Mühafizəsi Qaydasına (GDPR) riayət etmək üçün diqqətinizi NewPipe\'ın məxfilik siyasətinə cəlb edirik. Zəhmət olmasa, diqqətlə oxuyun. Xəta hesabatını bizə göndərmək üçün onu qəbul etməlisiniz.
+ Bu adda fayl artıq mövcuddur
+ Bu adla gözlənilən bir endirmə var
+ Təyinat qovluğu yaradıla bilməz
+ Bənzərsiz ad yaradın
+ Bölmələr
+ Cihazınızdakı heç bir tətbiq bunu aça bilməz
+ Miniatürü göstərin
+ Bu, ən azı sizin ölkənizdə olan SoundCloud Go+ trekidir, ona görə də NewPipe tərəfindən yayımlamaq və ya endirmək mümkün deyil.
+ Bu məzmun şəxsidir, ona görə də NewPipe tərəfindən yayımlamaq və ya endirmək mümkün deyil.
+ Dəstək
+ Məxfilik
+ Sahib
+ Siyahıdan kənar
+ Şəxsi
+ Miniatür URL
+ Yaş həddi
+ Dil
+ İctimai
+ Abunəçi sayı əlçatan deyil
+ Lisenziyanı oxuyun
+ Tarixçə
+ Hərflər və rəqəmlər
+ Oynadıcını çökdür
+ Yalnız HTTPS URL\'ləri dəstəklənir
+ Oynadıcı bildirişi
+ Yeni yayımlar
+ Xəta hesabatı bildirişi
+ Video URL\'i imzasının şifrəsi qırılmadı
+ Endirmək üçün heç bir yayım yoxdur
+ Xəta baş verdi, bildirişə baxın
+ Şərhiniz (İngilis dilində):
+ Videonu oynadın, müddət:
+ Zəhmət olmasa, daha sonra tənzimləmələrdə endirmə qovluğunu təyin edin
+ NewPipe Endirilir
+ Hash hesablanır
+ Fayl adlarında icazə verilən simvollar
+ NewPipe Haqqında
+ Lisenziyalar
+ NewPipe müəllif hüquqlu sərbəst tətbiqdir: Siz onu istədiyiniz zaman istifadə edə, öyrənə, paylaşa və təkmilləşdirə bilərsiniz. Xüsusilə, siz Lisenziyanın 3-cü versiyası və ya (sizin seçiminizə görə) hər hansı sonrakı versiyada Azad Proqram Təminatı Fondu tərəfindən dərc edilən GNU Ümumi İctimai Lisenziyasının şərtlərinə uyğun olaraq onu yenidən paylaya və/yaxud dəyişdirə bilərsiniz.
+ İxrac edildi
+ Elementləri silmək üçün sürüşdürün
+ Hələ,əlfəcinlənmiş pleylistlər yoxdur
+ Bu, cari quraşdırmanızı ləğv edəcək.
+ Növbəyə qoy
+ Qara ekranla qarşılaşsanız və ya videonu oynatdıqda səs qırılarsa, media tunelini deaktiv edin
+ Növbəti sıraya salındı
+ Arxa fonda oynatmağa başlayın
+ Yayım təfərrüatları yüklənir…
+ Media tunelini deaktiv edin
+ Tətbiq çökdü
+ YouTube abunəliklərini Google takeout\'dan
+\nidxal edin:
+\n
+\n1. Bu URL\'ə keçin: %1$s
+\n2. Soruşulduqda daxil olun
+\n3.\"Bütün Məlumatlar Daxildir\",sonra \"Heçbirini Seçmə\", yalnız \"abunəliklər\"i seçin və \"OK\" kliklə
+\n4. \"Növbəti addım\"üzərinə klikləyin, sonra isə \"İxrac Yarat\" üzərinə klikləyin
+\n5. Görünəndən sonra \"Endirin\"düyməsini basın
+\n6. Aşağıdakı FAYLI İDXAL ET düyməsinə klikləyin və endirilmiş .zip faylını seçin
+\n7. [Əgər .zip faylı idxalı uğursuz olsa] .csv faylını çıxarın(adətən\"YouTubevəYouTubeMusic/subscriptions/subscriptions.csv\" altında),aşağıda İDXAL EDİLƏN FAYL-ı klikləyin və çıxarılmış csv faylını seçin
+ Oynatma Sürəti Nizamlamaları
+ Ayır (pozuntuya səbəb ola bilər)
+ Xətanı göstər
+ Bəli və qismən baxılmış videolar
+
+ - %1$s endirməsi silindi
+ - %1$s endirmə silindi
+
+ Dayandır
+ Nümunə seçin
+ Sürətli axın rejimi bu barədə əlavə məlumat vermir.
+ ExoPlayer məhdudiyyətlərinə görə axtarış müddəti %d saniyəyə təyin edildi
+ Bəzi xidmətlərdə mövcuddur, adətən daha sürətli olur, lakin məhdud sayda elementləri və çox vaxt natamam məlumatı qaytara bilər (məsələn, müddət, element növü, canlı status yoxdur)
+ Bu əməliyyat üçün uyğun fayl meneceri tapılmadı.
+\nZəhmət olmasa, fayl menecerini quraşdırın və ya endirmə tənzimləmələrində \'%s\'-i deaktiv etməyə çalışın.
+ \'%s\' üçün axın yükləmək mümkün olmadı.
+ Bu əməliyyat üçün uyğun fayl meneceri tapılmadı.
+\nZəhmət olmasa ,Yaddaş Giriş Çərçivəsinə uyğun fayl menecerini quraşdırın.
+ Bu video yalnız YouTube Music Premium üzvləri üçün əlçatandır, ona görə də NewPipe tərəfindən yayımlamaq və ya endirmək mümkün deyil.
+ İndi açıqlamadakı mətni seçə bilərsiniz. Nəzərə alın ki, seçim rejimində səhifə titrəyə bilər və keçidlər kliklənməyə bilər.
+ Bildirişdə göstərilən video miniatürünü 16:9-dan 1:1 nisbətinə qədər ölçün (pozuntulara səbəb ola bilər)
+ Aşağıdakı bildiriş fəaliyyətini hər birinin üzərinə toxunaraq redaktə edin. Sağdakı təsdiq qutularından istifadə edərək yığcam bildirişdə göstərilməsi üçün onlardan üçə qədərini seçin
+ Belə fayl/məzmun mənbəyi yoxdur
+ Seçilmiş yayım xarici oynadıcılar tərəfindən dəstəklənmir
+ Yükləyici tərəfindən hələ dəstəklənməyən yayımlar göstərilmir
+ Xarici oynadıcılar üçün heç bir səs yayımı yoxdur
+ Xarici oynadıcılar üçün heç bir video yayımı yoxdur
+ Xarici oynadıcılar üçün keyfiyyət seçin
+ Naməlum format
+ Naməlum keyfiyyət
+ Oynatma yükləmə intervalı həcmi
\ 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 c5b9e9646..b3642829c 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
@@ -670,12 +670,9 @@
找不到适合此操作的文件管理器。
\n请安装与存储访问框架(SAF)兼容的文件管理器。
NewPipe 遇到了一个错误,点击此处报告此错误
- 已经在后台播放
置顶评论
LeakCanary 不可用
- 以音乐半音调整音高
- 节奏步长
- 改变加载间隔的大小(当前%s),较低的值可以加快初始的视频加载速度,改变需要重启播放器。
+ 更改加载间隔的大小(当前为 %s),较低的值可以加快视频的首次加载速度。更改需要重启播放器。
ExoPlayer 默认
配置当前正在播放的串流的通知
新串流通知
@@ -699,4 +696,13 @@
清除所有下载的文件?
获取通知
来自订阅的新串流的通知
+ 半音
+ 百分比
+ 未知格式
+ 没有音频流可用于外部播放器
+ 选择外部播放器画质
+ 外部播放器不支持所选串流
+ 没有视频流可用于外部播放器
+ 不显示下载器尚不支持的串流
+ 未知画质
\ No newline at end of file
diff --git a/app/src/main/res/values-bn-rBD/strings.xml b/app/src/main/res/values-bn-rBD/strings.xml
index 6fa45249e..02d35d384 100644
--- a/app/src/main/res/values-bn-rBD/strings.xml
+++ b/app/src/main/res/values-bn-rBD/strings.xml
@@ -1,45 +1,45 @@
- অনুসন্ধান এ চাপ দিয়ে শুরু করুন
- প্রকাশকাল %1$s
- কোন স্ট্রিম প্লেয়ার পাওয়া যায়নি। VLC ইনস্টল করতে চান\?
- ইনস্টল করুন
- বাতিল করুন
- ওয়েব ব্রাউজারে ওপেন করুন
+ শুরু করতে আতস কাঁচটিতে টাচ করুন।
+ %1$s তারিখে প্রকাশিত
+ কোন স্ট্রিম প্লেয়ার নেই। VLC ইনস্টল করতে চান\?
+ ইনস্টল
+ বাতিল
+ ব্রাউজারে ওপেন করুন
পপ-আপ মোডে ওপেন করো
শেয়ার
- ডাউনলোউড
+ ডাউনলোড
খুঁজুন
সেটিংস
- আপনি কি বুঝিয়েছেনঃ %1$s\?
+ আপনি কি %1$s বুঝিয়েছেন\?
শেয়ার করুন
বাইরের ভিডিও প্লেয়ার ব্যবহার করুন
- বহির্গত অডিও প্লেয়ার ব্যবহার করুন
+ অন্য অডিও প্লেয়ার ব্যবহার করুন
ব্যাকগ্রাউন্ড
পপআপ
- ভিডিও ডাউনলোড করার ফোল্ডার
+ ভিডিও ডাউনলোড ফোল্ডার
ডাউনলোড করা ভিডিওগুলো এখানে থাকে
- ভিডিওগুলির জন্য ডাউনলোডের পাথ নির্বাচন কর
+ ভিডিও ফাইলের জন্য ডাউনলোড ফোল্ডার বাছুন
অডিও ডাউনলোড ফোল্ডার
- ডাউনলোড করা অডিও এখানে রাখা হয়
- অডিও ফাইলগুলির জন্য ডাউনলোডের ফোল্ডার নির্বাচন করুন
- ডিফল্ট রেজোল্যুশন
- ডিফল্ট পপআপ রেজোল্যুশন
- উচ্চ রেজোল্যুশন দেখাও
- শুধুমাত্র কিছু ডিভাইস 2K/4K ভিডিও চালাতে পারে
+ ডাউনলোড করা অডিও ফাইল এখানে জমা হয়
+ অডিও ফাইলের জন্য ডাউনলোড ফোল্ডার বাছুন
+ ডিফল্ট রেজ্যুলেশন
+ ডিফল্ট পপআপ রেজ্যুলেশন
+ উচ্চ রেজ্যুলেশন দেখাও
+ শুধু কিছু ডিভাইস 2K/4K ভিডিও চালাতে পারে
Kodi এর মাধ্যমে চালাও
হারানো কোর ইনস্টল করবেন\?
- দেখাও \"Kodi এর মাধ্যমে চালাও \" বিকল্প
- Kodi মিডিয়া সেন্টারে এর মাধ্যমে ভিডিও প্লে করার জন্য একটি বিকল্প প্রদর্শন কর
+ \"Kodi দিয়ে চালাও\" অপশন দেখাও
+ Kodi মিডিয়া সেন্টারে এর মাধ্যমে ভিডিও প্লে করার জন্য একটি বিকল্প দেখাও
অডিও
ডিফল্ট অডিও ফরম্যাট
- পছন্দসই ভিডিও ফরম্যাট
+ ডিফল্ট ভিডিও ফরম্যাট
থিম
উজ্জ্বল
অন্ধকার
কালো
পপআপ আকার এবং অবস্থান মনে রাখো
- শেষ আকার এবং পপআপ সেট অবস্থান মনে রাখো
+ পপআপের শেষ আকার ও অবস্থান মনে রাখো
ডাউনলোড
পরবর্তী এবং অনুরূপ ভিডিওগুলি দেখাও
URL সমর্থিত নয়
@@ -49,7 +49,7 @@
ব্যাকগ্রাউন্ডে চলছে
পপআপ মোডে চলছে
কন্টেন্ট
- বয়স সীমাবদ্ধ কন্টেন্ট দেখাও
+ বয়সের অনুপযোগী কন্টেন্ট দেখাও
লাইভ
ডাউনলোডগুলি
ডাউনলোডগুলি
@@ -76,16 +76,16 @@
তোমার মন্তব্য (ইংরেজিতে):
বর্ণনা:
- ভিডিও প্রাকদর্শন, সময়ঃ
+ ভিডিও চালাও, সময়:
আপলোডারের ইউজারপিক থাম্বনেইল
পছন্দ হয়েছে
অপছন্দ হয়েছে
ভিডিও
অডিও
পুনরায় চেষ্টা করো
- K
+ হা
M
- B
+ বি
শুরু
বিরতি
@@ -101,18 +101,19 @@
বিস্তারিত জানার জন্য আলতো চাপ
অনুগ্রহপূর্বক অপেক্ষা করো…
ক্লিপবোর্ডে অনুলিপি করা হয়েছে
- অনুগ্রহ করে একটি উপলব্ধ ডাউনলোড ডিরেক্টরি নির্বাচন করো।
- এই অনুমতিটি পপআপ মোডে খুলতে প্রয়োজন
+ পরে সেটিংস থেকে একটি ডাউনলোড ফোল্ডার নির্ধারণ করে দিন
+ পপআপ মোডে চালু হতে
+\nএই অনুমতির প্রয়োজন আছে
reCAPTCHA চ্যালেঞ্জ
reCAPTCHA চ্যালেঞ্জ অনুরোধ করা হয়েছে
কি:\\nঅনুরোধ:\\nকন্টেন্ট ভাষা:\\nসার্ভিস:\\nসময়(GMT এ):\\nপ্যাকেজ:\\nসংস্করণ:\\nওএস সংস্করণ:\\nআইপি পরিসর:
- স্ট্রিম ফাইল ডাউনলোড করুন।
- তথ্য দেখুন
+ স্ট্রিম ফাইল ডাউনলোড করুন
+ তথ্য দেখাও
নতুন যা কিছু
যুক্ত করুন
- খোজ ইতিহাস
- ইতিহাস
+ অনুসন্ধানের ইতিহাস
+ দেখার ইতিহাস
প্লেয়ার
ব্যাবহার
ইতিহাস
@@ -123,13 +124,13 @@
কোন ভিউ নেই
নাম পরিবর্তন করুন
ওয়েবসাইট
- কোন স্ট্রিম প্লেয়ার পাওয়া যায়নি (প্লে করতে VLC ইন্সটল করতে পারেন)
- কিছু কিছু রেজোলিউশনে অডিও বন্ধ করে দেয়
+ কোন স্ট্রিম প্লেয়ার নেই (প্লে করতে VLC ইন্সটল করতে পারেন)।
+ কিছু কিছু রেজ্যুলেশনে অডিও বন্ধ করে দেয়
সাবস্ক্রাইব
- সাবস্ক্রাইব করা আছে
+ সাবস্ক্রাইবকৃত
চ্যানেল থেকে আনসাবস্ক্রাইব্ড
সাবস্ক্রিপশন পরিবর্তন করা যায়নি
- সাবস্ক্রিপশন আপডেটে ব্যার্থ
+ সাবস্ক্রিপশন আপডেট করা যায়নি
সাবস্ক্রিপশন
বুকমার্ককৃত প্লেলিস্টসমূহ
দ্রুত টানা ব্যাবহার করুন
@@ -137,28 +138,28 @@
ট্যাব পছন্দ করুন
অনির্দিষ্ট সন্ধান প্লেয়ারকে আরো দ্রুত গতিতে সন্ধান করার সুবিধা দেয়, কিন্তু এটি সম্পূর্ণ নির্ভুল নাও হতে পারে ৷ ৫, ১৫ ও ২৫ সেকেন্ডের জন্য এটা কাজ করবে না ৷
দ্রুত-ফরওয়ার্ড/-পুনরায় সন্ধান সময়কাল
- মতামত প্রদর্শন বন্ধ করতে অপশনটি বন্ধ করুন
- মতামত প্রদর্শন করুন
- থাম্বনেইল লোড করুন
+ মন্তব্যসমূহ লুকাতে বন্ধ করুন
+ মন্তব্যসমূহ দেখাও
+ থাম্বনেইল লোড করো
থাম্বনেইল প্রদর্শন বন্ধ করার মাধ্যমে, ডাটা এবং মেমোরি সংরক্ষণ করুন। অপশনটি পরিবর্তনে ইন-মেমোরি এবং অন-ডিস্ক ইমেজ ক্যাশ উভয়ই মুছে যাবে।
- ছবির ক্যাশ মুছে ফেলা হয়েছে
+ ছবির ক্যাশ মোছা হয়েছে
সব ক্যাশড ওয়েবপেজ ডেটা মুছে ফেলো
- ক্যাশ করা মেটাডেটা মুছো
- মেটাডেটা ক্যাশ মুছে ফেলা হয়েছে
- পরবর্তী স্ট্রিম স্বয়ংক্রিয়ংভাবে সংযোজন করুন
+ ক্যাশ করা মেটাডেটা মোছ
+ মেটাডেটা ক্যাশ মোছা হয়েছে
+ পরবর্তী স্ট্রিম স্বয়ংক্রিয়ংভাবে সংযোজন করো
প্লেয়ারের উজ্জ্বলতা নিয়ন্ত্রণ করতে সংকেত ব্যবহার করো
উজ্জ্বলতার নিয়ন্ত্রণ সংকেত
প্লেয়ারের ভলিউম নিয়ন্ত্রণ করতে সংকেত ব্যবহার করো
ভলিউম সংকেত নিয়ন্ত্রণ
- সম্পূর্ণ
+ সম্পন্ন
তালিকা
- তালিকাতে পজিশন
+ তালিকা আকারে সাজাও
শেষ প্লেব্যাক পজিশন এ যাও
পুনরায় প্লে ব্যাক চালু করো
- সার্চগুলো স্থানীয়ভাবে জমা করো
- সার্চের সময় পরামর্শ দেখাও
- সার্চ পরামর্শ
- রেজাল্ট দেখানো হচ্ছেঃ %s
+ অনুসন্ধানের বিষয়বস্তু স্থানীয়ভাবে জমা করো
+ অনুসন্ধানকালীন কী পরামর্শ দেওয়া হবে তা বাছুন
+ অনুসন্ধানের পরামর্শ
+ ফলাফল দেখাচ্ছে: %s এর জন্যে
সম্পর্কিত
নিউপাইপ এর সম্বন্ধে
ট্রেন্ডিং
@@ -175,11 +176,11 @@
পাওয়া যায় নি
সার্ভার পাওয়া যায় নি
এরর দেখান
- ডাউন লোড হয় নি
- পজ হয়েছে
- ডাউন লোড করার জন্য চাপ দিন
+ ডাউনলোড ব্যর্থ হয়েছে
+ স্থগিত
+ ডাউনলোড করতে টোকা দিন
অটো
- কোন সীমা নেই
+ সীমাহীন
কোন ক্যাপশন নেই
প্লে লিস্ট তৈরি হয়েছে
প্লে লিস্ট ডিলিট করতে চান\?
@@ -231,15 +232,15 @@
স্ট্রিম টি চালানো গেল না
বাহ্যিক স্টোরেজ নেই
সাহায্য
- সার্চ ইতিহাস ডিলিট হয়েছে।
+ অনুসন্ধান ইতিহাস মোছা হয়েছে
সমগ্র সার্চ ইতিহাস মুছবেন\?
সার্চের ইতিহাস মোছা হয়
সার্চ ইতিহাস মুছুন
- প্লে ব্যাক এর অবস্থান মোছা হয়েছে।
+ প্লেব্যাক অবস্থানসমূহ মোছা হয়েছে
সমস্ত প্লে লিস্ট এর অবস্থান মুছবেন\?
সমস্ত প্লে লিস্ট এর অবস্থান মুছে ফেলুন
প্লে লিস্ট এর অবস্থান মুছে ফেলুন
- দেখার ইতিহাস মুছে গেছে।
+ দেখা ভিডিওর ইতিহাস মোছা হয়েছে
সম্পূর্ণ দেখার ইতিহাস মুছে ফেলুন\?
দেখার ইতিহাস মুছে ফেলুন
ডাটা বেস এক্সপোর্ট করুন
@@ -247,9 +248,9 @@
মেন এ ফিরে যান
পপ-আপ এ খুলুন
পেছনে নিয়ে যান
- নিউ পাইপ এর নতুন ভার্সন এর সূচনা
- অ্যাপ আপডেট এর সূচনা
- নিউ পাইপ এর সূচনা
+ নতুন নিউপাইপ ভার্সন এর নোটিফিকেশন
+ অ্যাপ আপডেট নোটিফিকেশন
+ নিউ পাইপ নোটিফিকেশন
সব চালু করুন
ফাইল ডিলিট হয়েছে
সেরা রেজুলিউসন
@@ -257,7 +258,7 @@
অ্যালবাম গুলি
গান গুলি
ভিডিও গুলি
- YouTube নিষিদ্ধ মোড
+ YouTube-এর \"সীমিত মোড\" চালু করো
শুধুমাত্র HTTPS URL গুলি সাপোর্ট করে
ইন্সটান্স এর ইউ আর এল
ইন্সটান্স যোগ করুন
@@ -295,14 +296,14 @@
সরাও
তৈরি করো
ফিরে যাও
- স্বয়ংক্রিয়
+ স্বয়ংক্রিয় প্লে
পুনরায় চালু করো
দেখা ভিডিওগুলোর হিসেব
- ডাটা মুছে ফেলুন
+ ডাটা মুছে ফেল
কিছুই না
পুনরায়
প্রথম ক্রিয়া বোতাম
- থাম্বনেলে ১:১ অনুপাতে করো
+ থাম্বনেল ১:১ অনুপাতে সেট করো
সিস্টেম ডিফল্ট
এ ফাইলটি চালানোর জন্য কোন অ্যাপ ইন্সটলকৃত নেই
প্লেলিস্ট বুকমার্ক করুন
@@ -351,4 +352,111 @@
ভাষা
বয়স সীমা
প্রধান পাতার উপাদান সমূহ
+ কেউ শুনছে না
+ মূল প্লেয়ার পুরো পর্দাজুড়ে চালাও
+ বর্ণনা
+ প্লেয়ার নোটিফিকেশন
+ ইউটিউব একটি \"সীমিত ধরন\" সরবরাহ করে যেটি সম্ভাব্য প্রাপ্তবয়স্ক কন্টেন্ট লুকিয়ে রাখে
+ ভিডিও হ্যাশ নোটিফিকেশন
+ নতুন স্ট্রিমসমূহ
+ রিক্যাপচা কুকিজ মোছা হয়েছে
+ অন্যান্য অ্যাপের উপরে হাজির হতে অনুমতি দিন
+ সমস্যা হয়েছে, নোটিফিকেশন দেখুন
+ তৈরিকৃত রিপোর্ট কপি করো
+ সম্পর্কিত আইটেমসমূহ
+ ঝিঁঝিঁপোকা ছাড়া এখানে কিছু নেই
+ পুনর্বিন্যাস্ত করতে টান দিন
+ গ্রাহকসংখ্যা সুলভ নয়
+ কেউ দেখছে না
+
+ - %s ভিডিও
+ - %s ভিডিও
+
+
+ - %s জন শ্রোতা
+ - %s জন শ্রোতা
+
+ ∞ ভিডিও
+ সমাধান করো
+ নতুন স্ট্রিমের নোটিফিকেশন
+ যেকোন নেটওয়ার্ক
+ আপডেটসমূহ
+ শুধু ওয়াই-ফাই তে
+ কখনো না
+ অপেক্ষমাণ
+ আপডেট চেক করা হচ্ছে …
+ ,
+ রাত্রিকালীন থিম
+ বর্ণনা দেখাও
+ দ্বিতীয় পদক্ষেপ বোতাম
+ পঞ্চম পদক্ষেপ বোতাম
+ ভিডিও বর্ণনা ও বাড়তি তথ্য লুকাতে বন্ধ করুন
+ দেখিও না
+ নিউ পাইপ আপডেট এসেছে!
+ মন্তব্যসমূহ নিষ্ক্রিয় আছে
+
+ - %s বার দেখেছে
+ - %s বার দেখেছে
+
+ ওভাররাইট
+ তৃতীয় পদক্ষেপ বোতাম
+ স্থানীয় অনুসন্ধানের পরামর্শ
+ মোবাইল ডেটা ব্যবহারের সময় রেজুলেশন সীমিত করো
+ অপেক্ষমাণ
+ এক প্লেয়ার বদলে অন্য প্লেয়ারে যাওয়া আপনার অপেক্ষমাণ সারিকে প্রতিস্থাপন করতে পারে
+ সক্রিয় প্লেয়ারের অপেক্ষমাণ সারি প্রতিস্থাপিত হবে
+ বিঘ্ন ঘটার পরে চালানো অব্যাহত রাখো (যেমন. ফোনকল)
+ প্লেব্যাক অবস্থানের চিহ্নসমূহ তালিকায় দেখাও
+ URL টি বোঝা যায়নি। অন্য অ্যাপ দিয়ে খুলবো\?
+ কনটেন্টের জন্য পূর্বনির্ধারিত দেশ
+ বাইরের প্লেয়ারসমূহ এ ধরনের লিঙ্কসমূহ সমর্থন করে না
+ হ্যাশ হিসাব করা হচ্ছে
+ আপডেট চেক করো
+
+ - %s জন দেখছে
+ - %s জন দেখছে
+
+
+ - %s টি নতুন স্ট্রিম
+ - %s টি নতুন স্ট্রিম
+
+
+ - ডাউনলোড সম্পন্ন
+ - %s ডাউনলোড হয়েছে
+
+ ১০০+ ভিডিও
+ ব্যবহারকারীগণ
+
+ - %s জন গ্রাহক
+ - %s জন গ্রাহক
+
+ আনুষঙ্গিক তথ্য দেখাও
+ নেটওয়ার্ক সংযোগ দরকার
+ উচ্চ মান (বড় আকারের)
+ নিম্ন মান (ছোট আকার)
+ চতুর্থ পদক্ষেপ বোতাম
+ এলোমেলো
+ বাফারিং
+ নোটিফিকেশন বর্ণিল করো
+ এই ভিডিও বয়স দ্বারা সীমিত।
+\n
+\nএটি দেখতে চাইলে সেটিংস থেকে \"%1$s\" চালু করুন।
+ সব টগল করো
+ আপনি এখন এই চ্যানেলের গ্রাহক
+ অপেক্ষমাণ সারি বাদ দেওয়ার আগে নিশ্চিত হতে জিজ্ঞেস করো
+ শিশুদের জন্য বয়সসীমার কারণে অনুপযোগী হতে পারে এমন কন্টেন্ট দেখাও (যেমন ১৮+)
+ প্লে করা স্ট্রিমসমূহের ইতিহাস এবং প্লেব্যাক অবস্থানসমূহ মুছে দেবে
+ নোটিফিকেশনসমূহ
+ ত্রুটি প্রতিবেদন এর নোটিফিকেশন
+ ইতিহাস, সাবস্ক্রিপশন্স, প্লেলিস্ট ও সেটিংস রপ্তানি করো
+ কোনো ডাউনলোড ফোল্ডার ঠিক করা নেই, এখন ডিফল্ট ডাউনলোড ফোল্ডার বাছুন
+ স্বয়ংক্রিয় (ডিভাইস থিম)
+ প্রিয় রাত্রিকালীন থিম বেছে নিন — %s
+ লাইসেন্স
+ গোপনতা
+ নিচ থেকে আপনার পছন্দের রাত্রিকালীন থিম বেছে নিতে পারেন
+ ডাউনলোড শুরু হয়েছে
+ দেখা হিসেবে মার্ক করো
+ ট্যাগসমূহ
+ অ্যান্ড্রয়েডকে থাম্বনেইলের প্রধান রং অনুযায়ী রঙিন করুন (উল্লেখ্য যে, এটি সব ডিভাইসে উপলব্ধ নয়)
\ No newline at end of file
diff --git a/app/src/main/res/values-bn-rIN/strings.xml b/app/src/main/res/values-bn-rIN/strings.xml
index 24498f162..00468f7f7 100644
--- a/app/src/main/res/values-bn-rIN/strings.xml
+++ b/app/src/main/res/values-bn-rIN/strings.xml
@@ -33,7 +33,7 @@
ভিডিও প্রাকদর্শন, সময়ঃ
বর্ণনা:
আপনার মন্তব্য (ইংরেজিতে):
- কি:\\nঅনুরোধ:\\nকন্টেন্ট ভাষা:\\nসার্ভিস:\\nসময়(GMT এ):\\nপ্যাকেজ:\\nসংস্করণ:\\nওএস সংস্করণ:\\nআইপি পরিসর:
+ কি:\\nঅনুরোধ:\\nকন্টেন্ট ভাষা:\\nকন্টেন্ট দেশ:\\nঅ্যাপ ভাষা:\\nসার্ভিস:\\nসময়(GMT এ):\\nপ্যাকেজ:\\nসংস্করণ:\\nওএস সংস্করণ:
কি হয়েছিল:
তথ্য:
প্রতিবেদন
@@ -83,7 +83,7 @@
সব ক্যাশড ওয়েবপেজ ডেটা মুছে ফেলো
ক্যাশ করা মেটাডেটা মুছো
ছবির ক্যাশ মুছে ফেলা হয়েছে
- থাম্বনেইল প্রদর্শন বন্ধ করার মাধ্যমে, ডাটা এবং মেমোরি সংরক্ষণ করুন। অপশনটি পরিবর্তনে ইন-মেমোরি এবং অন-ডিস্ক ইমেজ ক্যাশ উভয়ই মুছে যাবে।
+ থাম্বনেইল প্রদর্শন বন্ধ করার মাধ্যমে, ডাটা এবং মেমোরি সংরক্ষণ করুন। অপশনটি পরিবর্তনে ইন-মেমোরি এবং অন-ডিস্ক ইমেজ ক্যাশ উভয়ই মুছে যাবে
মতামত প্রদর্শন বন্ধ করতে অপশনটি বন্ধ করুন
মতামত প্রদর্শন করুন
থাম্বনেইল লোড করুন
@@ -142,7 +142,7 @@
কোন স্ট্রিম প্লেয়ার পাওয়া যায়নি (প্লে করতে VLC ইন্সটল করতে পারেন)।
কোন স্ট্রিম প্লেয়ার পাওয়া যায়নি। VLC ইনস্টল করতে চান\?
প্রকাশকাল %1$s
- অনুসন্ধান এ চাপ দিয়ে শুরু করুন
+ অনুসন্ধান শুরু করুন
নতুন
নতুন কি
অ্যাপ এর ভাষা
@@ -224,7 +224,7 @@
প্লেয়ার এর এরর থেকে বেরিয়ে আসুন
স্ট্রিম টি চালানো গেল না
সার্চের ইতিহাস মোছা হয়
- প্লে ব্যাক এর অবস্থান মোছা হয়েছে।
+ প্লে ব্যাক এর অবস্থান মোছা হয়েছে
সমস্ত প্লে লিস্ট এর অবস্থান মুছবেন\?
সমস্ত প্লে লিস্ট এর অবস্থান মুছে ফেলুন
প্লে লিস্ট এর অবস্থান মুছে ফেলুন
@@ -300,4 +300,20 @@
প্রথম অ্যাকশান বোতাম
দ্বিতীয় অ্যাকশান বোতাম
নতুন স্ট্রিম
+ শুধুমাত্র ওয়াইফাইএ
+ ভাষা
+ URL বোঝা যায় নি, অন্য অ্যাপ এ খুলুন\?
+ ফাইল তৈরি করা যাচ্ছে না
+ ভিডিও বিবরণ ও বাড়তি তথ্য বন্ধ করুন
+ মূল প্লেয়ার ফুল স্ক্রীন এ শুরু করুন
+ " "
+ সার্ভার এর সাথে যোগাযোগ করা যাচ্ছে না
+ বাফার হচ্ছে
+ ডাউনলোড শুরু হয়েছে
+ এটি দিয়ে খুলুন
+ দেখা হিসাবে চিহ্নিত করুন
+ থাম্বনেল 1:1 আকৃতির অনুপাতের করুন
+ বিজ্ঞপ্তিতে প্রদর্শিত ভিডিও থাম্বনেল 16:9 থেকে 1:1 অনুপাতের করুন (বিকৃতি দেখা যেতে পারে)
+ অদলবদল
+ কিছু না
\ No newline at end of file
diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml
index 0f1ea9c25..a1acf9fd1 100644
--- a/app/src/main/res/values-bn/strings.xml
+++ b/app/src/main/res/values-bn/strings.xml
@@ -44,7 +44,7 @@
সবসময় জিজ্ঞেস করুন
ভিডিও প্লেয়ার
ড্রয়ার বন্ধ করুন
- ড্রয়ার খুলন
+ ড্রয়ার খুলো
অডিও সেটিং
বিবরণ
সরাও
@@ -126,7 +126,7 @@
গিটহাব এ এরর রিপোর্ট করুন
মেইলের মাধ্যমে ত্রুটি প্রতিবেদন করুন
দুঃখিত, এটা ঘটা উচিত ছিল না।
- আপনি কি ডিফল্ট এ ফিরতে চান\?
+ তুমি কি এই সহজাত পছন্দ ফিরত চাও\?
ডিফল্ট এ ফিরে যান
ডাউন লোড এর জন্য কোন স্ট্রিম নেই
একটা এরর হয়েছেঃ %1$s
@@ -147,15 +147,15 @@
বাহ্যিক স্টোরেজ নেই
ত্রুটি
সাহায্য
- সার্চ ইতিহাস ডিলিট হয়েছে।
+ সার্চ ইতিহাস ডিলিট হয়েছে
সমগ্র সার্চ ইতিহাস মুছবেন\?
সার্চের ইতিহাস মোছা হয়
সার্চ ইতিহাস মুছুন
- প্লে ব্যাক এর অবস্থান মোছা হয়েছে।
+ প্লে ব্যাক এর অবস্থান মোছা হয়েছে
সমস্ত প্লে লিস্ট এর অবস্থান মুছবেন\?
সমস্ত প্লে লিস্ট এর অবস্থান মুছে ফেলুন
প্লে লিস্ট এর অবস্থান মুছে ফেলুন
- দেখার ইতিহাস মুছে গেছে।
+ দেখার ইতিহাস মুছে গেছে
সম্পূর্ণ দেখার ইতিহাস মুছে ফেলুন\?
দেখার ইতিহাস মুছে ফেলুন
ডাটা বেস এক্সপোর্ট করুন
@@ -189,7 +189,7 @@
ডাউনলোডগুলি
ডাউনলোডগুলি
লাইভ
- YouTube \"নিষিদ্ধ মোড\" চালু করুন
+ ইউটিউব ‘সীমিত মোড’ চালু করো
বয়স সীমাবদ্ধ কন্টেন্ট দেখাও
কন্টেন্ট
পপআপ মোডে চলছে
@@ -231,12 +231,12 @@
সব ক্যাশড ওয়েবপেজ ডেটা মুছে ফেলো
ক্যাশ করা মেটাডেটা মুছো
ছবির ক্যাশ মুছে ফেলা হয়েছে
- থাম্বনেইল প্রদর্শন বন্ধ করার মাধ্যমে, ডাটা এবং মেমোরি সংরক্ষণ করুন। অপশনটি পরিবর্তনে ইন-মেমোরি এবং অন-ডিস্ক ইমেজ ক্যাশ উভয়ই মুছে যাবে।
+ থাম্বনেইল প্রদর্শন বন্ধ করার মাধ্যমে, ডাটা এবং মেমোরি সংরক্ষণ করুন। অপশনটি পরিবর্তনে ইন-মেমোরি এবং অন-ডিস্ক ইমেজ ক্যাশ উভয়ই মুছে যাবে
মতামত প্রদর্শন বন্ধ করতে অপশনটি বন্ধ করুন
মতামত প্রদর্শন করুন
থাম্বনেইল লোড করুন
দ্রুত-ফরওয়ার্ড/-পুনরায় সন্ধান সময়কাল
- অনির্দিষ্ট সন্ধান প্লেয়ারকে আরো দ্রুত গতিতে সন্ধান করার সুবিধা দেয়, কিন্তু এটি সম্পূর্ণ নির্ভুল নাও হতে পারে ৷ ৫, ১৫ ও ২৫ সেকেন্ডের জন্য এটা কাজ করবে না ৷
+ অনির্দিষ্ট সন্ধান, চালককে আরো দ্রুত গতিতে সন্ধান করার সুবিধা দেয়, কিন্তু এটি সম্পূর্ণ নির্ভুল নাও হতে পারে ৷ ৫, ১৫ ও ২৫ সেকেন্ডের জন্য এটা কাজ করবে না।
দ্রুত টানা ব্যাবহার করুন
শেষ আকার এবং পপআপ সেট অবস্থান মনে রাখো
পপআপ আকার এবং অবস্থান মনে রাখো
@@ -253,12 +253,12 @@
থাম্বনেলে ১:১ অনুপাতে করো
Kodi মিডিয়া সেন্টারে এর মাধ্যমে ভিডিও প্লে করার জন্য একটি বিকল্প প্রদর্শন কর
\"Kodi দ্বারা চালান\" বিকল্পটি প্রদর্শন কর
- হারানো কোর ইনস্টল করবেন\?
+ হারানো কোর ইনস্টল করবে\?
Kodi দ্বারা চালাও
শুধুমাত্র কিছু ডিভাইস 2K/4K ভিডিও চালাতে পারে
উচ্চতর রেজুলেশন প্রদর্শন করা হবে
- ডিফল্ট পপ-আপ রেজোল্যুশন
- ডিফল্ট রেজোল্যুশন
+ সহজাত ভাসমান আকার
+ সহজাত আকার
অডিও ফাইলগুলির জন্য ডাউনলোডের ফোল্ডার নির্বাচন করুন
ডাউনলোড করা অডিও ফাইলগুলি এখানে সঞ্চিত থাকে
অডিও ডাউনলোড ফোল্ডার
@@ -283,20 +283,20 @@
বাইরের ভিডিও প্লেয়ার ব্যবহার করুন
শেয়ার করুন
রেজাল্ট দেখানো হচ্ছেঃ %s
- আপনি কি বুঝিয়েছেনঃ %1$s\?
+ তুমি কি বুঝিয়েছো ‘%1$s’\?
সেটিংস
খুঁজুন
স্ট্রিম ফাইল ডাউনলোড করুন
ডাউনলোউড
শেয়ার
- পপ-আপ মোডে ওপেন করো
- ব্রাউজারে ওপেন করো
+ ভাসমান অবস্থায় খুলো
+ ব্রাউজারে খুলো
বাতিল
ইনস্টল
- কোন স্ট্রিম প্লেয়ার পাওয়া যায়নি (প্লে করতে VLC ইন্সটল করতে পারেন).
+ কোনো ধারা চালক পাওয়া যায়নি (প্লে করতে VLC ইন্সটল করতে পারো)।
কোন স্ট্রিম প্লেয়ার পাওয়া যায়নি। VLC ইনস্টল করতে চান\?
প্রকাশকাল %1$s
- \"অনুসন্ধান\" এ চাপ দিয়ে শুরু করুন
+ আতশী কাঁচে টিপ দিয়ে শুরু করো।
বাফারিং
সাফল
পঞ্চম অ্যাকশন বাটন
@@ -308,7 +308,7 @@
এক প্লেয়ার থেকে অন্য প্লেয়ারে পরিবর্তন করলে তোমার সারি প্রতিস্থাপিত হতে পারে
কিউ মোছার আগে নিশ্চিত করো
কমপ্যাক্ট বিজ্ঞপ্তিতে প্রদর্শন করতে তুমি সর্বাধিক তিনটি ক্রিয়া নির্বাচন করতে পারো!
- নিচের প্রতিটি প্রজ্ঞাপন ক্রিয়া সম্পাদনা করো। ডান দিকের চেকবাক্স ব্যবহার করে কম্প্যাক্ট নোটিফিকেশনে দেখানোর জন্য তিনটি পর্যন্ত নির্বাচন করো।
+ নিচের প্রতিটি প্রজ্ঞাপন ক্রিয়া সম্পাদনা করো। ডান দিকের চেকবাক্স ব্যবহার করে কম্প্যাক্ট নোটিফিকেশনে দেখানোর জন্য তিনটি পর্যন্ত নির্বাচন করো
১৬:৯ থেকে ১:১অনুপাতে প্রদর্শিত ভিডিও থাম্বনেইল পরিবর্তন করো (বিকৃতি প্রবর্তন করতে পারে)
ফিড
ওভাররাইট
@@ -441,7 +441,7 @@
অধ্যায়
মতামত
বর্ণনা
- দিয়ে খুলুন
+ দিয়ে খুলো
ফিড হালনাগাদ সীমা
খালি গ্রুপ নাম
কোনো সদস্যতা নির্বাচিত হয়নি
@@ -454,7 +454,7 @@
শুধুমাত্র ওয়াই-ফাই-তে
নীরবতার সময় দ্রুত আগাও
প্লেব্যাক গতি নিয়ন্ত্রণ
- পছন্দসই \'মুক্ত\' ক্রিয়া
+ পছন্দসই \'খোলার\' ক্রিয়া
আউট-অফ-লাইফসাইকেল ত্রুটি প্রতিবেদন করো
প্লে-তালিকার থাম্বনেইল পরিবর্তিত হয়েছে।
অনুরোধকৃত তথ্য লোড হচ্ছে
@@ -484,14 +484,14 @@
চ্যানেলের অবতারের প্রতিচ্ছবি
দ্রুত মোড বন্ধ করো
দ্রুত মোড চালু করো
- ফাইল বামানো যায়নি
+ ফাইল বানানো যায়নি
মোবাইল ডাটা ব্যবহারের সময় আকার সীমিত রাখো
ভুক্তিতে আসল সময় দেখাও
রেডিও
বিশেষ
সমাধান করো
শেষ হালনাগাদের পর একটি সাবস্ক্রিপশনের আগের সময় সেকেলে বিবেচিত — %s
- আপনি কি এ গ্রুপটি মুছতে চান\?
+ তুমি কি এ গ্রুপটি মুছতে চাও\?
আরও তথ্য এবং খবরের জন্য নিউপাইপ ওয়েবসাইট দেখো।
এ ফাইলটি চালানোর জন্য কোন অ্যাপ ইন্সটলকৃত নেই
এতে তোমার বর্তমান অবস্থা সরানো হবে।
@@ -516,14 +516,14 @@
পছন্দসমূহ কি আমদানি করতে চাও\?
অবৈধ অক্ষরগুলো এই মান দ্বারা প্রতিস্থাপিত
অন্য অ্যাপের উপরে দেখাতে অনুমতি দাও
- %s-এ আপনার পছন্দের ইন্সট্যান্স খুঁজুন
+ %s-এ তোমার পছন্দের ইন্সট্যান্স খুজো
প্লে করা স্ট্রিমের ইতিহাস এবং প্লেব্যাক অবস্থানগুলি মুছে দেয়
এই ভিডিওটি বয়সসীমাবদ্ধ ।
\n
-\nআপনি এটি দেখতে চাইলে সেটটিংসে \"%1$s\" চালু করুন ।
+\nতুমি এটি দেখতে চাইলে পছন্দসমূহে \"%1$s\" চালু করো।
Youtube একটি \"সীমাবদ্ধ মোড\" সরবরাহ করে যা সম্ভাব্য বয়সসীমাবদ্ধ বিষয়গুলি গুপ্ত রাখে
শিশুদের জন্যে সম্ভবত অনুপযুক্ত বিষয়গুলোও দেখান যেগুলির একটি বয়সসীমা রয়েছে (যেমন ১৮+ বিষয়সমূহ)
- ইউআরএলটি চিন্থিত করা যায়নি | অন্য এপ্লিকেশন এ খুলতে চান \?
+ ইউআরএলটি চিহ্নিত করা যায়নি। অন্য অ্যাপ্লিকেশনে খুলতে চাও\?
এই ফাইলে কাজ করার সময় নিউপাইপ বন্ধ করা হয়েছে
এই নামের একটি ডাউনলোড প্রক্রিয়ারত
সংরক্ষিত ট্যাব পড়া যায় নি, তাই সহজাতটি ব্যবহার করা হচ্ছে
@@ -543,7 +543,7 @@
অনুসন্ধান ইতিহাস থেকে এই ভুক্তিটি মুছবে\?
প্রত্যেক ডাউনলোড কোথায় রাখা হবে তা জিজ্ঞেস করা হবে
এই নামের একটি ডাউনলোড চলমান
- অ্যাপ আবার শুরু হলে ভাষা পাল্টাবে।
+ অ্যাপ আবার শুরু হলে ভাষা পাল্টাবে
মিডিয়া সুরঙ্গকরণ অক্ষম
দ্রুত ফিড অবস্থা এ বিষয়ে এর বেশি তথ্য দেয় না।
কোনো ডাউনলোড ফোল্ডার নির্দিষ্ট করা হয়নি, এখনই একটা সহজাত ডাউনলোড ফোল্ডার নির্বাচন করো
@@ -555,7 +555,7 @@
বর্ণনার লেখা নির্বাচন করা সক্ষম করো
%s এই কারণ বলছে:
প্রক্রিয়াকরণ ফিডে ত্রুটি
- মুক্ত ওয়েবসাইট
+ ওয়েবসাইট খুলুন
অ্যাকাউন্ট ধ্বংসকৃত
প্রতিচ্ছবি সংযোগ
বয়সসীমা
@@ -582,4 +582,44 @@
- %sটি ডাউনলোড সমাপ্ত
দেখা হয়েছে চিহ্নিত করো
+ চালক বিজ্ঞপ্তি
+ নিম্ন মান(ছোট)
+ মূল তৈরিকারকের পছন্দ করা
+ ,
+ বিজ্ঞপ্তি পাঠাও
+ সব পরিবর্তন করো
+ উচ্চতর মান (বৃহত্তর)
+ মন্তব্য নিষ্ক্রিয়
+ স্থানীয় অনুসন্ধানের পরামর্শ
+ দূর অনুসন্ধানে পরামর্শ
+ শতাংশ
+ সেমিটোন
+ বাইরের চালক সহজাত
+ পরেরটা ক্রমে রাখো
+ চালক থামাও
+ নতুন ধারা
+ কম্পাঙ্ক দেখো
+ পূর্বদর্শন রেখার মাধ্যমে প্রাকদর্শন
+ ছবিরূপ সূচক দেখাও
+ দেখিও না
+ যেকোনো নেটওয়ার্ক
+ পরেরটা ক্রমে রাখা হয়েছে
+ পিনকৃত মন্তব্য
+ বিজ্ঞপ্তি
+ হালনাগাদ দেখা হচ্ছে …
+ হালনাগাদ আছে কিনা দেখো
+ মূল প্লেয়ার ফুল স্ক্রীন এ শুরু করুন
+ ধারার নতুন ভুক্তি
+ ত্রুটি প্রতিবেদন এর বিজ্ঞপ্তি
+ পটভূমি বা ভিডিওর ‘বিস্তারিত:’ এর ভাসমান বোতাম টিপলে একটা তথ্য দেখাও
+
+ - %s টি নতুন ধারা
+ - %s টি নতুন ধারা
+
+ প্রতিচ্ছবিত প্রধান রঙ অনুসারে অ্যান্ড্রয়েডকে বিজ্ঞপ্তির রঙ কাস্টমাইজ করতে দাও (দ্রষ্টব্য যে এটা সমস্ত ডিভাইসে উপলব্ধ নয়)
+ অজ্ঞাত ফরম্যাট
+ বাহ্যিক প্লেয়ারের জন্য মান নির্বাচন করুন
+ বাহ্যিক প্লেয়ারের জন্য কোনো অডিও স্ট্রিম নেই
+ বাহ্যিক প্লেয়ারের জন্য কোনো ভিডিও স্ট্রিম নেই
+ অজ্ঞাত মান
\ No newline at end of file
diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml
index 421139875..a42e68940 100644
--- a/app/src/main/res/values-ca/strings.xml
+++ b/app/src/main/res/values-ca/strings.xml
@@ -650,7 +650,6 @@
Inicia el reproductor principal en pantalla completa
Llisqueu els elements per eliminar-los
Si la rotació automàtica està bloquejada, no inicieu vídeos al mini reproductor, sinó que aneu directament al mode de pantalla completa. Podeu accedir igualment al mini reproductor sortint de pantalla completa
- Ja s\'està reproduint en segon pla
Notificació d\'informe d\'error
Tancar abruptament el reproductor
Comprovar si hi ha actualitzacions
diff --git a/app/src/main/res/values-ckb/strings.xml b/app/src/main/res/values-ckb/strings.xml
index f621b88bd..9796404b4 100644
--- a/app/src/main/res/values-ckb/strings.xml
+++ b/app/src/main/res/values-ckb/strings.xml
@@ -11,7 +11,7 @@
\nیوتیوب نموونەیە لەم خزمەتگوزارییە کە ڕێگەی خێرا بەکاردەبات بەهۆی پیشاندەری RSS.
\n
\nبۆیە هەڵژرادن بۆ خۆت دەگەڕێتەوە: زانیاری تەواو یان خێرا.
- نیوپایپ نهرمهوالایهكی سەرچاوە کراوەیە : دەتوانیت بەکاریبهێنیت، بیخوێنیتەوە و هاوبەشی پێبکەیت و بەرەوپێشی ببەیت. بەتایبەتی دەتوانی دابەشیبکەیتەوە یاخوود بگۆڕیت بەپێی مەرجەکانی GNU مۆڵەتنامەی گشتی وەک نهرمهواڵایهكی بڵاوی خۆڕایی, بەهۆی وەشانی ٣ ی مۆڵەتنامە، یان هەر وەشانێکی دوواتر.
+ نیوپایپ نهرمهوالایهكی سەرچاوە کراوەیە : دەتوانیت بەکاریبهێنیت، بیخوێنیتەوە، هاوبەشی پێبکەیت ،بەرەوپێشی ببەیت. بەتایبەتی دەتوانی دابەشیبکەیتەوە یاخوود بگۆڕیت بەپێی مەرجەکانی GNU مۆڵەتنامەی گشتی وەک نهرمهواڵایهكی بڵاوی خۆڕایی, بەهۆی وەشانی ٣ ی مۆڵەتنامە، یان هەر وەشانێکی دوواتر.
چی:\\nداواكاری:\\nزمانی بابەت:\\nوڵاتی بابەت:\\nزمانی بهرنامه:\\nخزمهتگوزاری:\\nGMT كات:\\nپاكێج:\\nوهشان:\\nOS وهشان:
پڕۆژەی نیوپایپ زانیارییە تایبەتییەکانت بە وردی دەپارێزێت. هەروەها بهرنامهكه هیچ زانایارییەکت بەبێ ئاگاداری تۆ بەکارنابات.
\nسیاسەتی تایبەتی نیوپایپ بە وردی ڕوونکردنەوەت دەداتێ لەسەر ئەو زانیاریانەی وەریاندەگرێت و بەکاریاندەبات.
@@ -672,7 +672,6 @@
پیشاندانی ”کڕاش کردنی لێدەرەکە“
سازاندنی پەیامی کێشەیەک
پشکنین بۆ نوێکردنەوە
- وا لە پاشبنەمادا لێدەدرێت
کێشە لە سکاڵا کردنی پەیام
پەیامەکانی سکاڵاکردن لە کێشەکان
بابەتە نوێیەکانی فیید
@@ -684,4 +683,29 @@
LeakCanary بەردەست نییە
هیچ ڕێکخەرێکی فایلی گونجاو نەدۆزرایەوە بۆ ئەم کردارە.
\nتکایە ڕێکخەرێکی فایلی دابمەزرێنە کە گونجاوبێت لەگەڵ دەسەڵاتی گەیشتن بە بیرگە.
+ پشکنین کردن بۆ پەخشی نوێ
+ پەیامەکانی پەخشە نوێیەکان
+ پەیام بکرێم لەکاتی هەبوونی پەخشی نوێی بەژدارییەکان
+ فریکوێنسی دەپشکنرێت
+ پەیوەندی تۆڕ داواکراوە
+ هەر تۆڕێک
+ تۆ ئێستا ئەم چەناڵەت بەژداری کردووە
+ ،
+ پەخشە نوێیەکان
+ پەیامی لێدەر
+ پەیامەکان
+ پەیامەکان بۆ پەخشە نوێیەکانی بەژدارییەکانت
+ وردەکاری پەخش باردەکرێت…
+
+ - %s پەخشی نوێ
+ - %s پەخشانی نوێ
+
+ پەیامی ئێستای لێدانی پەخش ڕێکبخە
+ هەموو فایلە دابەزێنراوەکان لە دیسک بسڕدرێتەوە؟
+ پەیامەکان ناکاراکراون
+ پەیامم بکە
+ "قەبارەی نێوان بارکردنەکە بگۆڕە (لە ئێستادا %s) . بەهایەکی کەمتر لەوانەیە بارکردنی ڤیدیۆی سەرەتایی خێراتر بکات. گۆڕانکارییەکان پێویستیان بە داگیرساندنەوەی لێدەر هەیە"
+ لەسەدا
+ نیمچەتەن
+ بنەڕەتی ExoPlayer
\ 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 84c957f1c..ea6d292d8 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -684,7 +684,6 @@
Vytvořit oznámení o chybě
Kontrola aktualizací…
Ukázat „Shodit přehrávač“
- Hraje již v pozadí
Nové položky feedů
Pro tuto akci nebyl nalezen žádný vhodný správce souborů.
\nProsím, nainstalujte správce souborů kompatibilní se Storage Access Framework.
@@ -698,7 +697,30 @@
Shodit přehrávač
Změnit interval načítání (aktuálně %s). Menší hodnota může zrychlit počáteční načítání videa. Změna vyžaduje restart přehrávače.
LeakCanary není dostupné
- Upravit výšku tónů po půltónech
- Krok tempa
Výchozí ExoPlayer
+ Nastavit oznámení k právě přehrávanému strýmu
+ Oznámení o nových strýmech
+ Oznámit o nových strýmech k objednání
+ Frekvence kontroly
+ Jakákoli síť
+ Nutné síťové připojení
+ Smazat všechny stažené soubory z disku\?
+ Objednali jste si nyní tento kanál
+ Všechny přepnout
+ Nové strýmy
+ Oznámení o nových strýmech k objednání
+ Spustit kontrolu nových strýmů
+ Oznámení přehrávače
+ Oznámení
+ Načítám podrobnosti o strýmu…
+ Oznámení jsou vypnuta
+ Přijímat oznámení
+ ,
+
+ - %s nový strým
+ - %s nové strýmy
+ - %s nových strýmů
+
+ Procento
+ Půltón
\ No newline at end of file
diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml
index 71bb83fb0..fc16fbd63 100644
--- a/app/src/main/res/values-da/strings.xml
+++ b/app/src/main/res/values-da/strings.xml
@@ -1,8 +1,8 @@
Tryk på forstørrelsesglasset for at komme i gang.
- Udgivet %1$s
- Ingen streamafspiller blev fundet. Installer VLC\?
+ Udgivet den %1$s
+ Ingen streamafspiller blev fundet. Installér VLC\?
Ingen streamafspiller fundet (du kan installere VLC for at afspille den).
Installer
Annuller
@@ -13,13 +13,13 @@
Download stream-fil
Søg
Indstillinger
- Mente du: %1$s\?
+ Mente du \"%1$s\"\?
Del med
Benyt ekstern videoafspiller
- Fjerner lyd ved NOGLE opløsninger
+ Fjerner lyd ved nogle opløsninger
Brug ekstern lydafspiller
Abonner
- Abonneret
+ Abonnerer
Afmeld abonnement
Abonnement afmeldt
Kunne ikke ændre abonnement
@@ -33,17 +33,17 @@
Pop op
Føj til
Placering af videodownloads
- Mappe som videoer skal downloades til
- Angiv downloadmappe for videoer
+ Downloadede videoer gemmes her
+ Angiv downloadmappe for videofiler
Downloadmappe for lydfiler
- Downloadede lydfiler bliver gemt her
+ Downloadede lydfiler gemmes her
Angiv downloadmappe for lydfiler
Standardopløsning
Standardopløsning for pop op
Vis højere opløsninger
- Ikke alle enheder understøtter afspilning af 2K/4K-videoer
+ Kun nogle enheder kan afspille 2K-/4K-videoer
Afspil med Kodi
- Kore-appen ikke fundet. Installer den\?
+ Installer manglede Kore-app\?
Vis valgmuligheden \"Afspil med Kodi\"
Vis en knap til at afspille en video via Kodi
Lyd
@@ -56,30 +56,30 @@
Husk størrelse og placering af pop op
Husk sidste størrelse og placering af pop op-afspiller
Brug hurtig og upræcis søgning
- Upræcis søgning lader afspilleren finde placeringer hurtigere, men mindre præcist
+ Upræcis søgning lader afspilleren finde placeringer hurtigere, men mindre præcist. Søgninger på 5, 15 eller 25 sekunder fungerer ikke med denne indstilling, slået til
Indlæs miniaturebilleder
- Slå fra for at undgå indlæsning af billeder, hvorved der spares data og hukommelse. Ændringer sletter billedcachen i både ram og lager.
+ Slå fra for at undgå indlæsning af billeder, hvorved der spares data og hukommelse. Ændringer sletter billedcachen i både ram og lager
Billedcache slettet
Slet metadata-cachen
Slet alle websidedata fra cachen
Metadata-cache slettet
Føj automatisk næste stream til køen
- Føj automatisk relaterede streams til køen når den sidste stream i en ikke-repeterende kø afspilles.
+ Fortsæt nedlukningen af en (ikke-gentagende) playback kø ved at tilføje et relateret stream
Juster lydstyrke ved hjælp af fingerbevægelser
- Brug fingerbevægelser til at kontrollere lydstyrke
+ Brug fingerbevægelser til at kontrollere afspillerens lydstyrke
Styr lysstyrken med fingerbevægelser
Brug fingerbevægelser til at justere afspillerens lysstyrke
Søgeforslag
- Vis forslag når der søges
+ Vælg forslagene, der vises, når der søges
Søgehistorik
Gem søgninger lokalt
- Historik og cache
+ Visningshistorik
Husk sete videoer
- Fortsæt når appen kommer i fokus
+ Fortsæt afspilning
Fortsæt afspilning efter afbrydelser (fx telefonopkald)
Download
Vis \'Næste\' og \'Lignende\' videoer
- Vis \"Hold for at tilføje\"-tip
+ Vis \"Hold for at sætte i kø\"-tip
Vis et tip når der trykkes på baggrunds- eller pop op-knappen på siden med videodetaljer
Denne webadresse er ikke understøttet
Standardland for indhold
@@ -94,8 +94,8 @@
Afspiller i baggrunden
Afspiller i pop op-tilstand
Indhold
- Aldersbegrænset indhold
- LIVE
+ Vis aldersbegrænset indhold
+ Live
Downloads
Downloads
Fejlrapport
@@ -117,29 +117,29 @@
Altid
Kun én gang
Fil
- NewPipe-notifikation
- Notifikationer for NewPipes baggrunds- og pop op-afspillere
+ NewPipe notifikation
+ Notifikationer for NewPipes afspiller
Notifikation om opdatering af app
- Notifikationer for nye NewPipe-versioner
+ Notifikationer for nye NewPipe versioner
[Ukendt]
Skift til baggrund
Skift til pop op
Skift til hovedafspiller
Importer database
Eksporter database
- Overskriver din nuværende historik og abonnementer
- Eksporter historik, abonnementer og spillelister
+ Overskriver din nuværende historik, abonnementer, spillelister og (hvis det ønskes) indstillinger
+ Eksporter historik, abonnementer, spillelister og indstillinger
Slet visningshistorik
- Sletter historikken for viste videoer
+ Sletter historikken og positioner af tidligere viste videoer
Slet hele visningshistorikken\?
- Visningshistorikken blev slettet.
+ Visningshistorikken blev slettet
Slet søgehistorik
Sletter historikken for søgeord
Slet hele søgehistorikken\?
- Søgehistorik slettet.
+ Søgehistorikken blev slettet
Fejl
Eksternt lager utilgængeligt
- Download til eksternt SD-kort er endnu ikke muligt. Nulstil placering af download-mappe\?
+ Det er endnu ikke muligt at downloade til et eksternt SD-kort. Nulstil download-mappens placering\?
Netværksfejl
Kunne ikke indlæse alle miniaturebilleder
Kunne ikke dekryptere URL-signatur for video
@@ -161,16 +161,16 @@
Ingen streams er tilgængelige for download
Bruger standardfaner pga. fejl ved indlæsning af gemte faner
Genskab standardindstillinger
- Vil du genskabe standardindstillingerne\?
+ Vil du genoprette standardindstillingerne\?
Undskyld, dette skulle ikke være sket.
- Rapporter fejl via e-mail
- Undskyld, nogle fejl opstod.
- RAPPORTER
+ Rapporter denne fejl via e-mail
+ Beklager, noget gik galt.
+ Rapporter
Information:
Hvad skete der:
Din kommentar (på engelsk):
Detaljer:
- Videominiaturebillede
+ Afspil video, længde:
Uploaders profilbillede
Synes godt om
Kan ikke lide
@@ -201,8 +201,9 @@
Tryk for detaljer
Vent venligst…
Kopieret til udklipsholderen
- Vælg venligst en tilgængelig downloadmappe
- Denne tilladelse er nødvendig for at kunne åbne i pop op-tilstand
+ Vælg senere en tilgængelig downloadmappe i indstillingerne
+ Denne tilladelse behøves for
+\nat åbne i pop op-tilstand
1 element slettet.
reCAPTCHA-udfordring
Der blev anmodet om en reCAPTCHA-udfordring
@@ -223,7 +224,7 @@
Hvad enten du har idéer til oversættelse, designændringer, kodeoprydning eller virkelig tunge kodeændringer, så er hjælp altid velkommen. Jo mere der bliver gjort, jo bedre bliver det!
Se på GitHub
Doner
- NewPipe er udviklet af frivillige der bruger tid på at give dig den bedste oplevelse. Giv noget tilbage for at hjælpe NewPipes udviklere til at gøre appen endnu bedre, mens de nyder en kop kaffe.
+ NewPipe er udviklet af frivillige, der bruger deres fritid på at give dig den bedst mulige brugeroplevelse. Giv noget tilbage for at hjælpe NewPipes udviklere til at gøre appen endnu bedre, mens de nyder en kop kaffe.
Giv noget tilbage
Websted
Besøg NewPipes websted for mere information og nyheder.
@@ -292,7 +293,7 @@
Færdig
Afventning
efterbehandling
- Kø
+ Læg i kø
Handling afvist af systemet
Download fejlede
Generer unikt navn
@@ -303,9 +304,9 @@
Vis fejl
Filen kan ikke oprettes
Destinationsmappen kan ikke oprettes
- Sikker forbindelse fejlede
+ Kunne ikke etablere en sikker forbindelse
Kunne ikke finde serveren
- Kan ikke forbinde til serveren
+ Kan ikke oprette forbindelse til serveren
Serveren sender ikke data
Serveren accepterer ikke multitrådede downloads; prøv igen med @string/msg_threads = 1
Ikke fundet
@@ -313,7 +314,7 @@
Stop
Hændelser
Intet at se her
- T
+ t
mio.
mia.
@@ -324,18 +325,18 @@
Kunne ikke importere abonnementer
Kunne ikke eksportere abonnementer
Konferencer
- Start her når i baggrunden
- Start her ved ny pop op
+ Start afspilningen i baggrunden
+ Start afspilning i et pop op
Åbn skuffe
Luk skuffe
- Hvad:\\nForespørgsel:\\nIndholdssprog:\\nTjeneste:\\nGMT-tid:\\nPakke:\\nVersion:\\nOS-version:
- Standardhandling ved åbning af indhold — %s
- Angiv som miniaturebillede for spilleliste
+ Hvad:\\nForespørgsel:\\nIndholdssprog:\\nIndholdsland:\\nAppsprog:\\nTjeneste:\\nGMT-tid:\\nPakke:\\nVersion:\\nOS-version:
+ Standardhandling når indhold åbnes – %s
+ Anvend som playlistens miniature
Bogmærk spilleliste
Fjern bogmærke
Føjet til spillelisten
Miniaturebillede for spilleliste ændret.
- Ændr undertekststørrelse og baggrundsstil. Kræver genstart af appen for at træde i kraft.
+ Ændr undertekststørrelse og baggrundsstil. Kræver genstart af appen for at træde i kraft
Monitorering for hukommelseslækager kan få appen til ikke at svare under heap dumping
Rapporter out-of-lifecycle-fejl
Importer
@@ -348,7 +349,11 @@
\n
\n1. Gå til denne webadresse: %1$s
\n2. Log ind når du bliver bedt om det
-\n3. En download bør starte (det er eksportfilen)
+\n3. Klik på \"Alle Youtube-data medtages\" og fravælg alt bortset fra \"abonnementer\".
+\n4. Klik på \"Næste trin\" og derefter \"Opret eksport\".
+\n5. Klik på \"Download\" knappen efter den popper frem.
+\n6. Klik på \"IMPORTER FIL\" nederst på denne side og vælg den downloadede .zip fil.
+\n7. [Såfremt .zip-importeringen slår fejl] Uddrag .csv filen (som normalt findes i \"YouTube og YouTube Music/abonnementer/abonnementer.csv\"). Klik på \"IMPORTER FIL\" nederst på denne side, og vælg den uddragede .csv fil
ditID, soundcloud.com/ditID
Bemærk at denne operation kan kræve meget netværkstrafik.
\n
@@ -364,37 +369,288 @@
Ingen
Minimer til baggrundsafspiller
Minimer til pop op-afspiller
- NewPipe-opdatering tilgængelig!
+ En NewPipe-opdatering er tilgængelig!
sat på pause
sat i kø
Maksimalt antal genforsøg
Maksimalt antal forsøg før downloaden opgives
- Sæt på pause ved skift til mobildata
- Downloads som ikke kan sættes på pause vil blive genstartet
- Kun HTTPS URL-er understøttet
+ Afbryd på forbrugsafregnede netværk
+ Nyttigt ved skift til mobildata, selv om nogle downloads ikke kan sættes på pause
+ Kun HTTPS adresser understøttes
Instansen findes allerede
- Kunde ikke bekræfte instans
- Skriv ind instans-URL
+ Kunne ikke validere instansen
+ Skriv instansens adresse
Tilføj instans
- Finn instanserne du liger på %s
- Vælg dine favorit-PeerTube-instanser
+ Find de instanserne du kan lide på %s
+ Vælg dine yndlings PeerTube-instanser
PeerTube-instanser
- Automatisk afspilning
- Tøm data
+ Afspil automatisk
+ Ryd data
Positioner i lister
Genopret forrige afspilningsposition
Fortsæt afspilning
- Skru av for at skjule kommentarer
+ Slå fra for at skjule kommentarer
Vis kommentarer
Ingenting
- Gentagelse
+ Gentag
Femte handlingstast
Fjerde handlingstast
Første handlingstast
- Andre handlingstast
+ Anden handlingstast
Tredje handlingstast
Viser resultater for: %s
Åben med
LeakCanary er ikke tilgængelig
Markér som set
+ Beskrivelse
+ Kapitler
+ Notifikationer
+ Hjælp
+ Kunstnere
+ Luk
+ Radio
+ Kategori
+ Privatliv
+ Procent
+ Ny
+ Systemstandard
+ Album
+ Sange
+ Videoer
+ Vis ikke
+ Bland
+ Vis beskrivelse
+ Åbn hjemmeside
+ Sprog
+ Lav kvalitet (mindre)
+ Start afspilning automatisk — %s
+ Aldrig
+ Kun på Wi-Fi
+
+ - %d sekund
+ - %d sekunder
+
+
+ - %1$s download slettet
+ - %1$s downloads slettet
+
+ Slet alle downloadede filer fra drevet\?
+ Sæt downloads på pause
+ Start hovedafspilleren i fuldskærmstilstand
+ Downloadmappe endnu ikke valgt. Vælg standardmappen nu
+ Læg automatisk i kø
+ Konfigurer det spillende streams notifikation
+ Vis aldersbegrænset indhold (f.eks. 18+)
+ Slå YouTube \"begrænset tilstand\" til
+ YouTube har en \"begrænset tilstand\" der skjuler videoer som potientielt er skadelige for børn
+ Denne video er aldersbegrænset.
+\n
+\nSlå \"%1$s\" fra i indstillingerne hvis du vil se den.
+ Nye streams
+ Notifikationer om nye streams fra abonnementer
+ reCAPTCHA cookies er ryddet
+ Slet alle playback positioner\?
+ Filen er flyttet eller slettet
+ NewPipe stødte ind i en fejl, tryk for at rapportere
+ Rapporter på GitHub
+ Høj kvalitet (større)
+ Begræns downloadkøen
+ Ryd de cookies som NewPipe opbevarer når du løser en reCAPTCHA
+ Farvelæg notifikationen
+ Afspillernotifikation
+ En fejl opstod, se notifikationen
+ Slå fra for at skjule videobeskrivelsen og yderligere information
+ Slå fra for at gemme metainformationskasser med yderligere information om streammets skaber, streammets indhold eller en søgeforespørgsel
+
+ - Download fuldført
+ - %s downloads fuldført
+
+ Lav indlæsningsintervallets størrelse, (som nu ligger på %s) om. En højere værdi kan øge videoindlæsningshastigheden. Ændringer af værdien kræver genstart.
+ Den aktive spilleliste bliver udskiftet
+ At skifte fra en afspiller til en anden kan udskifte din kø
+ Vis metainformation
+ Lokale søgeforslag
+ Fjerne søgeforslag
+ Start ikke videoer i miniafspilleren, men gå direkte til fuldskærmstilstand, hvis automatisk rotering er låst. Du kan stadig se miniafspilleren, hvis du går ud af fuldskærmstilstand
+ Kunne ikke genkende addressen. Vil du åbne den i en anden app\?
+ Videohashfunktion notifikation
+ Notifikationer om videohashfunktioners status
+ Fejlrapport-notifikation
+ Notifikationer for at rapportere fejl
+ Slet playback positioner
+ Sletter alle playback positioner
+ Spørg hvor filen skal downloades
+ Et download ad gangen
+ Slet downloadede filer
+ Vil du rydde din download historik eller slette alle downloadede filer\?
+ Kan ikke gendanne dette download
+ Ryd download historik
+ NewPipe projektet tager dit privatliv seriøst. Derfor samler appen intet data uden dit samtykke.
+\nNewPipes fortrolighedspolitik forklarer i detaljer, hvilke data der bliver sendt og opbevaret når du sender en nedbrudsrapport.
+ Kopier en formatteret rapport
+ Giv tilladelse til at vise over andre apps
+ Vis playback positionsvisere i lister
+ Playback positioner slettet
+ Ryd reCAPTCHA cookies
+ Der er en afventende download med dette navn
+ Start downloads
+ Skaler miniaturebilledet til 1:1 format
+ Skaler notifikationsminiaturebillederne fra 16:9 til 1:1 format (dette kan medføre forvrængninger)
+ Rediger hver eneste varselshandling nedenunder ved at trykke på dem. Vælg op til tre af dem som bliver vist i den lille notifikation, via kasserne til højre
+ Du kan kun vælge op til tre handlinger som kan vises i den lille notifikation!
+ Buffer
+ Få Android til at vælge notifikationens farve ud fra den primære farve i miniaturebilledet (virker ikke på alle enheder)
+ Nattetema
+ Frem- og tilbagesøgningstid
+ Denne video er aldersbegrænset.
+\nPga. YouTubes politik om aldersbegrænsede videoer har NewPipe ikke adgang til videoen.
+ Crash afspilleren
+ Spørg om bekræftelse før du tømmer en kø
+ Forhåndsvisning af miniaturebilleder på statuslinjen
+ Sæt i kø som næste
+ Er sat som næste i køen
+ Download er begyndt
+ Vis miniaturebilleder på både låseskærmen og notifikationer
+ Nylige
+ Notifikationer er slået fra
+ Kommentarer
+ Relaterede objekter
+ Stryg på elementer for at fjerne dem
+ Vælg en playliste
+ Ingen playliste bogmærker endnu
+ Sproget ændres når appen genstarter
+ Spillekø
+ Vis kanalens detaljer
+ Sæt i kø
+ Sat i kø
+ Loader streamets detaljer…
+ Processere... Det kan tage et øjeblik
+ Vis hukommelsestab
+ Deaktiver medietunneler
+ Vis billedindikatorer
+ Netværkskrav
+ Alle netværk
+ Kontrolfrekvens
+ Notifikationer ved nye streams
+ Notifikationer om ny streams fra abonnomenter
+ Tjek manuelt efter opdateringer
+ Tjekker efter opdateringer…
+ Gendanner
+ \"Hurtig feed\"-tilstand viser ikke mere information om dette.
+ Tjek efter opdateringer
+ Fjern sete videoer\?
+ Deaktiver medietunneler hvis du oplever en sort skærm eller hak ved videoafspilning
+ Venligst tjek om der allerede eksisterer en problemrapport som diskuterer dit crash. Hvis du opretter duplikatrapporter, tager du tid fra os som vi kunne bruge på at fikse fejlen.
+ Tjek efter nye streams
+ Lav en fejlnotifikation
+ Lokale
+ Udgiverens bruger er blevet slettet.
+\nNewpipe kan ikke indlæse dette feed i fremtiden.
+\nVil du fjerne dit abonnement på denne kanal\?
+ Feedet blev sidst opdateret for %s
+ Ikke indlæst: %d
+ Indlæser feed…
+ Nye feed elementer
+ Tid siden sidste opdatering for at et abonnoment bliver forældet - %s
+ Altid opdater
+ Vælg abonnementer
+ Vis sete elementer
+ Dette indhold er ikke tilgængeligt i dit land
+ Af %s
+ Videoer på playlisten som allerede er blevet set fjernes.
+\nDette kan ikke fortrydes!
+ Vis miniaturebillede
+ Tags
+ Aldersbegrænsning
+ Dette indhold er ikke understøttet af NewPipe.
+\n
+\nVi håber at kunne understøtte det i en fremtiden.
+ Dette indhold er kun tilgængeligt for brugere som har betalt for det. Det kan ikke blive streamet eller downloadet af NewPipe.
+ Bruger slettet
+ Dette indhold er privat, så det jan ikke blive streamet eller downloadet af NewPipe.
+ Nyligt tilføjede
+ Fremhævede
+ %s giver denne grund:
+
+ - %s lytter
+ - %s lyttere
+
+ 100+ videoer
+ Udregner hash
+ Løs
+ Ingen abonnementer valgt
+ Vil du slette denne gruppe\?
+ Licens
+
+ - %s nyt stream
+ - %s nye streams
+
+ Semitone
+
+ - %d time
+ - %d timer
+
+
+ - %d dag
+ - %d dage
+
+ Lavet af %s
+ Slå hurtigtilstand fra
+ Slå hurtigtilstand til
+ Hent fra det dedikerede feed når det er muligt
+ Feed opdateringsgrænse
+ Feed
+
+ - %d valgt
+ - %d valgte
+
+ Processerer feed…
+ Kanalgrupper
+
+ - %d minut
+ - %d minutter
+
+ Fjern sete
+ Vælg en instans
+ Forbindelse afbrudt
+ Fremskridt tabt fordi filen blev slettet
+ NewPipe blev lukket under arbejde på filen
+ Kan ikke overskrive filen
+ For at være i overenstemmelse med GDPR fanger vi din opmærksomhed hentil NewPipes privatpolitik. Venligst læs den med omhu.
+\nDu skal acceptere den for at sende os en fejlrapport.
+ Aflænk (kan skabe forvrængning)
+ Importer en SoundCloud profil ved at skrive enten dit URL eller ID:
+\n
+\n1. Slå \"desktop-version\" til i mobilbrowsere.
+\n2. Gå til denne adresse: %1$s
+\n3. Log ind når du bliver spurgt
+\n4. Kopier adressen på den profil du bliver henstillet til.
+ Vis den oprindelige tidsforskel på elementer
+ Autogenereret (ingen uploader fundet)
+ Slå lyd til
+ Sæt på lydløs
+ Mest likede
+ Kunne ikke indlæse kommentarer
+ Standard Kiosk
+ Færdig
+ Tryk på \"Færdig\" når den er løst
+ Ingen kommentarer
+ ∞ videoer
+ Ingen lyttere
+
+ - %s seer
+ - %s seere
+
+ Ingen seere
+ Skift service, nuværende valg:
+ Kommentarer er slået fra
+ Ingen apps på din enhed kan åbne dette
+ Ingen ledig plads på enheden
+ App sprog
+ Ja, og delvist sete videoer
+ Fejl ved indlæsning af feed
+ Kunne ikke indlæse feed for \'%s\'.
+ Vis \"crash afspilleren\"
+ Crash appen
+ Vis et crash alternativ når afspilleren er i brug
\ No newline at end of file
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 1b47fba2f..b91fec84a 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -1,7 +1,7 @@
Veröffentlicht am %1$s
- Kein Stream-Player gefunden. Möchtest du VLC installieren\?
+ Kein Stream-Player gefunden. Möchtest du den VLC installieren\?
Installieren
Abbrechen
Im Browser öffnen
@@ -373,7 +373,7 @@
Nachbearbeitung
In Wiedergabe einreihen
System verweigert den Zugriff
- Herunterladen fehlgeschlagen
+ Download fehlgeschlagen
Eindeutigen Namen erzeugen
Überschreiben
Eine Datei mit diesem Namen existiert bereits
@@ -684,12 +684,9 @@
\nBitte installiere einen Dateimanager oder versuche, \'%s\' in den Downloadeinstellungen zu deaktivieren.
Es wurde kein geeigneter Dateimanager für diese Aktion gefunden.
\nBitte installiere einen Storage Access Framework kompatiblen Dateimanager.
- Wird bereits im Hintergrund abgespielt
Angehefteter Kommentar
LeakCanary ist nicht verfügbar
- Tonhöhe nach musikalischen Halbtönen anpassen
Ändern der Größe des Ladeintervalls (derzeit %s). Ein niedrigerer Wert kann das anfängliche Laden des Videos beschleunigen. Änderungen erfordern einen Neustart des Players.
- Geschwindigkeitsstufe
ExoPlayer Standard
Benachrichtigungen
Benachrichtigen über neue abonnierbare Streams
@@ -710,5 +707,15 @@
Alle heruntergeladenen Dateien von der Festplatte löschen\?
Du hast jetzt diesen Kanal abonniert
Alle umschalten
- Aktualisierungsintervall
+ Prüfintervall
+ Prozent
+ Halbton
+ Keine Videostreams für externe Player verfügbar
+ Qualität für externe Player auswählen
+ Unbekanntes Format
+ Keine Audiostreams für externe Player verfügbar
+ Unbekannte Qualität
+ Streams, die noch nicht vom Downloader unterstützt werden, werden nicht angezeigt
+ Der ausgewählte Stream wird von externen Playern nicht unterstützt
+ Größe des Ladeintervalls für die Wiedergabe
\ No newline at end of file
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
index b54b34d60..06c7e4ae4 100644
--- a/app/src/main/res/values-el/strings.xml
+++ b/app/src/main/res/values-el/strings.xml
@@ -682,11 +682,8 @@
\nΕγκαταστήστε έναν συμβατό με το Πλαίσιο Πρόσβασης Αποθήκευσης.
Το NewPipe παρουσίασε ένα σφάλμα. Πατήστε για αναφορά
Εμφάνιση μιας snackbar σφάλματος
- Αναπαράγεται ήδη στο παρασκήνιο
Καρφιτσωμένο σχόλιο
Το LeakCanary δεν είναι διαθέσιμο
- Προσαρμόστε τον τόνο με βάση τα μουσικά ημιτόνια
- Βήμα τέμπο
Εξ\' ορισμού ExoPlayer
Αλλάξτε το μέγεθος του διαστήματος φόρτωσης (επί του παρόντος είναι %s). Μια χαμηλότερη τιμή μπορεί να επιταχύνει την αρχική φόρτωση βίντεο. Οι αλλαγές απαιτούν επανεκκίνηση της εφαρμογής.
Ειδοποιήσεις
@@ -711,4 +708,13 @@
Λάβετε ειδοποίηση
Έχετε εγγραφεί τώρα σε αυτό το κανάλι
Εναλλαγή όλων
+ Τοις εκατό
+ Ημιτόνιο
+ Οι ροές που δεν υποστηρίζονται ακόμα από τον λήπτη δεν εμφανίζονται
+ Η επιλεγμένη ροή δεν υποστηρίζεται από εξωτερικούς αναπαραγωγούς
+ Δεν διατίθενται ροές ήχου για εξωτερικούς αναπαραγωγούς
+ Δεν διατίθενται ροές βίντεο για εξωτερικούς αναπαραγωγούς
+ Επιλογή ποιότητας εξωτερικών αναπαραγωγών
+ Άγνωστος τύπος αρχείου
+ Άγνωστη ποιότητα
\ No newline at end of file
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index f386801e2..51d61c92d 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -292,7 +292,7 @@
Quitar todos los datos guardados de páginas web
Se vació la caché de metadatos
Controles de velocidad de reproducción
- Tiempo
+ Tempo
Tono
Desvincular (puede causar distorsión)
No hay streams disponibles para descargar
@@ -558,7 +558,7 @@
Almacenar en memoria (búfer)
Repetir
¡Puedes seleccionar como máximo tres acciones para mostrar en la notificación compacta!
- Edita cada acción de la notificación pulsando sobre ella. Selecciona hasta tres de ellas para mostrarlas en la notificación compacta usando las casillas de verificación a la derecha
+ Edita cada acción de notificación debajo pulsando sobre ella. Selecciona hasta tres de ellas para que aparezcan en la notificación compacta usando las casillas de verificación a la derecha
Botón de quinta acción
Botón de cuarta acción
Botón de tercera acción
@@ -686,14 +686,38 @@
No se encontró ningún gestor de archivos adecuado para esta acción.
\nPor favor instale un gestor de archivos compatible con \"Sistema de Acceso al Almacenamiento\".
Comentario fijado
- Ya se reproduce en segundo plano
LeakCanary no está disponible
ExoPlayer valor por defecto
- Paso de tempo
Cambia el tamaño del intervalo de carga (actualmente %s). Un valor más bajo puede acelerar la carga inicial del vídeo. Los cambios requieren un reinicio del reproductor.
- Ajustar el tono por semitonos musicales
Notificaciones
Nuevos streams
Notificación del reproductor
Configurar notificación de la reproducción en curso
+ Ahora estás suscrito a este canal
+ Cualquier red
+ ,
+ Comprobar la existencia de nuevos directos
+ Notificaciones de nuevos directos
+ Notificar de nuevos directos desde las suscripciones
+ Frecuencia de comprobación
+ ¿Desea borrar del disco todos los archivos descargados\?
+ Las notificaciones están desactivadas
+ Recibir notificaciones
+ Conmutar todo
+ Cargando detalles del directo…
+ Notificaciones sobre nuevos directos para suscriptores
+ Se requiere conexión a red
+
+ - %s nuevo directo
+ - %s nuevos directos
+
+ Porcentaje
+ Semitono
+ No se muestran flujos cuya descarga aún no está soportada
+ El flujo seleccionado no es soportado por reproductores externos
+ No hay flujos de audio disponibles para reproductores externos
+ No hay flujos de video disponibles para reproductores externos
+ Elija la calidad para reproductores externos
+ Formato desconocido
+ Calidad desconocida
\ No newline at end of file
diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml
index d04834f8a..7cb96405a 100644
--- a/app/src/main/res/values-et/strings.xml
+++ b/app/src/main/res/values-et/strings.xml
@@ -2,8 +2,8 @@
Alustamiseks toksa suurendusklaasi ikooni.
Avaldatud %1$s
- Voogesituseks puudub pleier. Kas paigaldada VLC?
- Voogesituseks puudub pleier (selleks võib paigaldada VLC).
+ Voogesituseks puudub pleier. Kas paigaldame VLC\?
+ Voogesituseks puudub pleier (selleks võid paigaldada VLC).
Paigalda
Tühista
Ava veebilehitsejas
@@ -305,7 +305,7 @@
reCAPTCHA nõue
reCAPTCHA nõude taotlus
© %1$s %2$s %3$s alla
- Vaba kergekaaluline Androidi voogesitus.
+ Vaba ja lihtne voogesitus Androidis.
Kui sul on ideid kujunduse muutmisest, koodi puhastamisest või suurtest koodi muudatustest - abi on alati teretulnud. Mida rohkem tehtud, seda paremaks läheb!
NewPipe arendajad on vabatahtlikud, kes kulutavad oma vaba aega, toomaks sulle parimat kasutamise kogemust. On aeg anda tagasi aidates arendajaid ja muuta NewPipe veel paremaks, nautides ise tassi kohvi.
Anneta
@@ -674,7 +674,6 @@
NewPipe töös tekkis viga, sellest teavitamiseks klõpsi
Jooksuta meediamängija kokku
Näita veateate akent
- Meedia esitamine taustal toimib juba
Teavitus vigadest
Teavitused vigadest informeerimiseks
Tekkis viga, vaata vastavat teadet
@@ -709,6 +708,13 @@
Sa oled nüüd selle kanali tellija
,
Lülita kõik sisse
- Reguleeri helikõrgust muusikaliste pooltoonide kaupa
- Tempo samm
+ Välise pleieri jaoks ei leidu sobilikke helivoogusid
+ Need meediavood, mida allalaadija ei oska kasutada, on peidetud
+ Välise pleieri jaoks ei leidu sobilikke videovoogusid
+ Vali välis pleieri jaoks sobilik kvaliteet
+ Tundmatu vorming
+ Teadmata kvaliteet
+ Valitud meediavood ei ole toetatud välises pleieris
+ Protsent
+ Pooltoon
\ No newline at end of file
diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml
index 1c710641e..80e2d5a04 100644
--- a/app/src/main/res/values-eu/strings.xml
+++ b/app/src/main/res/values-eu/strings.xml
@@ -666,7 +666,6 @@
Gehitu bideo hau isatsari
Erakutsi \"Itxi erreproduzigailua\"
Prozesatzen... Itxoin mesedez
- Atzeko planoan erreproduzitzen dagoeneko
Erroreen txostenen jakinarazpena
Jakinarazpenak erroreen berri emateko
NewPipe-k errore bat aurkitu du, sakatu berri emateko
@@ -695,8 +694,6 @@
Kanal honetara harpidetu zara
,
Txandakatu denak
- Doitu tonua semitono musikalen arabera
- Tempo urratsa
Aldatu karga maiztasun tamaina (unean %s). Balio txikiago batek bideoaren hasierako karga azkartu dezake. Erreproduzigailuaren berrabiarazte bat behar du.
Harpidetzen jario berriei buruz jakinarazi
Ezabatu deskargatutako fitxategi guztiak biltegitik\?
@@ -711,4 +708,6 @@
Jakinarazi
ExoPlayer lehenetsia
Beharrezko sare konexioa
+ Portzentaia
+ Semitonoa
\ No newline at end of file
diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml
index d0f4d3d84..a00c64ad0 100644
--- a/app/src/main/res/values-fa/strings.xml
+++ b/app/src/main/res/values-fa/strings.xml
@@ -683,10 +683,7 @@
نیوپایپ به خطایی برخورد. برای گزارش، بزنید
خطایی رخ داد. آگاهی را ببینید
نظر سنجاق شده
- در حال پخش در پسزمینه
لیککاناری موجود نیست
- گام سرعت
- تنظیم زیر و بم با شبهتنها
تغییر اندازهٔ بازهٔ بار (هماکنون %s). مقداری پایینتر، میتواند بار کردن نخستین ویدیو را سرعت بخشد. تغییرها نیاز به یک آغاز دوبارهٔ پخشکننده دارند.
پیشگزیدهٔ اگزوپلیر
آگاهیها
@@ -711,4 +708,13 @@
پاک کردن تمامی پروندههای بارگرفته از دیسک؟
هر شبکهای
اکنون مشترک این کانال شدهاید
+ نیمپرده
+ درصد
+ هیچ جریان ویدیوییای برای پخشکنندههای خارجی موجود نیست
+ جریانهایی که هنوز به دست بارگیر پشتیبانی نمیشوند نشان داده نشدهاند
+ هیچ جریان صوتیای برای پخشکنندههای خارجی موجود نیست
+ جریان گزیده به دست پخشکنندههای خارجی پشتیبانی نمیشود
+ گزینش کیفیت برای پخشکنندههای خارجی
+ قالب ناشناخته
+ کیفیت ناشناخته
\ No newline at end of file
diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml
index b4e807e5e..8eef663b0 100644
--- a/app/src/main/res/values-fi/strings.xml
+++ b/app/src/main/res/values-fi/strings.xml
@@ -684,4 +684,5 @@
Näytä soitinta käytettäessä soittimen kaatamisen vaihtoehto
Näytä virheen ponnahdusilmoitus
Uudet syötteet
+ Ilmoitukset
\ No newline at end of file
diff --git a/app/src/main/res/values-fil/strings.xml b/app/src/main/res/values-fil/strings.xml
index 1795fc98c..d8c6682d6 100644
--- a/app/src/main/res/values-fil/strings.xml
+++ b/app/src/main/res/values-fil/strings.xml
@@ -1,6 +1,6 @@
- Pindutin ang magnifying glass para makapagsimula.
+ Pindutin ang magnifying glass upang magsimula.
Inilathala noong %1$s
Walang nakitang stream player. I-install ang VLC\?
Walang nakitang stream player (pwede mong i-install ang VLC para ma-play ito).
@@ -13,7 +13,7 @@
I-download ang stream file
Maghanap
Ayos ng App
- Ibig mo bang sabihin \"%1$s\"\?
+ \"%1$s\" ba ang tinutukoy mo\?
Ibahagi sa
Gumamit ng ibang video player
Natatanggal ang tunog sa ilang mga resolusyon
@@ -42,7 +42,7 @@
Ilalagay rito ang mga na-download na video file
Download folder ng mga audio
Ipinapakita ang mga resulta para sa: %s
- Buksan gamit ng
+ Buksan gamit ang
Pang-apat na action button
Ipakita ang \"I-play gamit Kodi\"
Pangalawang action button
@@ -58,7 +58,7 @@
Tema
Lokal na mungkahi
Remote na mungkahi
- Markahang napanood
+ Markahan bilang napanood
Kusang ipila
Ulitin
Halo-halo
@@ -116,6 +116,145 @@
Gamitin ang mabilis ngunit di-saktong seek
Haba ng fast forward/-rewind seek
Ituloy ang pagpapalabas
- Mga Patok Ngayon
+ Patok Ngayon
Subaybayan ang mga napanood nang video
+ Kumakailan
+ Kategorya
+ Mga Tag
+ Praybasi
+ Nakapatay ang Mga Notipikasyon
+ Publiko
+ Mga Puna
+ Nagda-dawnload ang NewPipe
+ Naka-bukas
+ Wika ng App
+ Tungkol dito
+ Ang video na ito ay may paghihigpit sa edad.
+\nDahil sa mga bagong polisiya ng Youtube, hindi maaring ma-access ng NewPipe ang mga video streams nito, kaya hindi ito maipapalabas.
+ Notipikasyon sa NewPipe
+
+ - %s nakikinig
+ - Mga %s nakikinig
+
+ 100+ na mga video
+ ∞ na mga video
+ Walang Komento
+ Ipalabas Lahat
+ Mga Notipikasyon
+ Pinagbabawal ang pagkomento
+ Mga pinahihintulutang karakter sa pangalan ng file
+ Mga Lisensya
+ Kasaysayan
+ Laman ng pangunahing pahina
+ Live
+ Palagi
+ Gusto mo bang burahin ito sa kasaysayan ng paghanap\?
+ Laman
+
+ - %d oras
+ - mga %d oras
+
+ Karaniwan ng Sistema
+ Nakapribado ang content na ito, kaya hindi ito maipalabas o mai-download ng NewPipe.
+ Sinisimulan na ang Pagdownload
+ Pinusuan ng creator
+ Ni %s
+ Tulong
+ Naka-pin na komento
+ Buksan ang website
+ Lisensya
+ Wika
+ Pribado
+ URL ng Thumbnail
+ Hindi nakalista
+ Nakapatay
+ Bago at patok
+ Panimulang wika ng content
+ Itsura ng App
+
+ - %d segundo
+ - Mga %d segundo
+
+ Nilikha ni %s
+ Mga Kabanata
+ Walang app sa device mo ang makakabukas nito
+ Tampok
+ Huling Pinanood
+ Kasaysayan
+ Walang nakikinig
+ Walang nahanap
+ Pumalit sa Likuran
+ Linisin ang kasaysayan ng panonood
+
+ - %s nanonood
+ - Mga %s nanonood
+
+ Walang mga video
+ Madalas na Pinanood
+
+ - %s video
+ - Mga %s na video
+
+
+ - %d minuto
+ - Mga %d minuto
+
+
+ - %d araw
+ - mga %d araw
+
+ Buksan ang pangunahing player sa fullscreen
+ Itakda ang kasalukuyang notipikasyon ng playing stream
+ Mga download
+ Ulat sa problema
+ Mga channel
+ Mga listahan ng nilalaman
+ Mga bidyo
+ Mga pangyayari
+ Mga album
+ Naka-disable
+ Alisin
+ Pinakamainam na resolusyon
+ Natanggal ang file
+ Isang Beses Lang
+ Mga HTTPS URL lang ang suportado
+ Mga kanta
+ Awtopaandar
+ Mahahanap mo sa %s ang hilig mong mga instansya
+ Patuloy na mag-play pagkatapos ng istorbo (hal. tawag sa telepono)
+ I-download
+ Huwag buksan ang mga video sa mini player at dumiretso na sa fullscreen mode kung naka-lock ang awto rotasyon. Magagamit mo pa rin ang mini player kung aalis ka sa fullscreen
+ Ipakita ang tip ng \"I-hold para ipila\"
+ Ipakita ang tip tuwing pinpindot ang background o ang buttong pumapop-up sa video na \"Details:\"
+ Hindi makilala ang URL. Buksan sa ibang app\?
+ Mga instansya ng PeerTube
+ Ang video na ito ay may paghihigpit sa edad.
+\n
+\nBuksan ang \"%1$s\" sa ayos ng app kung gusto mong makita ito.
+ Magdagdag ng mga instansya
+ Hindi maberipika ang instansya
+ Naidagdag na ang instansyang iyan
+ Ugali
+ Bidyo at tunog
+ Kasaysayan at cache
+ Mga update
+ Ipakita ang nilalamang hindi pambata
+ Ipakita ang nilalamang maaaring makahamak sa bata dahil limitado ito sa edad (hal. 18+)
+ May \"Restricted Mode\" ang YouTube kung saan nakatago ang nilalamang hindi pambata
+ Notipikasyon sa pag-update ng app
+ Mga notipikasyon para sa player ng NewPipe
+ Mga artista
+ Gamitin ang \"Restricted Mode\" ng YouTube
+ Nagpe-play sa popup mode
+ Nagpe-play sa background
+ Player
+ Ilagay ang URL ng instansya
+ Piliin ang iyong mga paboritong instansya ng PeerTube
+ Mga download
+ Lahat
+ Default na bansa ng nilalaman
+ Di-suportadong URL
+ Notipikasyon ng player
+ Mga track
+ Mga gumagamit
\ No newline at end of file
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 4a4559477..327877651 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -84,7 +84,7 @@
Tout
Défi reCAPTCHA
Défi reCAPTCHA demandé
- Ouvrir en mode pop-up
+ Ouvrir en mode flottant
Lecture en mode flottant
Désactivés
Quoi :\\nRequest :\\nContent Language :\\nContent Country :\\nApp Language :\\nService :\\nGMT Time :\\nPackage :\\nVersion :\\nOS version :
@@ -326,7 +326,7 @@
Morceaux
Utilisateurs
Accélérer pendant les silences
- Étape
+ Graduation
Réinitialiser
Minimiser lors du changement d’application
Action lors du basculement à une autre application depuis le lecteur vidéo principal — %s
@@ -551,7 +551,7 @@
Impossible de reconnaitre l’URL fournie. Voulez-vous l’ouvrir avec une autre application \?
Ajouter automatiquement à la liste de lecture
La liste de lecture du lecteur actif sera remplacée
- Confirmer av. de suppr. la liste de lecture
+ Confirmer avant de supprimer la liste de lecture
Rien
Chargement
Lire aléatoirement
@@ -685,11 +685,8 @@
Aucun gestionnaire de fichier approprié n\'a été trouvé pour cette action.
\nVeuillez installer un gestionnaire de fichiers ou essayez de désactiver \'%s\' dans les paramètres de téléchargement.
Commentaire épinglé
- Une lecture est déjà en arrière-plan
LeakCanary n\'est pas disponible
Modifie la taille de l\'intervalle de chargement (actuellement %s). Une valeur plus faible peut accélérer le chargement initial des vidéos .
- Règler la hauteur par demi-tons musicaux
- Pas du tempo
Valeur par défaut d’ExoPlayer
Nouveaux flux
Configurer la notification du flux en cours de lecture
@@ -702,7 +699,7 @@
Connexion réseau requise
Notifications
Notifications désactivées
- Vous êtes maintenant abonné(e) à cette chaîne
+ Vous vous êtes maintenant abonné à cette chaîne
Notification du Lecteur
Notifications pour de nouveaux flux des abonnements
Supprimer tous les fichiers téléchargés du disque \?
@@ -712,5 +709,15 @@
Fréquence de vérification
Notifications pour de nouveaux flux des abonnements
,
- Sélectionner/Désélectionner tout
+ Tout basculer
+ Pourcent
+ Demi-ton
+ Les flux qui ne sont pas encore supportés ne sont pas montrés
+ Aucun flux audio n\'est disponible pour les lecteurs externes
+ Sélectionner la qualité pour les lecteurs externes
+ Format inconnu
+ Qualité inconnue
+ Le flux séléctionné n\'est pas supporté par les lecteurs externes
+ Aucun flux vidéo n\'est disponible pour les lecteurs externes
+ Taille de l\'intervalle de chargement de la lecture
\ No newline at end of file
diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml
index 52225b843..06b35acd3 100644
--- a/app/src/main/res/values-gl/strings.xml
+++ b/app/src/main/res/values-gl/strings.xml
@@ -61,7 +61,7 @@
Os metadatos da caché foron eliminados
Eliminar todos os datos de páxinas en caché
Os metadatos da caché foron eliminados
- Colocar a seguinte emisión na cola automaticamente
+ Colocar a seguinte emisión na fila automaticamente
Continúa rematando (non se repite) a cola de reprodución engadindo unha transmisión relacionada
Suxestións de procura
Escolla suxestións a mostrar ao procurar
@@ -226,7 +226,7 @@
\nA política de privacidade do NewPipe explica con máis detalle que datos son enviados e gardados cando envía un relatorio de erros.
Ler a política de privacidade
Licenza do NewPipe
- NewPipe é un software libre copyleft: Pode usar, estudar compartir e melloralo a vontade. En concreto, pode redistribuír e / ou modificala segundo os termos da Licenza Pública Xeral GNU publicada pola Free Software Foundation, xa sexa a versión 3 da licenza, ou (na súa opción) calquera outra versión posterior.
+ NewPipe é un software libre copyleft: Pode usar, estudar compartillar e melloralo a vontade. En concreto, pode redistribuír e / ou modificala segundo os termos da Licenza Pública Xeral GNU publicada pola Free Software Foundation, xa sexa a versión 3 da licenza, ou (na súa opción) calquera outra versión posterior.
Ler a licenza
Historial
Historial
@@ -321,8 +321,8 @@
Avanzar rápido durante os momentos de silencio
Paso
Reiniciar
- Para cumprirmos co Regulamento Xeral Europeo de Protección de Datos (GDPR), chamamos a súa atención sobre a nova política de privacidade do NewPipe. Por favor, léao con coidado.
-\nDebe aceptalo para nos enviar un relatorio de erro.
+ Para cumprirmos co Regulamento Xeral Europeo de Protección de Datos (GDPR), chamamos a súa atención sobre a nova política de privacidade do NewPipe. Por favor, léaa con coidado.
+\nDebe aceptala para nos enviar un relatorio de erro.
Aceptar
Recusar
Sen límite
@@ -434,7 +434,7 @@
Non se puido atopar o servidor
Non se puido establecer unha conexión segura
Non se pode crear o cartafol de destino
- Non se puido crear este ficheiro
+ Non se pode crear o ficheiro
Amosar o erro
Hai unha descarga pendente con este nome
Hai unha descarga en curso con este nome
@@ -557,7 +557,7 @@
Privado
Público
URL da miniatura
- Soporte
+ Apoio
Idioma
Límite de idade
Privacidade
@@ -672,8 +672,6 @@
Enfileirado
Procurar actualizacións
Procurar manualmente novas versións
- Axustar o ton do semitóns musicais
- Paso do tempo
A procurar actualizacións…
A partir do Android 10, só o \'Sistema de Acceso ao Almacenamento\' está soportado
Cambia o tamaño do intervalo de carga (actualmente %s). Un valor menor pode acelerar o carregamento do vídeo. Cambios poden precisar un reinicio do reprodutor.
@@ -688,4 +686,28 @@
Amosa unha opción de travamento ao usar o reprodutor
Travar o reprodutor
LeakCanary non está dispoñíbel
+ Notificacións sobre novas emisións para subscricións
+ Frecuencia de verificación
+ Conexión á rede necesaria
+ ,
+ Alternar todo
+
+ - %s nova emisión
+ - %s novas emisións
+
+ Por cento
+ Semitón
+ Notificar sobre novas emisións de subscricións
+ Notificacións sobre novas emisións
+ Notificación do reprodutor
+ Novas emisións
+ Carregando detalles da emisión…
+ Verifique se hai novas emisións
+ Configurar a notificación da emisión actual
+ Notificacións
+ Calquera rede
+ Desexa eliminar todos os ficheiros descarregados do disco\?
+ As notificacións están desactivadas
+ Recibir notificacións
+ Agora está subscrito a esta canle
\ No newline at end of file
diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml
index a0e6c32a9..b0ece06f1 100644
--- a/app/src/main/res/values-he/strings.xml
+++ b/app/src/main/res/values-he/strings.xml
@@ -706,11 +706,8 @@
התראת דיווח שגיאה
לא נמצאו מנהלי קבצים שמתאימים לפעולה הזאת.
\nנא להתקין מנהל קבצים שתומך בתשתית גישה לאחסון.
- כבר מתנגן ברקע
הערה ננעצה
LeakCanary אינה זמינה
- התאמת גובה הצליל לפי חצאי טונים מוזיקליים
- צעד מקצב
ברירת מחדל של ExoPlayer
שינוי גודל מרווח הטעינה (כרגע %s). ערך נמוך יותר עשוי להאיץ את טעינת הווידאו הראשונית. שינויים דורשים את הפעלת הנגן מחדש.
התראות על תזרימים חדשים להרשמה
@@ -737,4 +734,13 @@
למחוק את כל הקבצים שהורדו מהכונן\?
התראות מושבתות
נרשמת לערוץ הזה
+ אחוז
+ חצי טון
+ תזרימים שעדיין לא נתמכים על ידי המוריד לא מופיעים
+ התזרים הנבחר לא נתמך על ידי נגנים חיצוניים
+ אין תזרימי שמע שזמינים לנגנים חיצוניים
+ איכות לא מוכרת
+ אין תזרימי וידאו שזמינים לנגנים חיצוניים
+ בחירת איכות לנגנים חיצוניים
+ תצורה לא מוכרת
\ No newline at end of file
diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml
index 01cbab7f6..0ca0c75dc 100644
--- a/app/src/main/res/values-hr/strings.xml
+++ b/app/src/main/res/values-hr/strings.xml
@@ -340,7 +340,7 @@
NewPipe razvijaju volonteri koji provode vrijeme donoseći vam najbolje iskustvo. Vratite im kako biste programerima učinili da NewPipe bude još bolji dok uživate u šalici kave.
Koje su kartice prikazane na glavnoj stranici
Konferencije
- Preferirana \'otvori\' akcija
+ Željena radnja otvaranja streama
Zadana radnja pri otvaranju sadržaja — %s
Titlovi
Promijeni veličinu podnaslova reproduktora i pozadinske stilove reproduktora. Za stupanje na snagu, program se mora ponovo pokrenuti
@@ -602,8 +602,8 @@
Ovaj je video dobno ograničen.
\nZbog novih YouTube pravila za videa s dobnim ograničenjem, NewPipe ne može pristupiti nijednoj vlastitoj video emisiji i stoga je ne može reproducirati.
Preuzimanje je započeto
- Dolje možeš odabrati omiljenu noćnu temu
- Odaberi omiljenu noćnu temu – %s
+ Dolje možete odabrati željenu noćnu temu
+ Odaberi željenu noćnu temu – %s
Automatski (tema uređaja)
Radio
Istaknuto
@@ -687,4 +687,29 @@
NewPipe je naišao na grešku, dodirni za prijavu
Došlo je do greške, pogledaj obavijest
Prekini rad playera
+ Obavijest reproduktora
+ Prilagođavanje obavijesti reproduktora
+ Obavijesti
+ Novi videozapisi
+ Obavijesti novih streamova pretplaćenih kanala
+ Želite li izbrisati sve preuzete datoteke\?
+ Obavijesti su onemogućene
+ Pretplatili ste se ovome kanalu
+ ,
+ Uključiti/isključiti sve
+ Bilo kakva mreža
+ Obavijesti novih streamova pretplaćenih kanala
+ Pokaži zalogajnicu greške
+ Učitavanje pojedinosti streama…
+ Pokrenite provjeru novih streamova
+ Učestalost provjere
+ LeakCanary nije dostupno
+ Podešavanje visine tona po glazbenim polutonovima
+ Obavijesti o novim streamovima
+ Potrebna mrežna veza
+ Zadano za ExoPlayer
+ Primite obavijesti
+ Za ovu radnju nije pronađen odgovarajući upravitelj datoteka.
+\nMolimo vas da instalirate upravitelj za datoteke ili da pokušate onemogućiti \'%s\' u postavkama preuzimanja.
+ Prikvačeni komentar
\ 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 adaf6a8c0..e5457d3e1 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -683,6 +683,5 @@
- %1$s letöltés törölve
Rögzített megjegyzés
- Már megy a lejátszás a háttérben
LeakCanary nem elérhető
\ No newline at end of file
diff --git a/app/src/main/res/values-hy/strings.xml b/app/src/main/res/values-hy/strings.xml
index 2a2716d47..10d397aef 100644
--- a/app/src/main/res/values-hy/strings.xml
+++ b/app/src/main/res/values-hy/strings.xml
@@ -142,4 +142,79 @@
Պատմություն
Պատմություն
Թրենդային
+ Նվագացանկեր
+ [Անհայտ]
+ Մաքրել որոնման պատմությունը
+ Մեկնաբանությունները անջատված են
+ Միշտ հարցնել
+ Լցնել
+ Մեծացնել
+ Գեներացված
+ Ներմուծել ֆայլ
+ Ստուգել թարմացումները
+ Ինքնին
+ Բարձր որակ (մեծ)
+ Ցածր որակ (փոքր)
+ հերթագրված
+ Ջնջել ներբեռնված ֆայլերը
+ Սկսել ներբեռնումները
+ Օգտատերեր
+ Հեռացնել
+ Ներմուծում եմ…
+ Տարիքային սահմանափակում
+ Անհայտ որակ
+ Նվագել Kodi֊ով
+ Լռեցնել
+ Անսահման
+ Չի բեռնվել՝ %d
+ Թարմացվել է՝ %s
+ Ներբեռնումը սկսվեց
+ Ռադիո
+ Ներկել ծանուցումները
+ Մեկնաբանություններ
+ Թեժ 50
+ Տեսնել նկարագիրը
+ Որոնման հուշումներ
+ Մաքրել տվյալները
+
+ - %s լսող
+ - %s լսող
+
+
+ - %s հետևորդ
+ - %s հետևորդ
+
+ Տոկոս
+ Աղյուսակ
+ Ներմուծել
+ Նվագել
+ Մանրամասներ
+ Նշել նվագացանկ
+ Տառեր և թվեր
+ Բեռնումներ
+ Եղավ
+ Նվագել ամենը
+ Ուղիղ
+ Շարունակել նվագարկումը
+ Օգնություն
+ Լուծել
+ Նշել ալիք
+ Ցույց չտալ
+ Խառը
+ Հարմարվել
+ Ափփի լեզու
+ Ոչինչ
+ Առանց հետևորդ
+ Հոսքեր
+ Ավելացնել նվագացանկին
+ Անենթագիր
+ Ենթագրեր
+ Անջատված
+ Նկարագիր
+ Ծանուցումներ
+ Բացել կայքը
+ Գլուխներ
+ Հանրային
+ Պիտակներ
+ Ծանուցումները անջատված են
\ No newline at end of file
diff --git a/app/src/main/res/values-ia/strings.xml b/app/src/main/res/values-ia/strings.xml
index e9d400be2..d5bc90e6f 100644
--- a/app/src/main/res/values-ia/strings.xml
+++ b/app/src/main/res/values-ia/strings.xml
@@ -229,7 +229,6 @@
Aperir con
Suggestiones de recerca remote
Cargar miniaturas
- Notification
Monstrante resultatos pro: %s
Solmente alicun apparatos pote reproducer videos 2K/4K
Initiar le reproductor principal in schermo plen
diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml
index 056cdfce4..c1f296849 100644
--- a/app/src/main/res/values-in/strings.xml
+++ b/app/src/main/res/values-in/strings.xml
@@ -121,7 +121,7 @@
Terlepas apakah Anda memiliki ide untuk; terjemahan, perubahan desain, pembersihan kode, atau perubahan kode yang signifikan, segala bantuan akan selalu diterima. Semakin banyak akan semakin baik jadinya!
Baca lisensi
Kontribusi
- Berlangganan
+ Subscribe
Disubscribe
Apa Yang Baru
Lanjutkan pemutaran
@@ -260,7 +260,7 @@
Putar otomatis video berikutnya
Berhenti berlanggan channel
Tidak bisa memperbarui langganan
- Langganan
+ Subscription
Gunakan tinjau cepat tak pasti
Memungkinkan pengguna memilih posisi waktu video dengan cepat tetapi dengan tingkat presisi yang rendah. Mencari 5, 15 atau 25 detik tidak berhasil dengan ini
NewPipe adalah perangkat lunak libre copyleft: Anda bisa menggunakannya, mempelajarinya, berbagi, dan meningkatkannya. Secara khusus Anda bisa mendistribusikan ulang dan/atau memodifikasinya dibawah syarat Lisensi Publik Umum GNU yang diterbitkan oleh Free Software Foundation, baik versi 3 dari Lisensi, atau (sesuai pilihan Anda) versi yang lebih baru.
@@ -671,11 +671,8 @@
Tidak ada manajer file yang ditemukan untuk tindakan ini.
\nMohon instal sebuah manajer file yang kompatibel dengan Storage Access Framework.
Komentar dipin
- Sudah diputar di latar belakang
LeakCanary tidak tersedia
- Langkah tempo
Default ExoPlayer
- Atur nada berdasarkan semitone musik
Ubah ukuran interval pemuatan (saat ini %s). Sebuah nilai yang rendah mungkin dapat membuat pemuatan video awal lebih cepat. Membutuhkan sebuah pemulaian ulang pada pemain.
Memuat detail stream…
Frekuensi pemeriksaan
@@ -698,4 +695,14 @@
Notifikasi stream baru
Beritahu tentang stream baru dari notifikasi
Anda sekarang berlangganan ke channel ini
+ Semiton
+ Persen
+ Stream yang dipilih tidak didukung oleh pemain eksternal
+ Tidak ada stream video yang tersedia untuk pemain eksternal
+ Kualitas tidak diketahui
+ Stream yang belum didukung oleh pengunduh tidak ditampilkan
+ Tidak ada stream audio yang tersedia untuk pemain eksternal
+ Pilih kualitas untuk pemain eksternal
+ Format tidak diketahui
+ Ukuran interval pemuatan playback
\ No newline at end of file
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 9f7b74d8e..f347eba67 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -94,7 +94,7 @@
In sottofondo
Popup
Risoluzione predefinita lettore popup
- Mostra altre risoluzioni
+ Mostra risoluzioni più elevate
Solo alcuni dispositivi possono riprodurre video 2K/4K
Formato video predefinito
Ricorda proprietà lettore popup
@@ -664,7 +664,7 @@
Avvia il lettore principale a schermo intero
Aggiunto alla coda come prossimo
Accoda come prossimo
- Elaborazione... Potrebbe volerci un attimo
+ Elaborazione… Potrebbe volerci un attimo
Controlla aggiornamenti
Verifica manualmente la presenza di nuove versioni
Controllo aggiornamenti…
@@ -683,32 +683,39 @@
Non è stato trovato alcun gestore di file appropriato per questa azione.
\nInstallane uno compatibile con Storage Access Framework.
Commento in primo piano
- Già in riproduzione in sottofondo
LeakCanary non è disponibile
- Regola il tono secondo i semitoni musicali
Predefinito ExoPlayer
Cambia la dimensione dell\'intervallo da caricare (attualmente %s). Un valore basso può velocizzare il caricamento iniziale del video. La modifica richiede il riavvio del lettore.
- Passo tempo
- Notifiche di nuove stream dalle iscrizioni
+ Notifiche di nuovi contenuti dalle iscrizioni
Frequenza controllo
- Richiesta connessione alla rete
+ Connessione di rete richiesta
Ricevi le notifiche
- Sei ora iscritto a questo canale
+ Ti sei iscritto a questo canale
,
Attiva/disattiva tutti
- Notifiche per nuove stream
- Notifica lettore
- Configura la notifica della stream attualmente in riproduzione
+ Notifiche per nuovi contenuti
+ Notifica del lettore multimediale
+ Configura la notifica dell\'elemento attualmente in riproduzione
Notifiche
- Nuove stream
+ Nuovi contenuti
- - %s nuova stream
- - %s nuove stream
+ - %s nuovo contenuto
+ - %s nuovi contenuti
- Caricando i dettagli della stream…
- Cancellare tutti i file scaricati dal dispositivo\?
- Avvia controllo per nuove stream
+ Caricamento dei dettagli dei contenuti…
+ Cancellare dal dispositivo i file scaricati\?
+ Controlla la presenza di nuovi contenuti
Qualsiasi rete
Le notifiche sono disabilitate
- Notifica se ci sono nuove stream dalle iscrizioni
+ Notifica la presenza di nuovi contenuti dalle iscrizioni
+ Semitono
+ Percentuale
+ Il flusso selezionato non è supportato dai lettori esterni
+ Non sono disponibili flussi video per i lettori esterni
+ I flussi che non sono ancora supportati dal downloader non vengono visualizzati
+ Non sono disponibili flussi audio per i lettori esterni
+ Seleziona qualità per lettori esterni
+ Qualità sconosciuta
+ Formato sconosciuto
+ Dimensione dell\'intervallo di caricamento della riproduzione
\ 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 a0699f229..955912126 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -663,7 +663,6 @@
エラーが発生しました。通知をご覧ください
NewPipe はエラーに遭遇しました。タップして報告
スナックバーにエラーを表示
- 既にバックグラウンドで再生されています
固定されたコメント
この動作に適切なファイルマネージャが見つかりませんでした。
\nStorage Access Frameworkと互換性のあるファイルマネージャをインストールしてください。
@@ -673,7 +672,6 @@
エラー通知を作成
エラーを報告する通知
LeakCanaryが利用不可能です
- 緩急音階
プレイヤー通知
ストリームの詳細を読み込んでいます…
登録チャンネルの新しいストリームについて通知する
@@ -695,4 +693,8 @@
通知
現在再生しているストリームの通知を構成
読み込む間隔を変更します (現在 %s)。小さい値にすると初回読み込み時間が短くなります。変更にはプレイヤーの再起動が必要です。
+ 必要なネットワークの種類
+ パーセント
+ 半音
+ すべてのネットワーク
\ No newline at end of file
diff --git a/app/src/main/res/values-kk/strings.xml b/app/src/main/res/values-kk/strings.xml
new file mode 100644
index 000000000..a6b3daec9
--- /dev/null
+++ b/app/src/main/res/values-kk/strings.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml
index c28c80f38..4db63cf13 100644
--- a/app/src/main/res/values-lv/strings.xml
+++ b/app/src/main/res/values-lv/strings.xml
@@ -156,8 +156,8 @@
Detaļas:
Jūsu komentārs (Angliski):
Kas:\\nRequest:\\nContent Valoda:\\nContent Valsts:\\nApp Valoda:\\nService:\\nGMT Laiks:\\nPackage:\\nVersion:\\nOS versija:
- Notifikācijas priekš video apstrādes progresa
- Video Haša Notifikācija
+ Notifikācijas video apstrādes progresam
+ Video haša notifikācija
Atcerēties pēdējo popup izmēru un pozīciju
Atcerēties popup īpašības
Noklusējuma popup izšķirtspēja
@@ -223,9 +223,9 @@
Pārslēgt uz Popup
Pārslēgt uz Fonu
[Nezināms]
- Notifikācijas priekš NewPipe versijas
- Aplikācijas Atjauninājuma Notifikācija
- Notifikācijas priekš NewPipe fona un popup atskaņotājiem
+ Notifikācijas NewPipe versijām
+ Aplikācijas atjauninājuma notifikācija
+ Notifikācijas priekš NewPipe atskaņotāja
NewPipe Notifikācija
Fails
Tikai Vienreiz
@@ -253,7 +253,7 @@
Šis video ir vecuma ierobežots.
\n
\nIeslēdziet \"%1$s\" iestatījumos, ja vēlaties to redzēt.
- YouTube piedāvā \"ierobežotu režīmu\", kas slēpj iespējams pieaugušo saturu
+ YouTube piedāvā \"ierobežotu režīmu\", kas slēpj potenciāli pieaugušo saturu
Ieslēgt YouTube \"Ierobežoto režīmu\"
Rādīt saturu, iespējams nepiemērotu bērniem, jo tam ir vecuma ierobežojums
Rādīt vecuma ierobežotu saturu
@@ -273,7 +273,7 @@
Ievadīt instances saites URL
Pievienot instanci
Atrodiet instances, kas jums patīk ar %s
- Izvēlaties jūsu mīļāko PeerTube instanci
+ Izvēlaties jūsu mīļākās PeerTube instances
PeerTube instances
Valoda nomainīsies, kad aplikāciju restartēs
Neviena lietotne jūsu ierīcē nevar šo atvērt
@@ -493,7 +493,7 @@
Rādīt \"Nospiediet, lai pievienotu\" padomu
Automātiski atskaņot
Lejupielādēt
- Turpināt atskaņošanu pēc pārtraukumiem (piemēram telefona zvana)
+ Turpināt atskaņošanu pēc pārtraukumiem (piemēram, telefona zvana)
Turpināt atskaņošanu
Saglabājiet skatītos videoklipus
Dzēst datus
@@ -502,9 +502,9 @@
Saglabāt pēdējo atskaņošanas pozīciju
Atsākt atskaņošanu
Skatīšanās vēsture
- Glabāt meklēšanas vēsturi uz telefona
+ Glabāt meklēšanas vēsturi atmiņā
Meklēšanas vēsture
- Izvēlaties, kādus ieteikumus rādīt, rakstot meklēšanas joslā
+ Izvēlieties, kādus ieteikumus rādīt, rakstot meklēšanas joslā
Meklēšanas ieteikumi
Velkot ar pirkstu, mainiet video atskaņošanas spilgtumu
Spilgtuma kontrole, atskaņojot video
@@ -522,15 +522,15 @@
Izslēdziet, ja nevēlaties redzēt video aprakstus un papildus informāciju
Rādīt video aprakstu
Rādīt \'Nākošos\' un \'Līdzīgos\' videoklipus
- Izslēdziet, ja vēlaties neredzēt komentārus
+ Izslēdziet, lai paslēptu komentārus
Rādīt komentārus
Izslēdziet, ja vēlaties nelādēt video attēlus, ietaupot datus un atmiņu. Opcija notīra kešatmiņu, izdzēšot visus saglabātos video attēlus
Ielādēt video attēlus
Tagadējā atskaņošanas rinda tiks aizvietota
- Mainoties vienam video uz citu, iespējams notīrīsies jūsu atskaņošanas rinda
+ Mainoties vienam video uz citu, iespējams, notīrīsies jūsu atskaņošanas rinda
Prasīt apstiprinājumu, pirms notīrīt atskaņošanas rindu
- Uz priekšu/ uz atpakaļu, meklētāja ilgums
- Neprecīzs meklētājs atļauj video atskaņotājam atrast pozīciju atrāk, bet ar zemāku precizitāti. Meklēšana 5, 15 vai 25 sekundes uz priekšu vai atpakaļ, nestrādā ar šo opciju
+ Uz priekšu/atpakaļ meklētāja ilgums
+ Neprecīzs meklētājs atļauj video atskaņotājam atrast pozīciju ātrāk, bet ar zemāku precizitāti. Meklēšana 5, 15 vai 25 sekundes uz priekšu vai atpakaļ, nestrādā ar šo opciju
Izmantot ātru, neprecīzu meklētāju
Melna
Tumša
@@ -539,7 +539,7 @@
Noklusējuma video formāts
Noklusējuma audio formāts
Audio
- Ļaujiet Android pielāgot paziņojuma krāsu atbilstoši galvenajai krāsai video attēlā (ņemiet vērā, ka tas nav pieejams visās ierīcēs)
+ Ļaujiet Android pielāgot notifikācijas krāsu atbilstoši galvenajai krāsai video attēlā (ņemiet vērā, ka tas nav pieejams visās ierīcēs)
Kopīgot
Atvērt ar
Atvērt pārlūkprogrammā
@@ -548,22 +548,22 @@
Netika atrasts video atskaņotājs (jūs varat instalēt VLC, lai to atskaņotu).
Netika atrasts video atskaņotājs. Instalēt VLC\?
Publicēts %1$s
- Nospiediet \"Meklēt\", lai sāktu.
+ Nospiediet uz meklēšanas ikonas, lai sāktu.
Iekrāsot notifikāciju
Nekas
- Lādējas
+ Ielādējas
Sajaukt
Atkārtot
Jūs varat izvēlēties tikai 3 darbības, kuras rādīs kompaktajā notifikācijā!
- Rediģējiet katru notifikācijas darbību pieskaroties tai. Izvēlaties trīs darbības, kuras rādīs kompaktā notifikācijā, izmantojot rūtiņas labajā pusē
+ Rediģējiet katru notifikācijas darbību, pieskaroties tai. Izvēlieties trīs darbības, kuras rādīs kompaktā notifikācijā, izmantojot rūtiņas labajā pusē
Piektā darbības poga
Ceturtā darbības poga
Trešā darbības poga
Otrā darbības poga
Pirmā darbības poga
- Piemērot video attēlu, kuru rāda notifikācijā, no 16:9 uz 1:1 proporciju (iespējams, ka attēls būs izstiepts)
+ Piemērot video attēlu, kuru rāda notifikācijā, no 16:9 uz 1:1 proporciju (iespējams, attēls būs izstiepts)
Piemērot video attēlu 1:1 proporcijai
- Rādīt opciju, atskaņot video ar Kodi mediju centru
+ Rādīt opciju atskaņot video ar Kodi mediju centru
Rādīt \"Atskaņot ar Kodi\" opciju
Instalēt trūkstošo Kore aplikāciju\?
Atskaņot ar Kodi
@@ -589,10 +589,10 @@
Abonēts
Abonēt
Izmantot ārējo audio atskaņotāju
- Noņem skaņu dažās rezolūcijās
+ Noņem skaņu dažās izšķirtspējās
Izmantot ārējo video atskaņotāju
Kopīgot ar
- Rāda rezultātus priekš: %s
+ Tiek rādīti rezultāti priekš: %s
Vai jūs domājāt \"%1$s\"\?
Iestatījumi
Meklēt
@@ -638,7 +638,7 @@
Rādīt krāsainas lentes virs attēliem, norādot to avotu: sarkana - tīkls, zila - disks, zaļa - atmiņa
“Krātuves Piekļuves Sistēma” ir neatbalstīta uz Android KitKat un zemākām versijām
Ieslēgt teksta atlasīšanu video aprakstā
- Nav izvēlēts noklusējuma lejupielādes mape, izvēlaties to tagad
+ Lejupielādes mape vēl nav iestatīta, izvēlieties noklusējuma lejupielādes mapi
Pārvelciet objektus, lai tos noņemtu
Rādīt noskatītos video
Lokālie meklēšanas ieteikumi
@@ -680,4 +680,9 @@
- Lejupielādes pabeigtas
Tagad varat atlasīt tekstu video aprakstā.
+ Notifikācijas
+ Izmainīt ielādēšanas intervāla izmēru (pašlaik %s). Zemāka vērtība var paātrināt sākotnējo video ielādi. Izmainot vērtību, nepieciešams restartēt atskaņotāju.
+ Avarēt atskaņotāju
+ Pielāgojiet pašlaik atskaņotās plūsmas notifikāciju
+ Atskaņotāja notifikācija
\ No newline at end of file
diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml
index c38e56671..edbc7785e 100644
--- a/app/src/main/res/values-ml/strings.xml
+++ b/app/src/main/res/values-ml/strings.xml
@@ -258,8 +258,8 @@
ബാക്ക്ഗ്രൗണ്ടിലേക്ക് മാറുക
[അജ്ഞാതം]
പുതിയ ന്യൂപൈപ്പ് പതിപ്പിന് വേണ്ടിയുള്ള അറിയിപ്പ്
- അപ്ഡേറ്റ് അറിയിപ്പ്
- ന്യൂപൈപ്പ് ബാക്ക്ഗ്രൗണ്ട്, പോപ്പപ്പ് പ്ലയറുകൾക്ക് വേണ്ടിയുള്ള അറിയിപ്പുകൾ
+ ആപ്പ് അപ്ഡേറ്റ് ചെയ്യാനുള്ള അറിയിപ്പ്
+ ന്യൂപൈപ്പ് പ്ലേയറിന് വേണ്ടിയുള്ള അറിയിപ്പുകൾ
ന്യൂപൈപ്പ് അറിയിപ്പ്
ഫയൽ
ഒരിക്കൽ മാത്രം
@@ -310,7 +310,7 @@
സ്ഥിര കന്റെന്റ് രാജ്യം
അനുയോജ്യമല്ലാത്ത URL
പോപ്പപ്പ്/ബാക്ക്ഗ്രൗണ്ട് ബട്ടൺ അമർത്തുമ്പോൾ \"വിശദാംശങ്ങൾ\" എന്ന ടിപ് കാണിക്കും
- \"ഹോൾഡ് ടു അപ്പെൻഡ്\" എന്ന ടിപ് കാണിക്കുക
+ \"ക്യൂവിൽ കയറ്റാൻ പിടിക്കുക\" എന്ന ടിപ് കാണിക്കുക
\'അടുത്ത\' , \'സമാനമായ\' വീഡിയോകൾ കാണിക്കുക
ഓട്ടോപ്ലേ
ഡൗൺലോഡ്
@@ -588,7 +588,7 @@
പക്വതയുള്ള ഉള്ളടക്കം മറയ്ക്കുന്ന \"നിയന്ത്രിത മോഡ്\" യൂട്യൂബ് നൽകുന്നു
കുട്ടികൾക്ക് അനുയോജ്യമല്ലാത്ത ഉള്ളടക്കം കാണിക്കുക കാരണം അതിന് പ്രായപരിധി ഉണ്ട് (18+ പോലെ)
URL തിരിച്ചറിയാൻ കഴിഞ്ഞില്ല. മറ്റൊരു അപ്ലിക്കേഷൻ ഉപയോഗിച്ച് തുറക്കണോ\?
- യാന്ത്രിക-ക്യൂ
+ തനിയെ ക്യൂവിൽ കയറ്റുക
സ്ട്രീം സ്രഷ്ടാവ്, സ്ട്രീം ഉള്ളടക്കം അല്ലെങ്കിൽ ഒരു തിരയൽ അഭ്യർത്ഥന എന്നിവയെക്കുറിച്ചുള്ള കൂടുതൽ വിവരങ്ങൾ ഉൾക്കൊള്ളുന്ന മെറ്റാ വിവര ബോക്സുകൾ മറയ്ക്കുന്നതിന് ഓഫാക്കുക
മെറ്റാഇൻഫോ കാണിക്കുക
വീഡിയോ വിവരണവും അധിക വിവരങ്ങളും മറയ്ക്കുന്നതിന് ഓഫാക്കുക
@@ -653,4 +653,10 @@
തുടങ്ങുന്ന പ്രേധാന പേജ് മുഴുവന് സ്ക്രീനില് കാണിക്കുക
ഐറ്റം കളയണം എന്നുണ്ടെല് സ്വൈപ്പ് ചൈയ്യുക
മിനി പ്ലേയര് -ല് വീഡിയോ -ക്കള് ഒരിക്കലും സ്റ്റാര്ട്ട് ചൈയ്യരുത് , പക്ഷേ നേരെ ഫുള് സ്ക്രീന് മോഡിലെക് മാറും .ഓട്ടോ റൊട്ടേഷന് ലോക്ക് ചെയിത്തിട്ടുണ്ടെങ്കില് നിലവിലെ ഫുള് സ്ക്രീന് നില് നിന്നും മിനി പ്ലായേറിലെക് മാറാന് ആകും
+ നിലവിൽ കാണുന്ന സ്ട്രീം അറിയിപ്പ് ക്രമീകരിക്കുക
+ അറിയിപ്പുകൾ
+ പ്ലേയർ അറിയിപ്പ്
+ പുതിയ സ്ട്രീമുകൾ
+ ലോഡ് ഇടവേള മാറ്റുക (ഇപ്പൊൾ %s). കുറഞ്ഞ മൂല്യം വീഡിയോ വേഗത്തിൽ ലോഡ് ചെയ്യാൻ ഇടയാക്കാം. മാറ്റങ്ങൾ പ്രാഭല്യതിൽ വരുത്താൻ പ്ലേയർ പുനരാരംഭിക്കണം.
+ പ്ലേയർ തകർക്കുക
\ No newline at end of file
diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml
index 92b24fbc8..58b4cb08c 100644
--- a/app/src/main/res/values-nb-rNO/strings.xml
+++ b/app/src/main/res/values-nb-rNO/strings.xml
@@ -303,8 +303,8 @@
\n2. Gå til denne nettadressen: %1$s
\n3. Logg inn når forespurt
\n4. Kopier profil-nettadressen du ble videresendt til.
- Unøyaktig blafring lar spilleren søke posisjoner raskere med redusert presisjon. Å søke i 5, 15 eller 25 sekunder fungerer ikke med dette.
- Skru av for å stoppe innlasting av miniatyrbilder, noe som sparer data- og minnebruk. Endring av dette vil tømme både disk- og minne-hurtiglager.
+ Unøyaktig blafring lar spilleren søke posisjoner raskere med redusert presisjon. Å søke i 5, 15 eller 25 sekunder fungerer ikke med dette
+ Skru av for å stoppe innlasting av miniatyrbilder, noe som sparer data- og minnebruk. Endring av dette vil tømme både disk- og minne-hurtiglager
Fortsett fullendt (ikke-repeterende) avspillingskø ved å legge til en relatert strøm
Minnelekkasjeoppsyn kan forårsake programmet å opptre uresponsivt under haugdumping
Rapporter feil utenfor livssyklusen
@@ -532,7 +532,7 @@
Originaltekster fra tjenester vil vises for elementer i strømmen
Sjekk om det allerede eksisterer et problem som diskuterer ditt krasj. Når du oppretter duplikatbilletter, tar du tid fra oss som vi kan bruke på å fikse den faktiske feilen.
Du kan maksimalt velge tre handlinger som skal vises i kompaktvarselet!
- Rediger hver varslingshandling nedenfor ved å trykke på den. Velg opptil tre av dem som skal vises i det kompakte varselet ved å bruke avmerkingsboksene til høyre.
+ Rediger hver varslingshandling nedenfor ved å trykke på den. Velg opptil tre av dem som skal vises i det kompakte varselet ved å bruke avmerkingsboksene til høyre
Skaler videominiatyrbildet som vises i varselet fra 16:9 til 1:1 sideforhold (kan føre til forvrengninger)
Tilgjengelig i noen tjenester, det er vanligvis mye raskere, men kan gi et begrenset antall elementer, og ofte ufullstendig info (f.eks. ingen varighet, elementtype, eller sanntidsstatus).
Hent fra dedikert strøm når tilgjengelig
@@ -575,7 +575,7 @@
Regner ut sjekksum
Merknad for videosjekksummeringsframdrift
Videosjekksumsmerknad
- Slå av for å skjule metainformasjonsbokser med tilleggsinformasjon om strømskaperen, strøminnhold eller en søkeforespørsel.
+ Slå av for å skjule metainformasjonsbokser med tilleggsinformasjon om strømskaperen, strøminnhold eller en søkeforespørsel
Vis metainfo
Nylige
Ingen programmer på enheten din kan åpne dette
@@ -672,7 +672,6 @@
Viser et krasjalternativ ved bruk av avspilleren
Det oppstod en feil. Sjekk merknaden.
Festet kommentar
- Spilles allerede i bakgrunnen
Feilrapport-merknad
Merknader for innrapportering av feil
NewPipe-feil. Trykk for å rapportere.
@@ -682,6 +681,4 @@
Installer en filbehandler som støtter lagringstilgangsrammeverk først.
LeakCanary er ikke tilgjengelig
ExoPlayer-forvalg
- Juster toneart etter musikalske halvtoner
- Tempo-steg
\ No newline at end of file
diff --git a/app/src/main/res/values-nl-rBE/strings.xml b/app/src/main/res/values-nl-rBE/strings.xml
index 376b43319..3b54c56f5 100644
--- a/app/src/main/res/values-nl-rBE/strings.xml
+++ b/app/src/main/res/values-nl-rBE/strings.xml
@@ -622,4 +622,22 @@
Niet tonen
Reacties zijn uitgeschakeld
Toon afbeeldingsindicatoren
+ Speler melding
+ Configureer actieve stream melding
+ Meldingen
+ Nieuwe streams
+ Meldingen over nieuwe streams voor abonnementen
+ NewPipe meldt een fout, tik om te rapporteren
+ Verander het laadinterval (momenteel %s). Een lagere waarde kan het laden van video versnellen. Vereist een herstart van de speler.
+
+ - %s nieuwe stream
+ - %s nieuwe streams
+
+ Foutrapport melding
+ Stream details aan het laden…
+ Een fout is opgetreden, zie melding
+ Crash de speler
+ Meldingen om fouten te rapporteren
+ Verwerken... Dit kan even duren
+ LeakCanary is niet beschikbaar
\ No newline at end of file
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index dd405963a..7bdc902a5 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -677,7 +677,6 @@
NewPipe meldt fout, tik voor bericht
Foutmelding
Maak een foutmelding
- Speelt al op de achtergrond
Korte foutmelding weergeven
Er is geen geschikte bestandsbeheerder gevonden voor deze actie.
\nInstalleer een bestandsbeheerder of probeer \'%s\' uit te schakelen in de download instellingen.
@@ -686,7 +685,29 @@
Vastgemaakt commentaar
LeakCanary is niet beschikbaar
Verander de laad interval tijd (nu %s). Een lagere waarde kan het initiële laden van de video versnellen. De wijziging vereist een herstart van de speler.
- Pas de toonhoogte aan met muzikale halve tonen
- Tempo stap
ExoPlayer standaard
+ Speler melding
+ Configureer meldingen van de huidige spelende stream
+ Meldingen
+ Nieuwe streams
+ Meldingen over nieuwe streams van abonnementen
+ Bezig met laden van stream details…
+ Controleer op nieuwe streams
+ Meldingen over nieuwe streams
+ Melding over nieuwe streams van abonnementen
+ Frequentie van controleren
+ Vereiste netwerk connectie
+ Elk netwerk
+ Meldingen zijn uitgeschakeld
+ Ontvang een melding
+ ,
+ Alles in-/uitschakelen
+ Percentage
+ Halve toon
+
+ - %s nieuwe stream
+ - %s nieuwe streams
+
+ Je bent nu geabonneerd op dit kanaal
+ Alle gedownloade bestanden van schijf wissen\?
\ 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 cfa995212..a09549fcf 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -701,14 +701,11 @@
\nZainstaluj menedżer plików lub spróbuj wyłączyć „%s” w ustawieniach pobierania.
Nie znaleziono odpowiedniego menedżera plików dla tej akcji.
\nZainstaluj menedżer plików zgodny z Storage Access Framework.
- Już jest odtwarzane w tle
Przypięty komentarz
LeakCanary jest niedostępne
Rozmiar interwału ładowania odtwarzania
Zmień rozmiar interwału ładowania (aktualnie %s). Niższa wartość może przyspieszyć początkowe ładowanie wideo. Zmiany wymagają ponownego uruchomienia odtwarzacza
domyślny ExoPlayera
- Dostosuj wysokość półtonami
- Krok tempa
Powiadomienie odtwarzacza
Skonfiguruj powiadomienie aktualnie odtwarzanego strumienia
Uruchom sprawdzenie nowych strumieni
@@ -733,4 +730,13 @@
Otrzymuj powiadomienia
,
Przełącz wszystkie
+ Procent
+ Półton
+ Wybrany strumień nie jest obsługiwany przez zewnętrzne odtwarzacze
+ Strumienie, których jeszcze nie da się pobrać, nie są wyświetlane
+ Brak dostępnych strumieni audio dla zewnętrznych odtwarzaczy
+ Brak dostępnych strumieni wideo dla zewnętrznych odtwarzaczy
+ Wybierz jakość dla zewnętrznych odtwarzaczy
+ Nieznany format
+ Nieznana jakość
\ No newline at end of file
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index 0850034f6..4b0af0718 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -682,12 +682,9 @@
Mostrar um snackbar de erro
Nenhum gerenciador de arquivos apropriado foi encontrado para esta ação.
\nInstale um gerenciador de arquivos ou tente desativar \'%s\' nas configurações de download.
- Já está tocando em segundo plano
Comentário fixado
O LeakCanary não está disponível
- Passo do tempo
Altere o tamanho do intervalo de carregamento (atualmente %s). Um valor menor pode acelerar o carregamento inicial do vídeo. As alterações exigem que o player reinicie.
- Ajustar o tom por semitons musicais
ExoPlayer padrão
Notificação do reprodutor
Configurar a notificação do fluxo da reprodução atual
@@ -711,4 +708,14 @@
Conexão de rede necessária
As notificações estão desativadas
Seja notificado
+ Por cento
+ Semitom
+ A transmissão selecionada não é compatível com players externos
+ Nenhum áudio de transmissão está disponível para players externos
+ As transmissão que ainda não são suportados pelo downloader não são exibidos
+ Nenhum vídeo de transmissão está disponível para players externos
+ Selecione a qualidade para players externos
+ Formato desconhecido
+ Qualidade desconhecida
+ Tamanho do intervalo de carregamento da reprodução
\ No newline at end of file
diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml
index 43e782251..9a9adad52 100644
--- a/app/src/main/res/values-pt-rPT/strings.xml
+++ b/app/src/main/res/values-pt-rPT/strings.xml
@@ -36,7 +36,7 @@
Legendas
Repor predefinições
Ocorreu um erro: %1$s
- Popup
+ Pop-up
Descarga NewPipe
Devido às restrições de ExoPlayer, a duração da pesquisa foi definida para %d segundos
Sobrescrever
@@ -683,11 +683,8 @@
Nenhum gestor de ficheiros apropriado foi encontrado para esta ação.
\nPor favor, instale um gestor de ficheiros compatível com o Storage Access Framework.
Comentário fixado
- Já está a reproduzir em segundo plano
LeakCanary não está disponível
Altere o tamanho do intervalo de carregamento (atualmente %s). Um valor menor pode acelerar o carregamento inicial do vídeo. Se fizer alterações é necessário reiniciar.
- Ajustar o tom por semitons musicais
- Passo do tempo
Predefinido do ExoPlayer
Notificações
A carregar detalhes do fluxo…
@@ -711,4 +708,13 @@
Agora assinou este canal
,
Novos fluxos
+ Por cento
+ Semitom
+ Não estão disponíveis transmissões de vídeo a reprodutores externos
+ As transmissões que ainda não são suportadas para descarregamento não são mostradas
+ A transmissão selecionada não é suportada por reprodutores externos
+ Não estão disponíveis transmissões de áudio a reprodutores externos
+ Formato desconhecido
+ Qualidade desconhecida
+ Selecione a qualidade para reprodutores externos
\ No newline at end of file
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index 9e1a80916..5453b1fa4 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -554,7 +554,6 @@
URL não reconhecido. Abrir com outra aplicação\?
Enfileiramento automático
Embaralhar
- Notificação
Apenas em Wi-Fi
Nada
Mudar de um reprodutor para outro pode substituir a sua fila
@@ -683,13 +682,10 @@
\nPor favor, instale um gestor de ficheiros ou tente desativar \'%s\' nas configurações de descarregar.
Nenhum gestor de ficheiros apropriado foi encontrado para esta ação.
\nPor favor, instale um gestor de ficheiros compatível com o Storage Access Framework.
- Já está a reproduzir em segundo plano
Comentário fixado
LeakCanary não está disponível
- Ajustar o tom por semitons musicais
Predefinido do ExoPlayer
Altere o tamanho do intervalo de carregamento (atualmente %s). Um valor menor pode acelerar o carregamento inicial do vídeo. Se fizer alterações é necessário reiniciar.
- Passo do tempo
Notificação do reprodutor
Configurar a notificação da reprodução do fluxo atual
Notificações
@@ -712,4 +708,13 @@
Seja notificado
Notificações são desativadas
,
+ Por cento
+ Semitom
+ As transmissões que ainda não são suportadas para descarregamento não são mostradas
+ Não estão disponíveis transmissões de áudio a reprodutores externos
+ Não estão disponíveis transmissões de vídeo a reprodutores externos
+ Selecione a qualidade para reprodutores externos
+ Formato desconhecido
+ Qualidade desconhecida
+ A transmissão selecionada não é suportada por reprodutores externos
\ No newline at end of file
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
index e83fc5462..fe2292ef7 100644
--- a/app/src/main/res/values-ro/strings.xml
+++ b/app/src/main/res/values-ro/strings.xml
@@ -111,7 +111,7 @@
Nu s-a putut modifica abonamentul
Nu s-a putut actualiza abonamentul
Abonamente
- Ce este nou
+ Noutăți
Istoric de căutări
Stochează local căutările
Istoricul vizionărilor
@@ -123,7 +123,7 @@
Istoric și cache
Anulare
Notificare NewPipe
- Notificări pentru playerele de fundal și popup NewPipe
+ Notificări pentru playerul NewPipe
Fără rezultate
Nimic aici în afară de sunetul greierilor
Fără abonați
@@ -144,12 +144,12 @@
- %s videoclipuri
- %s de videoclipuri
- Descărcați
+ Descărcări
Caractere permise în numele fișierelor
Caracterele nevalabile sunt înlocuite cu această valoare
Caracter de înlocuire
Litere și cifre
- Caracterele cele mai speciale
+ Caractere speciale
Despre NewPipe
Licențe terță-parte
© %1$s de %2$s sub %3$s
@@ -175,7 +175,7 @@
Top 50
Noi și populare
Niciun player pentru streaming găsit. (Totuși, puteți instala VLC).
- Descărcați fișierul de vizionat
+ Descărcați fișierul de flux
Arată informații
Playlist-uri salvate
Salvează în
@@ -306,7 +306,7 @@
Mărire
Subtitrări
Modificați scara textului de legendă a playerului și stilurile de fundal. Necesită repornirea aplicației pentru a intra în vigoare
- NewPipe este un software liber cu copyleft: îl puteți folosi, studia, partaja și îmbunătăți în voie. Mai exact, îl puteți redistribui și/sau modifica în conformitate cu termenii Licenței Publice Generale GNU, așa cum a fost publicată de Free Software Foundation, fie versiunea 3 a Licenței, fie (la alegerea dumneavoastră) orice versiune ulterioară.
+ NewPipe este un software liber cu copyleft: îl puteți folosi, studia, distribui și îmbunătăți în voie. Mai exact, îl puteți redistribui și/sau modifica în conformitate cu termenii Licenței Publice Generale GNU, așa cum a fost publicată de Free Software Foundation, fie versiunea 3 a Licenței, fie (la alegerea dumneavoastră) orice versiune ulterioară.
Politica de confidențialitate a NewPipe
Proiectul NewPipe ia foarte în serios confidențialitatea dumneavoastră. Prin urmare, aplicația nu colectează niciun fel de date fără consimțământul dumneavoastră.
\nPolitica de confidențialitate a NewPipe explică în detaliu ce date sunt trimise și stocate atunci când trimiteți un raport de eroare.
@@ -353,7 +353,7 @@
Cookie-urile reCAPTCHA au fost șterse
Ștergeți cookie-urile reCAPTCHA
Notificări pentru progresul hashing-ului video
- Notificare video Hash
+ Notificare hash video
Artiști
Albume
Melodii
@@ -505,7 +505,7 @@
Generați un nume unic
Descărcare eșuată
Acțiune refuzată de sistem
- Coadă
+ Puneți în coadă
Se recuperează
post-procesare
În așteptare
@@ -553,8 +553,8 @@
\n3. Faceți clic pe \"Toate datele incluse\", apoi pe \"Deselectați totul\", apoi selectați doar \"Abonamente\" și faceți clic pe \"OK\"
\n4. Faceți clic pe \"Pasul următor\" și apoi pe \"Creați exportul\"
\n5. Faceți clic pe butonul \"Descărcare\" după ce acesta apare
-\n6. Faceți clic pe IMPORT FIȘIER de mai jos și selectați fișierul zip descărcat
-\n7. [În cazul în care importul zip eșuează] Extrageți fișierul .csv (de obicei sub \"YouTube and YouTube Music/subscriptions/subscriptions.csv\"), faceți clic pe IMPORT FIȘIER de mai jos și selectați fișierul csv extras
+\n6. Faceți clic pe IMPORT FIȘIER de mai jos și selectați fișierul .zip descărcat
+\n7. [În cazul în care importul .zip eșuează] Extrageți fișierul .csv (de obicei sub \"YouTube and YouTube Music/subscriptions/subscriptions.csv\"), faceți clic pe IMPORT FIȘIER de mai jos și selectați fișierul csv extras
Textele originale din servicii vor fi vizibile în elementele de flux
Raportați erori în afara ciclului de viață
Afișați scurgeri de memorie
@@ -679,16 +679,48 @@
Procesarea.. Poate dura un moment
Verifică dacă există actualizări
Verifică manual dacă există versiuni noi
- Se redă deja pe fundal
Comentariu lipit
Notificare cu raport de eroare
Afișează opțiunea de a întrerupe atunci când utilizați playerul
- A apărut o eroare, verifică notificarea
+ A apărut o eroare, consultați notificarea
NewPipe a întămpinat o eroare, apăsați ca să raportați
Se verifică actualizări…
Notificări pentru a raporta erori
- Crează o notificare de eroare
- Arată \"închiderea bruscă a player-ului video\"
- Arată o eroare de tip snackbar
- Elemente de flux noi
+ Creați o notificare de eroare
+ Afișează \"Dați crash playerului\"
+ Afișați o eroare de tip snackbar
+ Elemente noi în flux
+ Notificarea player-ului
+ Configurați notificarea fluxului de redare curent
+ Fluxuri noi
+
+ - %s flux nou
+ - %s fluxuri noi
+ - %s de fluxuri noi
+
+ Se încarcă detaliile fluxului…
+ Notificări de fluxuri noi
+ Notificare despre fluxuri noi din abonamente
+ Frecvența verificării
+ Conexiune de rețea necesară
+ Orice rețea
+ Ștergeți toate fișierele descărcate de pe disc\?
+ Notificările sunt dezactivate
+ Procent
+ Semiton
+ Implicit ExoPlayer
+ Modificați dimensiunea intervalului de încărcare (în prezent %s). O valoare mai mică poate accelera încărcarea inițială a videoclipului. Modificările necesită o repornire a playerului.
+ Dați crash player-ului
+ LeakCanary nu este disponibil
+ Notificări
+ Notificări despre noi fluxuri pentru abonamente
+ Executați verificarea pentru fluxuri noi
+ Nu a fost găsit niciun manager de fișiere adecvat pentru această acțiune.
+\nVă rugăm să instalați un manager de fișiere sau să încercați să dezactivați \'%s\' în setările de descărcare.
+ Nu a fost găsit niciun manager de fișiere adecvat pentru această acțiune.
+\nVă rugăm să instalați un manager de fișiere compatibil cu Storage Access Framework.
+ Primiți notificări
+ V-ați abonat la acest canal
+ Comutați toate
+ ,
\ No newline at end of file
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index fc4c03f42..64351c7d3 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -691,8 +691,6 @@
Проверить обновления
Проверка обновлений…
Новое на канале
- Отчёт об ошибках плеера
- Подробные отчёты об ошибках плеера вместо коротких всплывающих сообщений (полезно при диагностике проблем)
Уведомления
Новые видео
Уведомления о новых видео в подписках
@@ -718,11 +716,8 @@
\nПожалуйста, установите файловый менеджер, или попробуйте отключить \'%s\' в настройках загрузок.
Для этого действия не найдено подходящего файлового менеджера.
\nПожалуйста, установите файловый менеджер, совместимый со Storage Access Framework (SAF).
- Уже проигрывается в фоне
Закреплённый комментарий
LeakCanary недоступна
- Регулировка высоты тона по музыкальным полутонам
- Шаг темпа
Стандартное значение ExoPlayer
Изменить размер интервала загрузки (сейчас %s). Меньшее значение может ускорить начальную загрузку видео. Изменение значения потребует перезапуска плеера.
Загрузка деталей трансляции…
@@ -730,4 +725,13 @@
Удалить все загруженные файлы\?
Уведомления плеера
,
+ Полутон
+ Проценты
+ Выбранная трансляция не поддерживается внешними проигрывателями
+ Нет ни одного доступного видео потока для внешних проигрывателей
+ Были скрыты трансляции, которые пока ещё не поддерживаются загрузчиком
+ Неизвестный формат
+ Нет ни одного доступного аудио потока для внешних проигрывателей
+ Выберите качество для внешних проигрывателей
+ Неизвестное качество
\ No newline at end of file
diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml
index 38a7c3303..2fc18cb6f 100644
--- a/app/src/main/res/values-sc/strings.xml
+++ b/app/src/main/res/values-sc/strings.xml
@@ -683,10 +683,7 @@
\nPro praghere installa unu gestore de documentos cumpatìbile cun su \"Sistema de Atzessu a s\'Archiviatzione\".
Faghe serrare su riproduidore
Cummentu apicadu
- Giai in riprodutzione in s\'isfundu
LeakCanary no est a disponimentu
- Règula s\'intonatzione in base a sos semitonos musicales
- Passu de tempus
Valore ExoPlayer predefinidu
Muda sa mannària de s\'intervallu de carrigamentu (in custu momentu %s). Unu valore prus bassu diat pòdere allestrare su carrigamentu de incumintzu de su vìdeu. Sas modìficas tenent bisòngiu de torrare a allùghere su riproduidore.
Cunfigura sa notìfica de su flussu in cursu de riprodutzione
@@ -711,4 +708,13 @@
Notìficas
Flussos noos
Iscantzellare totu sos archìvios iscarrigados dae su discu\?
+ Pertzentuale
+ Semitonu
+ Seletziona sa calidade pro sos letores esternos
+ Sos flussos chi non sunt galu suportados dae s\'iscarrigadore non sunt ammustrados
+ Non b\'est perunu flussu sonoru a disponimentu pro letores esternos
+ Non bi sunt flussos de vìdeu a disponimentu pro letores esternos
+ Formadu disconnotu
+ Calidade disconnota
+ Su flussu seletzionadu no est galu suportadu dae letores esternos
\ No newline at end of file
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index 9b7a62145..bfba46a5c 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -79,7 +79,7 @@
Prevzaté
Hlásenie o chybe
Aplikácia/UP zlyhalo
- Čo:\\nPožiadavka:\\nJazyk obsahu:\\nKrajina Obsahu:\\nJazyk Aplikácie:\\nSlužba:\\nČas v GMT:\\nBalík:\\nVerzia:\\nVerzia OS:
+ Čo:\\nPožiadavka:\\nJazyk obsahu:\\nKrajina Obsahu:\\nJazyk Aplikácie:\\nSlužba:\\nČas v GMT:\\nBalík:\\nVerzia:\\nOS:
Výzva reCAPTCHA
Čierna
Všetko
@@ -647,8 +647,6 @@
Pri každom sťahovaní sa zobrazí výzva kam uložiť súbor
Nie je nastavený adresár na sťahovanie, nastavte ho teraz
Označiť ako videné
- Načítavanie podrobností o kanáli…
- Chyba pri zobrazení podrobností kanála
Vypnuté
Zapnuté
Režim tabletu
@@ -698,8 +696,6 @@
Zobrazí možnosť zlyhania pri používaní prehrávača
Zobraziť krátke oznámenie chyby
Oznámte chybu
- Upraviť výšku poltónov
- Krok tempa
ExoPlayer preddefinovaný
Zmeniť interval načítania (aktuálne %s). Menšia hodnota môže zvýšiť rýchlosť prvotného načítania videa. Zmena vyžaduje reštart.
Upozornenia
@@ -712,4 +708,19 @@
Začali ste odoberať tento kanál
,
Zapnúť všetko
+ Nové streamy
+ Upozornenia na nové streamy v odberoch
+
+ - %s nový stream
+ - %s nové streamy
+ - %s nových streamov
+
+ Skontrolovať nové streamy
+ Upozornenia na nové streamy
+ Upozorniť na nové streamy z odberov
+ Akákoľvek sieť
+ Dostávať upozornenia
+ Poltón
+ Nahrávanie podrobností streamu…
+ Percent
\ No newline at end of file
diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml
index 3b170795b..68981868c 100644
--- a/app/src/main/res/values-sr/strings.xml
+++ b/app/src/main/res/values-sr/strings.xml
@@ -4,9 +4,9 @@
Нема плејера токова. Инсталирати ВЛЦ\?
Инсталирај
Откажи
- Отвори у прегледачу
+ Отвори у претраживачу
Подели
- Преузимање
+ Преузми
Тражи
Подешавања
Да ли сте мислили: „%1$s“\?
@@ -61,7 +61,7 @@
Аудио
Покушај поново
Уживо
- Тапните на лупу да започнете.
+ Кликните на лупу да започнете.
Почни
Паузирај
Обриши
@@ -515,7 +515,7 @@
Редослед активног плејера биће замењен
Пребацивање на други плејер може променити ваш редослед
Питај за потврду пре пражњења редоследа
- Време за премотавања напред/назад
+ Период премотавања напред/назад
ноћна тема
Андроид ће прилагодити боју обавештења према главној боји на сличици (није доступно на свим уређајима)
Обоји обавештења
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index 63af6d4d6..53cd6909b 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -682,19 +682,16 @@
\nInstallera en filhanterare eller testa att inaktivera \'%s\' i nedladdningsinställningarna.
Ingen lämplig filhanterare hittades för denna åtgärd.
\nInstallera en filhanterare som är kompatibel med Storage Access Framework.
- Spelas redan i bakgrunden
Fäst kommentar
LeakCanary är inte tillgänglig
- Justera tonhöjden med musikaliska halvtoner
ExoPlayer standard
Ändra inläsningsintervallets storlek (för närvarande %s). Ett lägre värde kan påskynda den första videoinläsningen. Ändringar kräver omstart av spelaren.
- Temposteg
Validera frekvens
Kräver nätverksanslutning
Alla nätverk
Radera alla nedladdade filer från disken\?
Notifikationer är avstängda
- Bli medelad
+ Bli meddelad
Du har nu prenumenerat till denna kanalen
Notifikationer om nya strömmar för prenumenanter
@@ -703,7 +700,7 @@
Konfigurera meddelande om aktuell ström som spelas upp
Kör leta efter nya strömmar
- Medela om nya strömmar från prenumenanter
+ Meddela om nya strömmar från prenumeranter
Notifikationer
Nya strömmar
Laddar strömdetaljer…
@@ -711,4 +708,10 @@
,
Spelaravisering
Växla alla
+ Procent
+ Halvton
+ Inga videoströmmar tillgängliga för externa spelare
+ Okänd kvalitet
+ Inga ljudströmmar tillgängliga för externa spelare
+ Okänt format
\ No newline at end of file
diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml
index d7c920f2b..827da452a 100644
--- a/app/src/main/res/values-ta/strings.xml
+++ b/app/src/main/res/values-ta/strings.xml
@@ -253,7 +253,6 @@
இயக்குதலைத் மறுதொடர்
நி
நிகழ்வு ஏற்கனவே உள்ளது
- அறிவிப்பு
யூடியூபின் \"கட்டுப்பாடு பயன்முறை\"ஐ இயக்கு
பாடல்கள்
பிழைகளைப் புகாரளிக்க அறிவிப்புகள்
diff --git a/app/src/main/res/values-te/strings.xml b/app/src/main/res/values-te/strings.xml
index 60542e0b6..3958e02b0 100644
--- a/app/src/main/res/values-te/strings.xml
+++ b/app/src/main/res/values-te/strings.xml
@@ -371,7 +371,6 @@
reCAPTCHA సవాలు
reCAPTCHA సవాలు అభ్యర్థించబడింది
ప్లేజాబితాను ఎంచుకోండి
- ఇప్పటికే వెనుకగా ప్లే అవుతోంది
డాటాబేసుని ఎగుమతిచేయుము
యాప్ పునఃప్రారంభించబడిన తర్వాత భాష మారుతుంది
ఛానెల్ వివరాలను చూపు
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index 958dc5eeb..512790f7c 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -1,7 +1,7 @@
- Başlamak için büyüteç simgesine dokunun.
- Yayınlanma: %1$s
+ Başlamak için büyütece dokunun.
+ %1$s tarihinde yayınlandı
Akış oynatıcısı bulunamadı. VLC yüklensin mi\?
Yükle
İptal
@@ -20,7 +20,7 @@
Ses indirme klasörü
İndirilen ses dosyaları burada depolanır
Ses dosyaları için indirme klasörünü seç
- Öntanımlı çözünürlük
+ Varsayılan çözünürlük
Kodi ile oynat
Eksik Kore uygulaması yüklensin mi\?
\"Kodi ile oynat\" seçeneğini göster
@@ -103,7 +103,7 @@
Açılan pencerenin son boyutunu ve konumunu hatırla
Bazı çözünürlüklerde sesi kaldırır
Arama önerileri
- Ararken gösterilecek önerileri seç
+ Arama yaparken gösterilecek önerileri seçin
En iyi çözünürlük
NewPipe Hakkında
Üçüncü Taraf Lisansları
@@ -282,7 +282,7 @@
\nSürdürmek istiyor musunuz\?
Küçük resimleri yükle
Küçük resimlerin yüklenmesini önleyerek veri ve hafıza kullanımından tasarruf etmek için kapatın. Değişiklikler, hem bellek içi hem de diskteki resim önbelleğini temizler
- Resim önbelleği temizlendi
+ Resim önbelleği silindi
Önbelleğe alınmış üstverileri temizle
Önbelleğe alınmış tüm web sayfası verilerini kaldır
Üstveri önbelleği temizlendi
@@ -541,7 +541,7 @@
Lütfen hatanızı tartışan sorunun var olup olmadığını kontrol edin. Yinelenen istekler oluştururken, bizden asıl hatayı düzeltmek için harcayabileceğimiz zamanı alırsınız.
Henüz oynatma listesi yer imleri yok
Asla
- Yalnızca Wi-Fi\'de
+ Yalnızca Wi-Fi
Oynatmayı kendiliğinden başlat — %s
Oynatma kuyruğu
URL tanınamadı. Başka bir uygulamayla açılsın mı\?
@@ -682,13 +682,10 @@
Hata raporları için bildirimler
Oynatıcı kullanırken çöktürme seçeneği gösterir
Oynatıcıyı çöktür
- Zaten arka planda oynuyor
Sabitlenmiş yorum
LeakCanary yok
Yükleme ara boyutunu değiştir (şu anda %s). Düşük bir değer videonun ilk yüklenişini hızlandırabilir. Değişiklikler oynatıcının yeniden başlatılmasını gerektirir.
ExoPlayer öntanımlısı
- Tempo adımı
- Perdeyi müzikal yarım tonlarla uyarla
Yeni akış bildirimleri
Bildirimler
@@ -711,4 +708,14 @@
İndirilen tüm dosyalar diskten silinsin mi\?
,
Tümünü değiştir
+ Yüzde
+ Ara ton
+ Seçilen yayın harici oynatıcılar tarafından desteklenmiyor
+ İndirici tarafından henüz desteklenmeyen yayınlar gösterilmez
+ Harici oynatıcılar için ses yayını yok
+ Harici oynatıcılar için video yayını yok
+ Harici oynatıcılar için kalite seçin
+ Bilinmeyen biçim
+ Bilinmeyen kalite
+ Oynatma yükleme aralığı boyutu
\ No newline at end of file
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index f2a3e986e..50e1b00b7 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -58,7 +58,6 @@
Завантаження
Звіт про помилку
Усе
- Так
Вимкнено
Збій застосунку/інтерфейсу
Ваш коментар (англійською):
@@ -93,7 +92,7 @@
Не вдалося оновити підписку
Підписки
Новинки
- У фоновому режимі
+ Фоново
У вікні
Типова роздільність вікна
Не всі пристрої можуть відтворювати 2K/4K-відео
@@ -699,11 +698,8 @@
\nУстановіть файловий менеджер, сумісний зі Storage Access Framework.
Показати панель помилок
Створити сповіщення про помилку
- Уже відтворюється у фоновому режимі
Закріплений коментар
LeakCanary недоступний
- Крок темпу
- Регулювання висоти звуку за музичними півтонами
Типовий ExoPlayer
Змінити розмір інтервалу завантаження (наразі %s). Менше значення може прискорити початкове завантаження відео. Зміни вимагають перезапуску програвача.
Ви підписалися на цей канал
@@ -729,4 +725,14 @@
Сповіщення про нові трансляції
Частота перевірки
Необхідний тип з\'єднання
+ Відсоток
+ Напівтон
+ Немає аудіотрансляцій доступних для зовнішніх програвачів
+ Немає доступних відеотрансляцій для зовнішніх програвачів
+ Невідомий формат
+ Трансляції, які ще не підтримуються завантажувачем, не показані
+ Вибрана трансляція не підтримується зовнішніми програвачами
+ Виберіть якість для зовнішніх програвачів
+ Невідома якість
+ Розмір інтервалу завантаження відтворення
\ No newline at end of file
diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml
index bdad22174..8fa00d0d8 100644
--- a/app/src/main/res/values-v21/styles.xml
+++ b/app/src/main/res/values-v21/styles.xml
@@ -12,21 +12,31 @@
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
index 7ea1a1ed5..658c96b55 100644
--- a/app/src/main/res/values-vi/strings.xml
+++ b/app/src/main/res/values-vi/strings.xml
@@ -67,7 +67,7 @@
Nội dung không khả dụng
Không thể thiết lập menu tải về
Ứng dụng / Giao diện người dùng bị lỗi
- Xin lỗi, điều đó không nên xảy ra.
+ Ai Da, NewPipe đã gặp lỗi. Tôi lấy làm tiếc cho bạn.
Báo lỗi qua email
Xin lỗi, đã xảy ra sự cố.
Báo cáo
@@ -100,7 +100,7 @@
Đã sao chép vào clipboard
Hãy chọn một thư mục để tải xuống trong phần cài đặt
Chế độ popup cần quyền này
-\n để hoạt động
+\n để hoạt động, hãy bật trong phần cài đặt
reCAPTCHA
Yêu cầu reCAPTCHA
Giới thiệu về NewPipe
@@ -156,7 +156,7 @@
Playlist
Bản nhạc
Người dùng
- Hủy bỏ
+ Hoàn tác
Chơi tất cả
Luôn luôn
Chỉ một lần
@@ -219,7 +219,7 @@
Đóng góp
NewPipe được phát triển bởi các tình nguyện viên dành thời gian mang lại cho bạn trải nghiệm tốt nhất. Đóng góp một tách cà phê để giúp các nhà phát triển làm NewPipe tốt hơn nữa.
Trả lại
- Trang mạng
+ Trang web
Truy cập trang web NewPipe để biết thêm thông tin và tin tức.
Chính sách bảo mật của NewPipe
Dự án NewPipe rất coi trọng quyền riêng tư của bạn. Do đó, ứng dụng không thu thập bất kỳ dữ liệu nào mà không có sự đồng ý của bạn.
@@ -344,7 +344,7 @@
Không thể đọc tab đã lưu, sử dụng tab mặc định
Khôi phục về mặc định
Bạn có muốn khôi phục về mặc định\?
- Số người đăng ký không có
+ Số người đăng ký không khả dụng
Chọn các tab để hiện trên trang chủ
Top 50 phổ biến
Nhập ID SoundCloud hoặc link soundcloud.com/<ID của bạn>
@@ -513,9 +513,9 @@
Chưa chọn kênh nào
Chọn kênh
- Đang xử lý feed…
+ Đang xử lý thông báo…
Số kênh không tải được: %d
- Đang tải feed…
+ Đang tải thông báo…
Feed cập nhật lần cuối vào: %s
Do giới hạn của ExoPlayer, khoảng thời gian tua đã được đặt lại thành %d giây
đang khôi phục
@@ -587,7 +587,7 @@
Video này chỉ được dành cho thành viên YouTube Music Premium, nên NewPipe không thể phát hay tải xuống.
Nội dung này được để ở chế độ riêng tư, nên NewPipe không thể phát hay tải xuống.
Đây là một track SoundCloud Go+, nên NewPipe không thể phát hay tải xuống được, ít nhất là tại quốc gia của bạn.
- Nội dung này không có sẳn ở quốc gia của bạn.
+ Nội dung này không có sẵn ở quốc gia của bạn.
Làm văng ứng dụng
Video này đã bị giới hạn độ tuổi.
\nVì những quy định mới của YouTube, NewPipe không thể phát được video này, vì không thể tìm thấy nguồn phát video.
@@ -610,7 +610,7 @@
Hỗ trợ
Ngôn ngữ
Giới hạn độ tuổi
- Sự riêng tư
+ Chế độ
Giấy phép
Thẻ
Thể loại
@@ -635,7 +635,7 @@
Chất lượng cao (lớn hơn)
Xem trước hình thu nhỏ trên thanh tua
Bình luận đang bị tắt
- Đã được người tạo gắn tim
+ Đã được người tạo thả tim
Đánh dấu là đã xem
Hiện ruy băng được tô màu Picasso ở trên cùng các hình ảnh và chỉ ra nguồn của chúng: đỏ đối với mạng, xanh lam đối với ổ đĩa và xanh lá đối với bộ nhớ
Hiện dấu chỉ hình ảnh
@@ -664,7 +664,7 @@
Kiểm tra cập nhật
Kiểm tra phiên bản mới theo cách thủ công
Đang kiểm tra cập nhật…
- Mục feed mới
+ Mục thông báo mới
Làm trình phát dừng
Không tìm thấy ứng dụng quản lý tệp phù hợp nào để thực hiện hành động.
\nVui lòng cài đặt ứng dụng quản lý tệp tương thích với Storage Access Framework.
@@ -672,8 +672,29 @@
\nVui lòng cài đặt ứng dụng quản lý tệp hoặc tắt \'%s\' trong cài đặt tải xuống.
Thay đổi kích thước khoảng thời gian tải (tầm khoảng %s). Để ở giá trị thấp hơn có thể sẽ tăng tốc độ tải video hơn ban đầu. Khởi động lại trình phát để áp dụng thay đổi.
LeakCanary không khả dụng
- Điều chỉnh cao độ theo nhạc nền âm nhạc
- Nhịp độ tiếp theo
ExoPlayer mặc định
Bình luận được ghim
+ Thông báo trình phát
+ Thiết lập thông báo hiện đang phát
+ Thông báo
+ Luồng truyền mới
+ Thông báo về luồng truyền mới đối với mục đăng ký
+
+ - %s luồng truyền mới
+
+ Đang tải chi tiết luồng truyền…
+ Chạy kiểm tra luồng truyền mới
+ Thông báo video mới
+ Thông báo về video mới từ kênh bạn đã đăng ký
+ Thời gian kiểm tra
+ Yêu cầu kết nối mạng
+ Bất kỳ mạng nào (có thể tính phí)
+ Xóa tất cả tệp đã tải xuống khỏi ổ đĩa\?
+ Thông báo bị tắt
+ Được thông báo
+ Bạn hiện đã đăng ký kênh này
+ Bật/tắt tất cả
+ Phần trăm
+ ,
+ Nửa cung
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml
index 0e79f5a58..399282f7c 100644
--- a/app/src/main/res/values-zh-rHK/strings.xml
+++ b/app/src/main/res/values-zh-rHK/strings.xml
@@ -20,7 +20,7 @@
顯示以 Kodi 媒體中心播放影片嘅選項
聲音
預設聲音檔案格式
- 主題
+ 色系
黑暗
明亮
下載
@@ -106,15 +106,15 @@
最佳解像度
十億
關於 NewPipe
- 第三方版權特許
- © %1$s %2$s,根據 %3$s 特許版權所有
+ 第三方版權協議
+ © %1$s %2$s 版權所有,根據 %3$s 嘅條款授權
關於
- 特許
+ 版權協議
喺 Android 上盡享自由輕便串流。
檢視我們的 GitHub
NewPipe 嘅授權協議
無論您對翻譯、設計改動、打掃程式碼,抑或開山劈石編寫程式碼有咩偈仔,都無任歡迎您幫手。聚沙成塔,眾志成城!
- 檢閱特許
+ 閱讀版權協議
貢獻
抹走睇過嘅紀錄
搵唔到串流播放器 (您可以裝 VLC 嚟播)。
@@ -145,7 +145,7 @@
睇過嘅紀錄
恢復播放
返返最後播放到去嗰個位
- 喺播放清單排第幾
+ 清單度睇到播到去邊
縮圖放到去 1:1 長寬比
顯示喺通知嘅影片縮圖由 16:9 放到去 1:1 長寬比 (話唔定會鬆郁矇)
通知色彩化
@@ -164,7 +164,7 @@
顯示留言
關閉去隱藏留言
搜尋紀錄
- 喺播放清單顯示位置編號
+ 喺清單入面顯示最後播放到去邊個位
抹除資料
顯示描述
抹除咗影像快取
@@ -201,7 +201,7 @@
匯入資料庫
主播放器用全螢幕開啟
如果自動旋轉鎖定,開啟影片嘅時候唔用袖珍播放器就直接飛去全螢幕模式。您仍可結束全螢幕返返去袖珍播放器
- 個 URL 識別唔到。會唔會用另一個 app 開?
+ 認唔出呢個 URL。要唔要用另一個 app 開?
YouTube 提供嘅「嚴格篩選模式」可以過濾潛在嘅成人內容
有年齡限制 (例如 18+) 故可能兒童不宜嘅內容都照顯示
呢部影片有年齡限制。
@@ -338,7 +338,7 @@
空白頁
重新開過個 app 之後就會轉新語言
時興
- 頭位 50
+ 頭 50 位
最新同大熱
警告:未能匯入晒全部檔案。
噉樣做會冚咗您而家嘅設定個囉噃。
@@ -357,8 +357,8 @@
自動產生
貼合
匯入
- 由邊處匯入:
- 匯出去邊處:
+ 由呢處匯入
+ 匯出去呢度
匯入緊…
無聲嘅時候快轉
重設
@@ -526,7 +526,7 @@
開啟網站
關
主機
- 喺影片「詳情:」度撳一下「幕後播」或者「浮面播」個掣嘅時候顯示提示
+ 喺影片詳情撳一下「幕後播」或「浮面播」個掣嘅時候顯示提示
紀錄
紀錄
攝下個等陣播
@@ -587,7 +587,7 @@
唔再收起
係咪要刪除呢個播放清單?
播放器通知
- 調整目前播放緊嘅串流嘅通知
+ 調整目前播放緊咩串流嘅通知
訂閱有新加串流嘅通知
載入緊串流詳細資料…
通知訂閱有新加串流
@@ -595,7 +595,7 @@
須要網絡連線
不拘任何網絡
收取通知
- 您現已訂閱呢個頻道
+ 您而家訂閱咗呢個頻道
全部切換
執行檢查有冇新加串流
通知
@@ -603,10 +603,64 @@
- %s 個新加串流
- 按樂音半度調整音高
新加串流通知
係咪要喺磁碟機上面消除晒全部下載咗嘅檔案?
通知已停用
單曲
- 節奏步伐
+ 全部剷走晒播放到邊個位
+ 請查看係咪已經有人喺度講緊您呢次彈 app 嘅狀況。若然重複發表新嘅議題,將會嘥我哋可以真正用嚟修復個問題嘅時間㗎喇。
+ NewPipe 係由一班熱心人用空閒時間義務開發,為您帶嚟最佳嘅使用體驗。係時候少少無拘,支持返開發者令 NewPipe 更臻完美,同時一顆心意挺佢哋可以嘆返杯咖啡。
+ 站內
+ 聚首
+ 轉 app 嘅時候借過
+ 當喺主影片播放器轉去第個 app 嘅時候點做好 — %s
+ 借過幕後播
+ 借過浮面播
+ 自動開啟播放 — %s
+ 上返行人路
+ 時間軸預覽縮圖
+ 載入緊摘要…
+ 處理緊摘要…
+ 摘要
+ 摘要上次更新:%s
+ 未有載入:%d
+ 摘要隔幾耐要更新
+ 訂閱對上一次更新後,隔幾耐視之為過時 — %s
+ 幾時都照更新
+ 載入摘要嘅時候發生問題
+ 載入唔到「%s」嘅摘要。
+ 快速摘要模式就此冇補充。
+ 有得攞就攞特設摘要
+ 啟用快速模式
+ 停用快速模式
+ 少少無拘
+ 全部剷走咗播放到邊個位
+ 揀選外面嘅播放器用咩畫質
+ 外面嘅播放器唔支援揀選嘅串流
+ 外面嘅播放器用得到嘅聲音串流欠奉
+ 外面嘅播放器用得到嘅影片串流欠奉
+ 未知格式
+ 未知畫質
+ 下載工具暫未支援嘅串流,就無謂擺出嚟喇
+ 著作者嘅帳戶已經被終止。
+\nNewPipe 日後唔會載入到呢個摘要。
+\n您要唔要取消訂閱呢個頻道?
+ 某啲服務會提供,通常快趣好多,但項目數量可能有限兼詳情欠奉 (例如片長、項目類型、直播狀態)
+ 剷走播放到邊個位
+ 係咪要全部剷走晒播放到邊個位?
+ 百分比
+ 半音
+ 更改載入相距大細 (目前係 %s)。細啲或者會加快影片一開波嘅載入。更改要開過個播放器至生效。
+ 問過先至將排隊播清零
+ 目前播放器入面嘅排隊播將會清零
+ 加一個站
+ 請輸入個站嘅 URL
+ 驗證唔到個站
+ 個站已經喺度喇
+ 冚過您目前嘅紀錄、訂閱、播放清單,同埋 (有得揀係咪冚埋) 設定
+ 匯出紀錄、訂閱、播放清單,同埋設定
+ 新嘅摘要項目
+ 揀選一個站
+ 轉換播放器嘅時候,排隊播可能會清零
+ NewPipe 係「著佐權」(copyleft) 自由軟件:您可以隨意使用、考究、分享同改進佢。具體而言,您可以依據自由軟件基金會發佈嘅《GNU 通用公眾特許條款》第 3 版或 (按您選擇) 之後任一版本之下嘅條款,重新分發及/或修改呢個軟件。
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index fd129fb6c..db6caf56d 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -539,7 +539,6 @@
作用中播放器的佇列可能會被取代
從一個播放器切換到另一個可能會取代您的佇列
清除佇列前要求確認
- 通知
無
正在緩衝
隨機播放
@@ -638,8 +637,6 @@
拖動列縮圖預覽
被創作者加心號
標記為已看過
- 正在載入頻道詳細資訊……
- 顯示頻道詳細資訊時發生錯誤
在圖片頂部顯示畢卡索彩色絲帶,指示其來源:紅色代表網路、藍色代表磁碟、綠色代表記憶體
顯示圖片指示器
遠端搜尋建議
@@ -674,12 +671,9 @@
NewPipe 遇到錯誤,點擊以回報
發生錯誤,請檢視通知
釘選的留言
- 已經在背景播放
LeakCanary 無法使用
- 步進時間
ExoPlayer 預設值
變更載入間隔大小(目前為 %s)。較低的值可能會提昇初始影片載入速度。變更需要重新啟動播放器。
- 按音樂半音調整音高
播放器通知
通知
正在載入串流詳細資訊……
@@ -701,4 +695,14 @@
- %s 個新串流
任意網路
+ 百分比
+ 半音
+ 未知格式
+ 未知品質
+ 不顯示下載程式尚不支援的串流
+ 外部播放程式不支援選定的串流
+ 沒有可用於外部播放程式的音訊串流
+ 沒有可用於外部播放程式的視訊串流
+ 選取外部播放程式的畫質
+ 播放載入間隔大小
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 022dc5179..419b3ca43 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -58,11 +58,6 @@
150dp
9dp
- 32dp
- 42dp
- 24dp
-
- 9dp
15sp
diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml
index bf42aaf0e..c13caf610 100644
--- a/app/src/main/res/values/settings_keys.xml
+++ b/app/src/main/res/values/settings_keys.xml
@@ -4,10 +4,6 @@
last_used_preferences_version
-
- - @string/youtube
- - @string/soundcloud
-
service
@string/youtube
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0af08a8ad..6a1c220f0 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -497,10 +497,10 @@
Pitch
Unhook (may cause distortion)
Fast-forward during silence
- Adjust pitch by musical semitones
Step
- Tempo step
Reset
+ Percent
+ Semitone
In order to comply with the European General Data Protection Regulation (GDPR), we hereby draw your attention to NewPipe\'s privacy policy. Please read it carefully.
\nYou must accept it to send us the bug report.
@@ -740,4 +740,11 @@
You now subscribed to this channel
,
Toggle all
+ Streams which are not yet supported by the downloader are not shown
+ The selected stream is not supported by external players
+ No audio streams are available for external players
+ No video streams are available for external players
+ Select quality for external players
+ Unknown format
+ Unknown quality
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 7c1265580..e711b35ab 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -130,11 +130,8 @@
- @color/black_settings_accent_color
-
-
+
+
diff --git a/app/src/main/res/values/styles_misc.xml b/app/src/main/res/values/styles_misc.xml
index f539eb5a1..ca285ae15 100644
--- a/app/src/main/res/values/styles_misc.xml
+++ b/app/src/main/res/values/styles_misc.xml
@@ -69,14 +69,17 @@
+