Merge branch 'refactor' into Import-export-worker
# Conflicts: # gradle/libs.versions.toml
This commit is contained in:
commit
abaf16e12b
@ -28,9 +28,9 @@ android {
|
||||
if (System.properties.containsKey('versionCodeOverride')) {
|
||||
versionCode System.getProperty('versionCodeOverride') as Integer
|
||||
} else {
|
||||
versionCode 1002
|
||||
versionCode 1003
|
||||
}
|
||||
versionName "0.27.5"
|
||||
versionName "0.27.6"
|
||||
if (System.properties.containsKey('versionNameSuffix')) {
|
||||
versionNameSuffix System.getProperty('versionNameSuffix')
|
||||
}
|
||||
@ -233,6 +233,7 @@ dependencies {
|
||||
implementation libs.androidx.work.runtime
|
||||
implementation libs.androidx.work.rxjava3
|
||||
implementation libs.androidx.material
|
||||
implementation libs.androidx.webkit
|
||||
|
||||
/** Third-party libraries **/
|
||||
// Instance state boilerplate elimination
|
||||
|
||||
127
app/src/main/assets/po_token.html
Normal file
127
app/src/main/assets/po_token.html
Normal file
@ -0,0 +1,127 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en"><head><title></title><script>
|
||||
/**
|
||||
* Factory method to create and load a BotGuardClient instance.
|
||||
* @param options - Configuration options for the BotGuardClient.
|
||||
* @returns A promise that resolves to a loaded BotGuardClient instance.
|
||||
*/
|
||||
function loadBotGuard(challengeData) {
|
||||
this.vm = this[challengeData.globalName];
|
||||
this.program = challengeData.program;
|
||||
this.vmFunctions = {};
|
||||
this.syncSnapshotFunction = null;
|
||||
|
||||
if (!this.vm)
|
||||
throw new Error('[BotGuardClient]: VM not found in the global object');
|
||||
|
||||
if (!this.vm.a)
|
||||
throw new Error('[BotGuardClient]: Could not load program');
|
||||
|
||||
const vmFunctionsCallback = function (
|
||||
asyncSnapshotFunction,
|
||||
shutdownFunction,
|
||||
passEventFunction,
|
||||
checkCameraFunction
|
||||
) {
|
||||
this.vmFunctions = {
|
||||
asyncSnapshotFunction: asyncSnapshotFunction,
|
||||
shutdownFunction: shutdownFunction,
|
||||
passEventFunction: passEventFunction,
|
||||
checkCameraFunction: checkCameraFunction
|
||||
};
|
||||
};
|
||||
|
||||
this.syncSnapshotFunction = this.vm.a(this.program, vmFunctionsCallback, true, this.userInteractionElement, function () {/** no-op */ }, [ [], [] ])[0]
|
||||
|
||||
// an asynchronous function runs in the background and it will eventually call
|
||||
// `vmFunctionsCallback`, however we need to manually tell JavaScript to pass
|
||||
// control to the things running in the background by interrupting this async
|
||||
// function in any way, e.g. with a delay of 1ms. The loop is most probably not
|
||||
// needed but is there just because.
|
||||
return new Promise(function (resolve, reject) {
|
||||
i = 0
|
||||
refreshIntervalId = setInterval(function () {
|
||||
if (!!this.vmFunctions.asyncSnapshotFunction) {
|
||||
resolve(this)
|
||||
clearInterval(refreshIntervalId);
|
||||
}
|
||||
if (i >= 10000) {
|
||||
reject("asyncSnapshotFunction is null even after 10 seconds")
|
||||
clearInterval(refreshIntervalId);
|
||||
}
|
||||
i += 1;
|
||||
}, 1);
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a snapshot asynchronously.
|
||||
* @returns The snapshot result.
|
||||
* @example
|
||||
* ```ts
|
||||
* const result = await botguard.snapshot({
|
||||
* contentBinding: {
|
||||
* c: "a=6&a2=10&b=SZWDwKVIuixOp7Y4euGTgwckbJA&c=1729143849&d=1&t=7200&c1a=1&c6a=1&c6b=1&hh=HrMb5mRWTyxGJphDr0nW2Oxonh0_wl2BDqWuLHyeKLo",
|
||||
* e: "ENGAGEMENT_TYPE_VIDEO_LIKE",
|
||||
* encryptedVideoId: "P-vC09ZJcnM"
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* console.log(result);
|
||||
* ```
|
||||
*/
|
||||
function snapshot(args) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
if (!this.vmFunctions.asyncSnapshotFunction)
|
||||
return reject(new Error('[BotGuardClient]: Async snapshot function not found'));
|
||||
|
||||
this.vmFunctions.asyncSnapshotFunction(function (response) { resolve(response) }, [
|
||||
args.contentBinding,
|
||||
args.signedTimestamp,
|
||||
args.webPoSignalOutput,
|
||||
args.skipPrivacyBuffer
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
function runBotGuard(challengeData) {
|
||||
const interpreterJavascript = challengeData.interpreterJavascript.privateDoNotAccessOrElseSafeScriptWrappedValue;
|
||||
|
||||
if (interpreterJavascript) {
|
||||
new Function(interpreterJavascript)();
|
||||
} else throw new Error('Could not load VM');
|
||||
|
||||
const webPoSignalOutput = [];
|
||||
return loadBotGuard({
|
||||
globalName: challengeData.globalName,
|
||||
globalObj: this,
|
||||
program: challengeData.program
|
||||
}).then(function (botguard) {
|
||||
return botguard.snapshot({ webPoSignalOutput: webPoSignalOutput })
|
||||
}).then(function (botguardResponse) {
|
||||
return { webPoSignalOutput: webPoSignalOutput, botguardResponse: botguardResponse }
|
||||
})
|
||||
}
|
||||
|
||||
function obtainPoToken(webPoSignalOutput, integrityToken, identifier) {
|
||||
const getMinter = webPoSignalOutput[0];
|
||||
|
||||
if (!getMinter)
|
||||
throw new Error('PMD:Undefined');
|
||||
|
||||
const mintCallback = getMinter(integrityToken);
|
||||
|
||||
if (!(mintCallback instanceof Function))
|
||||
throw new Error('APF:Failed');
|
||||
|
||||
const result = mintCallback(identifier);
|
||||
|
||||
if (!result)
|
||||
throw new Error('YNJ:Undefined');
|
||||
|
||||
if (!(result instanceof Uint8Array))
|
||||
throw new Error('ODM:Invalid');
|
||||
|
||||
return result;
|
||||
}
|
||||
</script></head><body></body></html>
|
||||
@ -28,6 +28,7 @@ import org.acra.config.CoreConfigurationBuilder
|
||||
import org.schabi.newpipe.error.ReCaptchaActivity
|
||||
import org.schabi.newpipe.extractor.NewPipe
|
||||
import org.schabi.newpipe.extractor.downloader.Downloader
|
||||
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor
|
||||
import org.schabi.newpipe.ktx.hasAssignableCause
|
||||
import org.schabi.newpipe.settings.NewPipeSettings
|
||||
import org.schabi.newpipe.util.BridgeStateSaverInitializer
|
||||
@ -36,6 +37,7 @@ import org.schabi.newpipe.util.ServiceHelper
|
||||
import org.schabi.newpipe.util.StateSaver
|
||||
import org.schabi.newpipe.util.image.ImageStrategy
|
||||
import org.schabi.newpipe.util.image.PreferredImageQuality
|
||||
import org.schabi.newpipe.util.potoken.PoTokenProviderImpl
|
||||
import java.io.IOException
|
||||
import java.io.InterruptedIOException
|
||||
import java.net.SocketException
|
||||
@ -116,6 +118,8 @@ open class App :
|
||||
)
|
||||
|
||||
configureRxJavaErrorHandler()
|
||||
|
||||
YoutubeStreamExtractor.setPoTokenProvider(PoTokenProviderImpl)
|
||||
}
|
||||
|
||||
override fun newImageLoader(context: Context): ImageLoader =
|
||||
|
||||
@ -142,7 +142,8 @@ public final class DownloaderImpl extends Downloader {
|
||||
}
|
||||
|
||||
final okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder()
|
||||
.method(httpMethod, requestBody).url(url)
|
||||
.method(httpMethod, requestBody)
|
||||
.url(url)
|
||||
.addHeader("User-Agent", USER_AGENT);
|
||||
|
||||
final String cookies = getCookies(url);
|
||||
@ -150,38 +151,33 @@ public final class DownloaderImpl extends Downloader {
|
||||
requestBuilder.addHeader("Cookie", cookies);
|
||||
}
|
||||
|
||||
for (final Map.Entry<String, List<String>> pair : headers.entrySet()) {
|
||||
final String headerName = pair.getKey();
|
||||
final List<String> headerValueList = pair.getValue();
|
||||
headers.forEach((headerName, headerValueList) -> {
|
||||
requestBuilder.removeHeader(headerName);
|
||||
headerValueList.forEach(headerValue ->
|
||||
requestBuilder.addHeader(headerName, headerValue));
|
||||
});
|
||||
|
||||
if (headerValueList.size() > 1) {
|
||||
requestBuilder.removeHeader(headerName);
|
||||
for (final String headerValue : headerValueList) {
|
||||
requestBuilder.addHeader(headerName, headerValue);
|
||||
}
|
||||
} else if (headerValueList.size() == 1) {
|
||||
requestBuilder.header(headerName, headerValueList.get(0));
|
||||
try (
|
||||
okhttp3.Response response = client.newCall(requestBuilder.build()).execute()
|
||||
) {
|
||||
if (response.code() == 429) {
|
||||
throw new ReCaptchaException("reCaptcha Challenge requested", url);
|
||||
}
|
||||
|
||||
String responseBodyToReturn = null;
|
||||
try (ResponseBody body = response.body()) {
|
||||
if (body != null) {
|
||||
responseBodyToReturn = body.string();
|
||||
}
|
||||
}
|
||||
|
||||
final String latestUrl = response.request().url().toString();
|
||||
return new Response(
|
||||
response.code(),
|
||||
response.message(),
|
||||
response.headers().toMultimap(),
|
||||
responseBodyToReturn,
|
||||
latestUrl);
|
||||
}
|
||||
|
||||
final okhttp3.Response response = client.newCall(requestBuilder.build()).execute();
|
||||
|
||||
if (response.code() == 429) {
|
||||
response.close();
|
||||
|
||||
throw new ReCaptchaException("reCaptcha Challenge requested", url);
|
||||
}
|
||||
|
||||
final ResponseBody body = response.body();
|
||||
String responseBodyToReturn = null;
|
||||
|
||||
if (body != null) {
|
||||
responseBodyToReturn = body.string();
|
||||
}
|
||||
|
||||
final String latestUrl = response.request().url().toString();
|
||||
return new Response(response.code(), response.message(), response.headers().toMultimap(),
|
||||
responseBodyToReturn, latestUrl);
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,6 +88,7 @@ import org.schabi.newpipe.util.SerializedCache;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
import org.schabi.newpipe.util.StateSaver;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
||||
import org.schabi.newpipe.views.FocusOverlayView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@ -116,7 +117,8 @@ public class MainActivity extends AppCompatActivity {
|
||||
private static final int ITEM_ID_DOWNLOADS = -4;
|
||||
private static final int ITEM_ID_HISTORY = -5;
|
||||
private static final int ITEM_ID_SETTINGS = 0;
|
||||
private static final int ITEM_ID_ABOUT = 1;
|
||||
private static final int ITEM_ID_DONATION = 1;
|
||||
private static final int ITEM_ID_ABOUT = 2;
|
||||
|
||||
private static final int ORDER = 0;
|
||||
|
||||
@ -258,6 +260,10 @@ public class MainActivity extends AppCompatActivity {
|
||||
drawerLayoutBinding.navigation.getMenu()
|
||||
.add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings)
|
||||
.setIcon(R.drawable.ic_settings);
|
||||
drawerLayoutBinding.navigation.getMenu()
|
||||
.add(R.id.menu_options_about_group, ITEM_ID_DONATION, ORDER,
|
||||
R.string.donation_title)
|
||||
.setIcon(R.drawable.volunteer_activism_ic);
|
||||
drawerLayoutBinding.navigation.getMenu()
|
||||
.add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about)
|
||||
.setIcon(R.drawable.ic_info_outline);
|
||||
@ -333,6 +339,9 @@ public class MainActivity extends AppCompatActivity {
|
||||
case ITEM_ID_SETTINGS:
|
||||
NavigationHelper.openSettings(this);
|
||||
break;
|
||||
case ITEM_ID_DONATION:
|
||||
ShareUtils.openUrlInBrowser(this, getString(R.string.donation_url));
|
||||
break;
|
||||
case ITEM_ID_ABOUT:
|
||||
NavigationHelper.openAbout(this);
|
||||
break;
|
||||
@ -840,4 +849,5 @@ public class MainActivity extends AppCompatActivity {
|
||||
return sheetState == BottomSheetBehavior.STATE_HIDDEN
|
||||
|| sheetState == BottomSheetBehavior.STATE_COLLAPSED;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -9,7 +9,6 @@ import com.google.auto.service.AutoService;
|
||||
import org.acra.config.CoreConfiguration;
|
||||
import org.acra.sender.ReportSender;
|
||||
import org.acra.sender.ReportSenderFactory;
|
||||
import org.schabi.newpipe.App;
|
||||
|
||||
/*
|
||||
* Created by Christian Schabesberger on 13.09.16.
|
||||
|
||||
@ -26,7 +26,7 @@ import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
@ -67,10 +67,6 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
public static final String ERROR_GITHUB_ISSUE_URL =
|
||||
"https://github.com/TeamNewPipe/NewPipe/issues";
|
||||
|
||||
public static final DateTimeFormatter CURRENT_TIMESTAMP_FORMATTER =
|
||||
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
|
||||
|
||||
|
||||
private ErrorInfo errorInfo;
|
||||
private String currentTimeStamp;
|
||||
|
||||
@ -107,7 +103,9 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
|
||||
// important add guru meditation
|
||||
addGuruMeditation();
|
||||
currentTimeStamp = CURRENT_TIMESTAMP_FORMATTER.format(LocalDateTime.now());
|
||||
// print current time, as zoned ISO8601 timestamp
|
||||
final ZonedDateTime now = ZonedDateTime.now();
|
||||
currentTimeStamp = now.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
|
||||
|
||||
activityErrorBinding.errorReportEmailButton.setOnClickListener(v ->
|
||||
openPrivacyPolicyDialog(this, "EMAIL"));
|
||||
@ -250,6 +248,9 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
.append("\n* __Content Language:__ ").append(getContentLanguageString())
|
||||
.append("\n* __App Language:__ ").append(getAppLanguage())
|
||||
.append("\n* __Service:__ ").append(errorInfo.getServiceName())
|
||||
.append("\n* __Timestamp:__ ").append(currentTimeStamp)
|
||||
.append("\n* __Package:__ ").append(getPackageName())
|
||||
.append("\n* __Service:__ ").append(errorInfo.getServiceName())
|
||||
.append("\n* __Version:__ ").append(BuildConfig.VERSION_NAME)
|
||||
.append("\n* __OS:__ ").append(getOsString()).append("\n");
|
||||
|
||||
|
||||
@ -95,7 +95,8 @@ import org.schabi.newpipe.player.Player;
|
||||
import org.schabi.newpipe.player.PlayerService;
|
||||
import org.schabi.newpipe.player.PlayerType;
|
||||
import org.schabi.newpipe.player.event.OnKeyDownListener;
|
||||
import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener;
|
||||
import org.schabi.newpipe.player.event.PlayerHolderLifecycleEventListener;
|
||||
import org.schabi.newpipe.player.event.PlayerServiceEventListener;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.player.helper.PlayerHolder;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
@ -136,7 +137,8 @@ import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
public final class VideoDetailFragment
|
||||
extends BaseStateFragment<StreamInfo>
|
||||
implements BackPressable,
|
||||
PlayerServiceExtendedEventListener,
|
||||
PlayerServiceEventListener,
|
||||
PlayerHolderLifecycleEventListener,
|
||||
OnKeyDownListener {
|
||||
public static final String KEY_SWITCHING_PLAYERS = "switching_players";
|
||||
|
||||
@ -234,10 +236,9 @@ public final class VideoDetailFragment
|
||||
// Service management
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@Override
|
||||
public void onServiceConnected(final Player connectedPlayer,
|
||||
final PlayerService connectedPlayerService,
|
||||
public void onServiceConnected(final PlayerService connectedPlayerService,
|
||||
final boolean playAfterConnect) {
|
||||
player = connectedPlayer;
|
||||
player = connectedPlayerService.getPlayer();
|
||||
playerService = connectedPlayerService;
|
||||
|
||||
// It will do nothing if the player is not in fullscreen mode
|
||||
@ -281,11 +282,11 @@ public final class VideoDetailFragment
|
||||
/*////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public static VideoDetailFragment getInstance(final int serviceId,
|
||||
@Nullable final String videoUrl,
|
||||
@Nullable final String url,
|
||||
@NonNull final String name,
|
||||
@Nullable final PlayQueue queue) {
|
||||
final VideoDetailFragment instance = new VideoDetailFragment();
|
||||
instance.setInitialData(serviceId, videoUrl, name, queue);
|
||||
instance.setInitialData(serviceId, url, name, queue);
|
||||
return instance;
|
||||
}
|
||||
|
||||
@ -393,7 +394,7 @@ public final class VideoDetailFragment
|
||||
if (activity.isFinishing() && isPlayerAvailable() && player.videoPlayerSelected()) {
|
||||
playerHolder.stopService();
|
||||
} else {
|
||||
playerHolder.setListener(null);
|
||||
playerHolder.unsetListeners();
|
||||
}
|
||||
|
||||
PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
@ -658,10 +659,10 @@ public final class VideoDetailFragment
|
||||
});
|
||||
|
||||
setupBottomPlayer();
|
||||
if (!playerHolder.isBound()) {
|
||||
if (playerHolder.isNotBoundYet()) {
|
||||
setHeightThumbnail();
|
||||
} else {
|
||||
playerHolder.startService(false, this);
|
||||
playerHolder.startService(false, this, this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1052,7 +1053,7 @@ public final class VideoDetailFragment
|
||||
|
||||
// See UI changes while remote playQueue changes
|
||||
if (!isPlayerAvailable()) {
|
||||
playerHolder.startService(false, this);
|
||||
playerHolder.startService(false, this, this);
|
||||
} else {
|
||||
// FIXME Workaround #7427
|
||||
player.setRecovery();
|
||||
@ -1115,7 +1116,7 @@ public final class VideoDetailFragment
|
||||
private void openNormalBackgroundPlayer(final boolean append) {
|
||||
// See UI changes while remote playQueue changes
|
||||
if (!isPlayerAvailable()) {
|
||||
playerHolder.startService(false, this);
|
||||
playerHolder.startService(false, this, this);
|
||||
}
|
||||
|
||||
final PlayQueue queue = setupPlayQueueForIntent(append);
|
||||
@ -1129,7 +1130,7 @@ public final class VideoDetailFragment
|
||||
|
||||
private void openMainPlayer() {
|
||||
if (!isPlayerServiceAvailable()) {
|
||||
playerHolder.startService(autoPlayEnabled, this);
|
||||
playerHolder.startService(autoPlayEnabled, this, this);
|
||||
return;
|
||||
}
|
||||
if (currentInfo == null) {
|
||||
@ -1384,9 +1385,11 @@ public final class VideoDetailFragment
|
||||
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
}
|
||||
// Rebound to the service if it was closed via notification or mini player
|
||||
if (!playerHolder.isBound()) {
|
||||
if (playerHolder.isNotBoundYet()) {
|
||||
playerHolder.startService(
|
||||
false, VideoDetailFragment.this);
|
||||
false,
|
||||
VideoDetailFragment.this,
|
||||
VideoDetailFragment.this);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -1723,7 +1726,7 @@ public final class VideoDetailFragment
|
||||
playQueue = queue;
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onQueueUpdate() called with: serviceId = ["
|
||||
+ serviceId + "], videoUrl = [" + url + "], name = ["
|
||||
+ serviceId + "], url = [" + url + "], name = ["
|
||||
+ title + "], playQueue = [" + playQueue + "]");
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,6 @@ package org.schabi.newpipe.fragments.list.comments
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.Fragment
|
||||
@ -20,7 +19,7 @@ class CommentsFragment : Fragment() {
|
||||
savedInstanceState: Bundle?
|
||||
) = content {
|
||||
AppTheme {
|
||||
Surface(color = MaterialTheme.colorScheme.background) {
|
||||
Surface {
|
||||
CommentSection()
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@ package org.schabi.newpipe.fragments.list.videos
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.Fragment
|
||||
@ -21,7 +20,7 @@ class RelatedItemsFragment : Fragment() {
|
||||
savedInstanceState: Bundle?
|
||||
) = content {
|
||||
AppTheme {
|
||||
Surface(color = MaterialTheme.colorScheme.background) {
|
||||
Surface {
|
||||
RelatedItems(requireArguments().serializable<StreamInfo>(KEY_INFO)!!)
|
||||
}
|
||||
}
|
||||
|
||||
@ -217,11 +217,16 @@ public final class PlayQueueActivity extends AppCompatActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(final ComponentName name, final IBinder service) {
|
||||
public void onServiceConnected(final ComponentName name, final IBinder binder) {
|
||||
Log.d(TAG, "Player service is connected");
|
||||
|
||||
if (service instanceof PlayerService.LocalBinder) {
|
||||
player = ((PlayerService.LocalBinder) service).getPlayer();
|
||||
if (binder instanceof PlayerService.LocalBinder localBinder) {
|
||||
final @Nullable PlayerService s = localBinder.getService();
|
||||
if (s == null) {
|
||||
player = null;
|
||||
} else {
|
||||
player = s.getPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
if (player == null || player.getPlayQueue() == null || player.exoPlayerIsNull()) {
|
||||
|
||||
@ -28,6 +28,8 @@ import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi;
|
||||
import org.schabi.newpipe.player.notification.NotificationPlayerUi;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
@ -36,7 +38,9 @@ import java.lang.ref.WeakReference;
|
||||
|
||||
|
||||
/**
|
||||
* One service for all players.
|
||||
* One background service for our player. Even though the player has multiple UIs
|
||||
* (e.g. the audio-only UI, the main UI, the pulldown-menu UI),
|
||||
* this allows us to keep playing even when switching between the different UIs.
|
||||
*/
|
||||
public final class PlayerService extends Service {
|
||||
private static final String TAG = PlayerService.class.getSimpleName();
|
||||
@ -46,6 +50,9 @@ public final class PlayerService extends Service {
|
||||
|
||||
private final IBinder mBinder = new PlayerService.LocalBinder(this);
|
||||
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Service's LifeCycle
|
||||
@ -167,6 +174,10 @@ public final class PlayerService extends Service {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows us this {@link org.schabi.newpipe.player.PlayerService} over the Service boundary
|
||||
* back to our {@link org.schabi.newpipe.player.helper.PlayerHolder}.
|
||||
*/
|
||||
public static class LocalBinder extends Binder {
|
||||
private final WeakReference<PlayerService> playerService;
|
||||
|
||||
@ -174,12 +185,12 @@ public final class PlayerService extends Service {
|
||||
this.playerService = new WeakReference<>(playerService);
|
||||
}
|
||||
|
||||
public PlayerService getService() {
|
||||
/**
|
||||
* Get the PlayerService object itself.
|
||||
* @return this
|
||||
* */
|
||||
public @Nullable PlayerService getService() {
|
||||
return playerService.get();
|
||||
}
|
||||
|
||||
public Player getPlayer() {
|
||||
return playerService.get().player;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,10 +14,12 @@ import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
||||
import static com.google.android.exoplayer2.util.Util.castNonNull;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getAndroidUserAgent;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getIosUserAgent;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTvHtml5UserAgent;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.isAndroidStreamingUrl;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.isIosStreamingUrl;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.isTvHtml5StreamingUrl;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.isWebStreamingUrl;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.isTvHtml5SimplyEmbeddedPlayerStreamingUrl;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.isWebEmbeddedPlayerStreamingUrl;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
import android.net.Uri;
|
||||
@ -270,6 +272,7 @@ public final class YoutubeHttpDataSource extends BaseDataSource implements HttpD
|
||||
|
||||
private static final String RN_PARAMETER = "&rn=";
|
||||
private static final String YOUTUBE_BASE_URL = "https://www.youtube.com";
|
||||
private static final byte[] POST_BODY = new byte[] {0x78, 0};
|
||||
|
||||
private final boolean allowCrossProtocolRedirects;
|
||||
private final boolean rangeParameterEnabled;
|
||||
@ -658,8 +661,11 @@ public final class YoutubeHttpDataSource extends BaseDataSource implements HttpD
|
||||
}
|
||||
}
|
||||
|
||||
final boolean isTvHtml5StreamingUrl = isTvHtml5StreamingUrl(requestUrl);
|
||||
|
||||
if (isWebStreamingUrl(requestUrl)
|
||||
|| isTvHtml5SimplyEmbeddedPlayerStreamingUrl(requestUrl)) {
|
||||
|| isTvHtml5StreamingUrl
|
||||
|| isWebEmbeddedPlayerStreamingUrl(requestUrl)) {
|
||||
httpURLConnection.setRequestProperty(HttpHeaders.ORIGIN, YOUTUBE_BASE_URL);
|
||||
httpURLConnection.setRequestProperty(HttpHeaders.REFERER, YOUTUBE_BASE_URL);
|
||||
httpURLConnection.setRequestProperty(HttpHeaders.SEC_FETCH_DEST, "empty");
|
||||
@ -679,6 +685,9 @@ public final class YoutubeHttpDataSource extends BaseDataSource implements HttpD
|
||||
} else if (isIosStreamingUrl) {
|
||||
httpURLConnection.setRequestProperty(HttpHeaders.USER_AGENT,
|
||||
getIosUserAgent(null));
|
||||
} else if (isTvHtml5StreamingUrl) {
|
||||
httpURLConnection.setRequestProperty(HttpHeaders.USER_AGENT,
|
||||
getTvHtml5UserAgent());
|
||||
} else {
|
||||
// non-mobile user agent
|
||||
httpURLConnection.setRequestProperty(HttpHeaders.USER_AGENT, DownloaderImpl.USER_AGENT);
|
||||
@ -687,22 +696,16 @@ public final class YoutubeHttpDataSource extends BaseDataSource implements HttpD
|
||||
httpURLConnection.setRequestProperty(HttpHeaders.ACCEPT_ENCODING,
|
||||
allowGzip ? "gzip" : "identity");
|
||||
httpURLConnection.setInstanceFollowRedirects(followRedirects);
|
||||
httpURLConnection.setDoOutput(httpBody != null);
|
||||
// Most clients use POST requests to fetch contents
|
||||
httpURLConnection.setRequestMethod("POST");
|
||||
httpURLConnection.setDoOutput(true);
|
||||
httpURLConnection.setFixedLengthStreamingMode(POST_BODY.length);
|
||||
httpURLConnection.connect();
|
||||
|
||||
// Mobile clients uses POST requests to fetch contents
|
||||
httpURLConnection.setRequestMethod(isAndroidStreamingUrl || isIosStreamingUrl
|
||||
? "POST"
|
||||
: DataSpec.getStringForHttpMethod(httpMethod));
|
||||
final OutputStream os = httpURLConnection.getOutputStream();
|
||||
os.write(POST_BODY);
|
||||
os.close();
|
||||
|
||||
if (httpBody != null) {
|
||||
httpURLConnection.setFixedLengthStreamingMode(httpBody.length);
|
||||
httpURLConnection.connect();
|
||||
final OutputStream os = httpURLConnection.getOutputStream();
|
||||
os.write(httpBody);
|
||||
os.close();
|
||||
} else {
|
||||
httpURLConnection.connect();
|
||||
}
|
||||
return httpURLConnection;
|
||||
}
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import com.google.android.exoplayer2.PlaybackParameters;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
|
||||
/** Player-specific events like queue or progress updates. */
|
||||
public interface PlayerEventListener {
|
||||
void onQueueUpdate(PlayQueue queue);
|
||||
void onPlaybackUpdate(int state, int repeatMode, boolean shuffled,
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
package org.schabi.newpipe.player.event;
|
||||
|
||||
import org.schabi.newpipe.player.PlayerService;
|
||||
|
||||
/** Gets signalled if our PlayerHolder (dis)connects from the PlayerService. */
|
||||
public interface PlayerHolderLifecycleEventListener {
|
||||
void onServiceConnected(PlayerService playerService,
|
||||
boolean playAfterConnect);
|
||||
void onServiceDisconnected();
|
||||
}
|
||||
@ -2,6 +2,9 @@ package org.schabi.newpipe.player.event;
|
||||
|
||||
import com.google.android.exoplayer2.PlaybackException;
|
||||
|
||||
/** {@link org.schabi.newpipe.player.event.PlayerEventListener} that also gets called for
|
||||
* application-specific events like screen rotation or UI changes.
|
||||
*/
|
||||
public interface PlayerServiceEventListener extends PlayerEventListener {
|
||||
void onViewCreated();
|
||||
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
package org.schabi.newpipe.player.event;
|
||||
|
||||
import org.schabi.newpipe.player.PlayerService;
|
||||
import org.schabi.newpipe.player.Player;
|
||||
|
||||
public interface PlayerServiceExtendedEventListener extends PlayerServiceEventListener {
|
||||
void onServiceConnected(Player player,
|
||||
PlayerService playerService,
|
||||
boolean playAfterConnect);
|
||||
void onServiceDisconnected();
|
||||
}
|
||||
@ -7,6 +7,7 @@ import android.content.ServiceConnection;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
@ -20,9 +21,13 @@ import org.schabi.newpipe.player.Player;
|
||||
import org.schabi.newpipe.player.PlayerService;
|
||||
import org.schabi.newpipe.player.PlayerType;
|
||||
import org.schabi.newpipe.player.event.PlayerServiceEventListener;
|
||||
import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener;
|
||||
import org.schabi.newpipe.player.event.PlayerHolderLifecycleEventListener;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
|
||||
/**
|
||||
* Singleton that manages a `PlayerService`
|
||||
* and can be used to control the player instance through the service.
|
||||
*/
|
||||
public final class PlayerHolder {
|
||||
|
||||
private PlayerHolder() {
|
||||
@ -39,10 +44,12 @@ public final class PlayerHolder {
|
||||
private static final boolean DEBUG = MainActivity.DEBUG;
|
||||
private static final String TAG = PlayerHolder.class.getSimpleName();
|
||||
|
||||
@Nullable private PlayerServiceExtendedEventListener listener;
|
||||
@Nullable private PlayerServiceEventListener listener;
|
||||
@Nullable private PlayerHolderLifecycleEventListener holderListener;
|
||||
|
||||
private final PlayerServiceConnection serviceConnection = new PlayerServiceConnection();
|
||||
private boolean bound;
|
||||
|
||||
@Nullable private PlayerService playerService;
|
||||
@Nullable private Player player;
|
||||
|
||||
@ -80,8 +87,8 @@ public final class PlayerHolder {
|
||||
return player != null && player.getPlayQueue() != null;
|
||||
}
|
||||
|
||||
public boolean isBound() {
|
||||
return bound;
|
||||
public boolean isNotBoundYet() {
|
||||
return !bound;
|
||||
}
|
||||
|
||||
public int getQueueSize() {
|
||||
@ -99,30 +106,48 @@ public final class PlayerHolder {
|
||||
return player.getPlayQueue().getIndex();
|
||||
}
|
||||
|
||||
public void setListener(@Nullable final PlayerServiceExtendedEventListener newListener) {
|
||||
listener = newListener;
|
||||
public void unsetListeners() {
|
||||
listener = null;
|
||||
holderListener = null;
|
||||
}
|
||||
|
||||
if (listener == null) {
|
||||
return;
|
||||
}
|
||||
public void setListener(@NonNull final PlayerServiceEventListener newListener,
|
||||
@NonNull final PlayerHolderLifecycleEventListener newHolderListener) {
|
||||
listener = newListener;
|
||||
holderListener = newHolderListener;
|
||||
|
||||
// Force reload data from service
|
||||
if (player != null) {
|
||||
listener.onServiceConnected(player, playerService, false);
|
||||
startPlayerListener();
|
||||
holderListener.onServiceConnected(playerService, false);
|
||||
player.setFragmentListener(internalListener);
|
||||
}
|
||||
}
|
||||
|
||||
// helper to handle context in common place as using the same
|
||||
// context to bind/unbind a service is crucial
|
||||
/**
|
||||
* Helper to handle context in common place as using the same
|
||||
* context to bind/unbind a service is crucial.
|
||||
*
|
||||
* @return the common context
|
||||
* */
|
||||
private Context getCommonContext() {
|
||||
return App.getInstance();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Connect to (and if needed start) the {@link PlayerService}
|
||||
* and bind {@link PlayerServiceConnection} to it.
|
||||
* If the service is already started, only set the listener.
|
||||
* @param playAfterConnect If the service is started, start playing immediately
|
||||
* @param newListener set this listener
|
||||
* @param newHolderListener set this listener
|
||||
* */
|
||||
public void startService(final boolean playAfterConnect,
|
||||
final PlayerServiceExtendedEventListener newListener) {
|
||||
final PlayerServiceEventListener newListener,
|
||||
final PlayerHolderLifecycleEventListener newHolderListener
|
||||
) {
|
||||
final Context context = getCommonContext();
|
||||
setListener(newListener);
|
||||
setListener(newListener, newHolderListener);
|
||||
if (bound) {
|
||||
return;
|
||||
}
|
||||
@ -131,8 +156,18 @@ public final class PlayerHolder {
|
||||
// bound twice. Prevent it with unbinding first
|
||||
unbind(context);
|
||||
ContextCompat.startForegroundService(context, new Intent(context, PlayerService.class));
|
||||
serviceConnection.doPlayAfterConnect(playAfterConnect);
|
||||
bind(context);
|
||||
serviceConnection.playAfterConnect = playAfterConnect;
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "bind() called");
|
||||
}
|
||||
|
||||
final Intent serviceIntent = new Intent(context, PlayerService.class);
|
||||
bound = context.bindService(serviceIntent, serviceConnection,
|
||||
Context.BIND_AUTO_CREATE);
|
||||
if (!bound) {
|
||||
context.unbindService(serviceConnection);
|
||||
}
|
||||
}
|
||||
|
||||
public void stopService() {
|
||||
@ -141,14 +176,35 @@ public final class PlayerHolder {
|
||||
context.stopService(new Intent(context, PlayerService.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Call {@link Context#unbindService(ServiceConnection)} on our service
|
||||
* (does not necesarily stop the service right away).
|
||||
* Remove all our listeners and deinitialize them.
|
||||
* @param context shared context
|
||||
* */
|
||||
private void unbind(final Context context) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "unbind() called");
|
||||
}
|
||||
|
||||
if (bound) {
|
||||
context.unbindService(serviceConnection);
|
||||
bound = false;
|
||||
if (player != null) {
|
||||
player.removeFragmentListener(internalListener);
|
||||
}
|
||||
playerService = null;
|
||||
player = null;
|
||||
if (holderListener != null) {
|
||||
holderListener.onServiceDisconnected();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PlayerServiceConnection implements ServiceConnection {
|
||||
|
||||
private boolean playAfterConnect = false;
|
||||
|
||||
public void doPlayAfterConnect(final boolean playAfterConnection) {
|
||||
this.playAfterConnect = playAfterConnection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(final ComponentName compName) {
|
||||
if (DEBUG) {
|
||||
@ -167,56 +223,22 @@ public final class PlayerHolder {
|
||||
final PlayerService.LocalBinder localBinder = (PlayerService.LocalBinder) service;
|
||||
|
||||
playerService = localBinder.getService();
|
||||
player = localBinder.getPlayer();
|
||||
if (listener != null) {
|
||||
listener.onServiceConnected(player, playerService, playAfterConnect);
|
||||
player = playerService != null ? playerService.getPlayer() : null;
|
||||
|
||||
if (holderListener != null) {
|
||||
holderListener.onServiceConnected(playerService, playAfterConnect);
|
||||
}
|
||||
startPlayerListener();
|
||||
}
|
||||
}
|
||||
|
||||
private void bind(final Context context) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "bind() called");
|
||||
}
|
||||
|
||||
final Intent serviceIntent = new Intent(context, PlayerService.class);
|
||||
bound = context.bindService(serviceIntent, serviceConnection,
|
||||
Context.BIND_AUTO_CREATE);
|
||||
if (!bound) {
|
||||
context.unbindService(serviceConnection);
|
||||
}
|
||||
}
|
||||
|
||||
private void unbind(final Context context) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "unbind() called");
|
||||
}
|
||||
|
||||
if (bound) {
|
||||
context.unbindService(serviceConnection);
|
||||
bound = false;
|
||||
stopPlayerListener();
|
||||
playerService = null;
|
||||
player = null;
|
||||
if (listener != null) {
|
||||
listener.onServiceDisconnected();
|
||||
if (player != null) {
|
||||
player.setFragmentListener(internalListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startPlayerListener() {
|
||||
if (player != null) {
|
||||
player.setFragmentListener(internalListener);
|
||||
}
|
||||
}
|
||||
|
||||
private void stopPlayerListener() {
|
||||
if (player != null) {
|
||||
player.removeFragmentListener(internalListener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate all {@link PlayerServiceEventListener} events to our current `listener` object.
|
||||
* Only difference is that if {@link PlayerServiceEventListener#onServiceStopped()} is called,
|
||||
* it also calls {@link PlayerHolder#unbind(Context)}.
|
||||
* */
|
||||
private final PlayerServiceEventListener internalListener =
|
||||
new PlayerServiceEventListener() {
|
||||
@Override
|
||||
|
||||
@ -25,9 +25,7 @@ import android.graphics.Color;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.provider.Settings;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@ -44,6 +42,7 @@ import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.exoplayer2.ui.SubtitleView;
|
||||
import com.google.android.exoplayer2.video.VideoSize;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
@ -522,11 +521,8 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh
|
||||
|
||||
@Override
|
||||
protected void setupSubtitleView(final float captionScale) {
|
||||
final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
||||
final int minimumLength = Math.min(metrics.heightPixels, metrics.widthPixels);
|
||||
final float captionRatioInverse = 20f + 4f * (1.0f - captionScale);
|
||||
binding.subtitleView.setFixedTextSize(
|
||||
TypedValue.COMPLEX_UNIT_PX, minimumLength / captionRatioInverse);
|
||||
binding.subtitleView.setFractionalTextSize(
|
||||
SubtitleView.DEFAULT_TEXT_SIZE_FRACTION * captionScale);
|
||||
}
|
||||
//endregion
|
||||
|
||||
|
||||
@ -424,9 +424,8 @@ public final class PopupPlayerUi extends VideoPlayerUi {
|
||||
|
||||
@Override
|
||||
protected void setupSubtitleView(final float captionScale) {
|
||||
final float captionRatio = (captionScale - 1.0f) / 5.0f + 1.0f;
|
||||
binding.subtitleView.setFractionalTextSize(
|
||||
SubtitleView.DEFAULT_TEXT_SIZE_FRACTION * captionRatio);
|
||||
SubtitleView.DEFAULT_TEXT_SIZE_FRACTION * captionScale);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -1414,6 +1414,10 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa
|
||||
binding.subtitleView.setStyle(captionStyle);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param captionScale Value returned by {@link PlayerHelper#getCaptionScale}.
|
||||
*/
|
||||
protected abstract void setupSubtitleView(float captionScale);
|
||||
//endregion
|
||||
|
||||
|
||||
@ -13,7 +13,6 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SearchBar
|
||||
import androidx.compose.material3.SearchBarDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
@ -91,14 +90,7 @@ fun Toolbar(
|
||||
active = true,
|
||||
onActiveChange = {
|
||||
isSearchActive = it
|
||||
},
|
||||
colors = SearchBarDefaults.colors(
|
||||
containerColor = MaterialTheme.colorScheme.background,
|
||||
inputFieldColors = SearchBarDefaults.inputFieldColors(
|
||||
focusedTextColor = MaterialTheme.colorScheme.onBackground,
|
||||
unfocusedTextColor = MaterialTheme.colorScheme.onBackground
|
||||
)
|
||||
)
|
||||
}
|
||||
) {
|
||||
onSearchQueryChange?.invoke(query)?.takeIf { it.isNotEmpty() }
|
||||
?.map { suggestionText -> SearchSuggestionItem(text = suggestionText) }
|
||||
|
||||
@ -3,16 +3,11 @@ package org.schabi.newpipe.ui.components.common
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
@Composable
|
||||
fun LoadingIndicator(modifier: Modifier = Modifier) {
|
||||
CircularProgressIndicator(
|
||||
modifier = modifier.fillMaxSize().wrapContentSize(Alignment.Center),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
trackColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
)
|
||||
CircularProgressIndicator(modifier = modifier.fillMaxSize().wrapContentSize(Alignment.Center))
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import my.nanihadesuka.compose.LazyColumnScrollbar
|
||||
import my.nanihadesuka.compose.ScrollbarSettings
|
||||
|
||||
@Composable
|
||||
@ -20,7 +21,7 @@ fun LazyColumnThemedScrollbar(
|
||||
indicatorContent: (@Composable (index: Int, isThumbSelected: Boolean) -> Unit)? = null,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
my.nanihadesuka.compose.LazyColumnScrollbar(
|
||||
LazyColumnScrollbar(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
settings = settings,
|
||||
|
||||
@ -64,7 +64,7 @@ private fun PlaylistListItemPreview() {
|
||||
playlist.uploaderName = "Uploader"
|
||||
|
||||
AppTheme {
|
||||
Surface(color = MaterialTheme.colorScheme.background) {
|
||||
Surface {
|
||||
PlaylistListItem(playlist)
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,7 +78,7 @@ private fun StreamListItemPreview(
|
||||
@PreviewParameter(StreamItemPreviewProvider::class) stream: StreamInfoItem
|
||||
) {
|
||||
AppTheme {
|
||||
Surface(color = MaterialTheme.colorScheme.background) {
|
||||
Surface {
|
||||
StreamListItem(stream, showProgress = false, isSelected = false)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
package org.schabi.newpipe.ui.components.items.stream
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
@ -34,8 +32,8 @@ fun StreamMenu(
|
||||
|
||||
DropdownMenu(expanded = expanded, onDismissRequest = onDismissRequest) {
|
||||
if (playerHolder.isPlayQueueReady) {
|
||||
StreamMenuItem(
|
||||
text = R.string.enqueue_stream,
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.enqueue_stream)) },
|
||||
onClick = {
|
||||
onDismissRequest()
|
||||
SparseItemUtil.fetchItemInfoIfSparse(context, stream) {
|
||||
@ -45,8 +43,8 @@ fun StreamMenu(
|
||||
)
|
||||
|
||||
if (playerHolder.queuePosition < playerHolder.queueSize - 1) {
|
||||
StreamMenuItem(
|
||||
text = R.string.enqueue_next_stream,
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.enqueue_next_stream)) },
|
||||
onClick = {
|
||||
onDismissRequest()
|
||||
SparseItemUtil.fetchItemInfoIfSparse(context, stream) {
|
||||
@ -57,8 +55,8 @@ fun StreamMenu(
|
||||
}
|
||||
}
|
||||
|
||||
StreamMenuItem(
|
||||
text = R.string.start_here_on_background,
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.start_here_on_background)) },
|
||||
onClick = {
|
||||
onDismissRequest()
|
||||
SparseItemUtil.fetchItemInfoIfSparse(context, stream) {
|
||||
@ -66,8 +64,8 @@ fun StreamMenu(
|
||||
}
|
||||
}
|
||||
)
|
||||
StreamMenuItem(
|
||||
text = R.string.start_here_on_popup,
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.start_here_on_popup)) },
|
||||
onClick = {
|
||||
onDismissRequest()
|
||||
SparseItemUtil.fetchItemInfoIfSparse(context, stream) {
|
||||
@ -75,8 +73,8 @@ fun StreamMenu(
|
||||
}
|
||||
}
|
||||
)
|
||||
StreamMenuItem(
|
||||
text = R.string.download,
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.download)) },
|
||||
onClick = {
|
||||
onDismissRequest()
|
||||
SparseItemUtil.fetchStreamInfoAndSaveToDatabase(
|
||||
@ -89,8 +87,8 @@ fun StreamMenu(
|
||||
}
|
||||
}
|
||||
)
|
||||
StreamMenuItem(
|
||||
text = R.string.add_to_playlist,
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.add_to_playlist)) },
|
||||
onClick = {
|
||||
onDismissRequest()
|
||||
val list = listOf(StreamEntity(stream))
|
||||
@ -103,29 +101,29 @@ fun StreamMenu(
|
||||
}
|
||||
}
|
||||
)
|
||||
StreamMenuItem(
|
||||
text = R.string.share,
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.share)) },
|
||||
onClick = {
|
||||
onDismissRequest()
|
||||
ShareUtils.shareText(context, stream.name, stream.url, stream.thumbnails)
|
||||
}
|
||||
)
|
||||
StreamMenuItem(
|
||||
text = R.string.open_in_browser,
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.open_in_browser)) },
|
||||
onClick = {
|
||||
onDismissRequest()
|
||||
ShareUtils.openUrlInBrowser(context, stream.url)
|
||||
}
|
||||
)
|
||||
StreamMenuItem(
|
||||
text = R.string.mark_as_watched,
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.mark_as_watched)) },
|
||||
onClick = {
|
||||
onDismissRequest()
|
||||
streamViewModel.markAsWatched(stream)
|
||||
}
|
||||
)
|
||||
StreamMenuItem(
|
||||
text = R.string.show_channel_details,
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.show_channel_details)) },
|
||||
onClick = {
|
||||
onDismissRequest()
|
||||
SparseItemUtil.fetchUploaderUrlIfSparse(
|
||||
@ -138,16 +136,3 @@ fun StreamMenu(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StreamMenuItem(
|
||||
@StringRes text: Int,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
Text(text = stringResource(text), color = MaterialTheme.colorScheme.onBackground)
|
||||
},
|
||||
onClick = onClick
|
||||
)
|
||||
}
|
||||
|
||||
@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
@ -94,7 +93,7 @@ private fun RelatedItemsPreview() {
|
||||
)
|
||||
|
||||
AppTheme {
|
||||
Surface(color = MaterialTheme.colorScheme.background) {
|
||||
Surface {
|
||||
RelatedItems(info)
|
||||
}
|
||||
}
|
||||
|
||||
@ -257,7 +257,7 @@ private fun CommentPreview(
|
||||
@PreviewParameter(CommentPreviewProvider::class) commentsInfoItem: CommentsInfoItem
|
||||
) {
|
||||
AppTheme {
|
||||
Surface(color = MaterialTheme.colorScheme.background) {
|
||||
Surface {
|
||||
Comment(commentsInfoItem) {}
|
||||
}
|
||||
}
|
||||
@ -267,7 +267,7 @@ private fun CommentPreview(
|
||||
@Composable
|
||||
private fun CommentListPreview() {
|
||||
AppTheme {
|
||||
Surface(color = MaterialTheme.colorScheme.background) {
|
||||
Surface {
|
||||
Column {
|
||||
for (comment in CommentPreviewProvider().values) {
|
||||
Comment(comment) {}
|
||||
|
||||
@ -6,14 +6,11 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.contentColorFor
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
@ -87,71 +84,64 @@ private fun CommentRepliesDialog(
|
||||
sheetState = sheetState,
|
||||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
// contentColorFor(MaterialTheme.colorScheme.containerColor), i.e. ModalBottomSheet's
|
||||
// default background color, does not resolve correctly, so need to manually set the
|
||||
// content color for MaterialTheme.colorScheme.background instead
|
||||
LocalContentColor provides contentColorFor(MaterialTheme.colorScheme.background)
|
||||
) {
|
||||
LazyColumnThemedScrollbar(state = listState) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.nestedScroll(nestedScrollInterop),
|
||||
state = listState
|
||||
) {
|
||||
LazyColumnThemedScrollbar(state = listState) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.nestedScroll(nestedScrollInterop),
|
||||
state = listState
|
||||
) {
|
||||
item {
|
||||
CommentRepliesHeader(
|
||||
comment = parentComment,
|
||||
onCommentAuthorOpened = nestedOnCommentAuthorOpened,
|
||||
)
|
||||
HorizontalDivider(
|
||||
thickness = 1.dp,
|
||||
modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
if (parentComment.replyCount >= 0) {
|
||||
item {
|
||||
CommentRepliesHeader(
|
||||
comment = parentComment,
|
||||
Text(
|
||||
modifier = Modifier.padding(
|
||||
horizontal = 12.dp,
|
||||
vertical = 4.dp
|
||||
),
|
||||
text = pluralStringResource(
|
||||
R.plurals.replies,
|
||||
parentComment.replyCount,
|
||||
parentComment.replyCount,
|
||||
),
|
||||
maxLines = 1,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (comments.itemCount == 0) {
|
||||
item {
|
||||
val refresh = comments.loadState.refresh
|
||||
if (refresh is LoadState.Loading) {
|
||||
LoadingIndicator(modifier = Modifier.padding(top = 8.dp))
|
||||
} else if (refresh is LoadState.Error) {
|
||||
// TODO use error panel instead
|
||||
EmptyStateComposable(
|
||||
EmptyStateSpec.DisabledComments.copy(
|
||||
descriptionText = {
|
||||
stringResource(R.string.error_unable_to_load_comments)
|
||||
},
|
||||
),
|
||||
)
|
||||
} else {
|
||||
EmptyStateComposable(EmptyStateSpec.NoComments)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
items(comments.itemCount) {
|
||||
Comment(
|
||||
comment = comments[it]!!,
|
||||
onCommentAuthorOpened = nestedOnCommentAuthorOpened,
|
||||
)
|
||||
HorizontalDivider(
|
||||
thickness = 1.dp,
|
||||
modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
if (parentComment.replyCount >= 0) {
|
||||
item {
|
||||
Text(
|
||||
modifier = Modifier.padding(
|
||||
horizontal = 12.dp,
|
||||
vertical = 4.dp
|
||||
),
|
||||
text = pluralStringResource(
|
||||
R.plurals.replies,
|
||||
parentComment.replyCount,
|
||||
parentComment.replyCount,
|
||||
),
|
||||
maxLines = 1,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (comments.itemCount == 0) {
|
||||
item {
|
||||
val refresh = comments.loadState.refresh
|
||||
if (refresh is LoadState.Loading) {
|
||||
LoadingIndicator(modifier = Modifier.padding(top = 8.dp))
|
||||
} else if (refresh is LoadState.Error) {
|
||||
// TODO use error panel instead
|
||||
EmptyStateComposable(
|
||||
EmptyStateSpec.DisabledComments.copy(
|
||||
descriptionText = {
|
||||
stringResource(R.string.error_unable_to_load_comments)
|
||||
}
|
||||
)
|
||||
)
|
||||
} else {
|
||||
EmptyStateComposable(EmptyStateSpec.NoComments)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
items(comments.itemCount) {
|
||||
Comment(
|
||||
comment = comments[it]!!,
|
||||
onCommentAuthorOpened = nestedOnCommentAuthorOpened,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,7 +143,7 @@ fun CommentRepliesHeaderPreview() {
|
||||
)
|
||||
|
||||
AppTheme {
|
||||
Surface(color = MaterialTheme.colorScheme.background) {
|
||||
Surface {
|
||||
CommentRepliesHeader(comment) {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -139,7 +139,7 @@ private fun CommentSection(
|
||||
@Composable
|
||||
private fun CommentSectionLoadingPreview() {
|
||||
AppTheme {
|
||||
Surface(color = MaterialTheme.colorScheme.background) {
|
||||
Surface {
|
||||
CommentSection(uiState = Resource.Loading, commentsFlow = flowOf())
|
||||
}
|
||||
}
|
||||
@ -167,7 +167,7 @@ private fun CommentSectionSuccessPreview() {
|
||||
}
|
||||
|
||||
AppTheme {
|
||||
Surface(color = MaterialTheme.colorScheme.background) {
|
||||
Surface {
|
||||
CommentSection(
|
||||
uiState = Resource.Success(
|
||||
CommentInfo(
|
||||
@ -186,7 +186,7 @@ private fun CommentSectionSuccessPreview() {
|
||||
@Composable
|
||||
private fun CommentSectionErrorPreview() {
|
||||
AppTheme {
|
||||
Surface(color = MaterialTheme.colorScheme.background) {
|
||||
Surface {
|
||||
CommentSection(uiState = Resource.Error(RuntimeException()), commentsFlow = flowOf())
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,62 +2,74 @@ package org.schabi.newpipe.ui.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val md_theme_light_primary = Color(0xFFBB171C)
|
||||
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
|
||||
val md_theme_light_primaryContainer = Color(0xFFFFDAD6)
|
||||
val md_theme_light_onPrimaryContainer = Color(0xFF410002)
|
||||
val md_theme_light_secondary = Color(0xFF984061)
|
||||
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
|
||||
val md_theme_light_secondaryContainer = Color(0xFFFFD9E2)
|
||||
val md_theme_light_onSecondaryContainer = Color(0xFF3E001D)
|
||||
val md_theme_light_tertiary = Color(0xFF006874)
|
||||
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
|
||||
val md_theme_light_tertiaryContainer = Color(0xFF97F0FF)
|
||||
val md_theme_light_onTertiaryContainer = Color(0xFF001F24)
|
||||
val md_theme_light_error = Color(0xFFBA1A1A)
|
||||
val md_theme_light_errorContainer = Color(0xFFFFDAD6)
|
||||
val md_theme_light_onError = Color(0xFFFFFFFF)
|
||||
val md_theme_light_onErrorContainer = Color(0xFF410002)
|
||||
val md_theme_light_background = Color(0xFFEEEEEE)
|
||||
val md_theme_light_onBackground = Color(0xFF1B1B1B)
|
||||
val md_theme_light_surface = Color(0xFFE53835)
|
||||
val md_theme_light_onSurface = Color(0xFFFFFFFF)
|
||||
val md_theme_light_surfaceVariant = Color(0xFFF5DDDB)
|
||||
val md_theme_light_onSurfaceVariant = Color(0xFF534341)
|
||||
val md_theme_light_outline = Color(0xFF857371)
|
||||
val md_theme_light_inverseOnSurface = Color(0xFFD6F6FF)
|
||||
val md_theme_light_inverseSurface = Color(0xFF00363F)
|
||||
val md_theme_light_inversePrimary = Color(0xFFFFB4AC)
|
||||
val md_theme_light_surfaceTint = Color(0xFFBB171C)
|
||||
val md_theme_light_outlineVariant = Color(0xFFD8C2BF)
|
||||
val md_theme_light_scrim = Color(0xFF000000)
|
||||
val primaryLight = Color(0xFF904A45)
|
||||
val onPrimaryLight = Color(0xFFFFFFFF)
|
||||
val primaryContainerLight = Color(0xFFFFDAD6)
|
||||
val onPrimaryContainerLight = Color(0xFF3B0908)
|
||||
val secondaryLight = Color(0xFF775653)
|
||||
val onSecondaryLight = Color(0xFFFFFFFF)
|
||||
val secondaryContainerLight = Color(0xFFFFDAD6)
|
||||
val onSecondaryContainerLight = Color(0xFF2C1513)
|
||||
val tertiaryLight = Color(0xFF725B2E)
|
||||
val onTertiaryLight = Color(0xFFFFFFFF)
|
||||
val tertiaryContainerLight = Color(0xFFFEDEA6)
|
||||
val onTertiaryContainerLight = Color(0xFF261900)
|
||||
val errorLight = Color(0xFFBA1A1A)
|
||||
val onErrorLight = Color(0xFFFFFFFF)
|
||||
val errorContainerLight = Color(0xFFFFDAD6)
|
||||
val onErrorContainerLight = Color(0xFF410002)
|
||||
val backgroundLight = Color(0xFFFFF8F7)
|
||||
val onBackgroundLight = Color(0xFF231918)
|
||||
val surfaceLight = Color(0xFFFFF8F7)
|
||||
val onSurfaceLight = Color(0xFF231918)
|
||||
val surfaceVariantLight = Color(0xFFF5DDDB)
|
||||
val onSurfaceVariantLight = Color(0xFF534342)
|
||||
val outlineLight = Color(0xFF857371)
|
||||
val outlineVariantLight = Color(0xFFD8C2BF)
|
||||
val scrimLight = Color(0xFF000000)
|
||||
val inverseSurfaceLight = Color(0xFF392E2D)
|
||||
val inverseOnSurfaceLight = Color(0xFFFFEDEB)
|
||||
val inversePrimaryLight = Color(0xFFFFB3AC)
|
||||
val surfaceDimLight = Color(0xFFE8D6D4)
|
||||
val surfaceBrightLight = Color(0xFFFFF8F7)
|
||||
val surfaceContainerLowestLight = Color(0xFFFFFFFF)
|
||||
val surfaceContainerLowLight = Color(0xFFFFF0EF)
|
||||
val surfaceContainerLight = Color(0xFFFCEAE8)
|
||||
val surfaceContainerHighLight = Color(0xFFF6E4E2)
|
||||
val surfaceContainerHighestLight = Color(0xFFF1DEDC)
|
||||
|
||||
val md_theme_dark_primary = Color(0xFFFFB4AC)
|
||||
val md_theme_dark_onPrimary = Color(0xFF690006)
|
||||
val md_theme_dark_primaryContainer = Color(0xFF93000D)
|
||||
val md_theme_dark_onPrimaryContainer = Color(0xFFFFDAD6)
|
||||
val md_theme_dark_secondary = Color(0xFFFFB1C8)
|
||||
val md_theme_dark_onSecondary = Color(0xFF5E1133)
|
||||
val md_theme_dark_secondaryContainer = Color(0xFF7B2949)
|
||||
val md_theme_dark_onSecondaryContainer = Color(0xFFFFD9E2)
|
||||
val md_theme_dark_tertiary = Color(0xFF4FD8EB)
|
||||
val md_theme_dark_onTertiary = Color(0xFF00363D)
|
||||
val md_theme_dark_tertiaryContainer = Color(0xFF004F58)
|
||||
val md_theme_dark_onTertiaryContainer = Color(0xFF97F0FF)
|
||||
val md_theme_dark_error = Color(0xFFFFB4AB)
|
||||
val md_theme_dark_errorContainer = Color(0xFF93000A)
|
||||
val md_theme_dark_onError = Color(0xFF690005)
|
||||
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
|
||||
val md_theme_dark_background = Color(0xFF212121)
|
||||
val md_theme_dark_onBackground = Color(0xFFFFFFFF)
|
||||
val md_theme_dark_surface = Color(0xFF992521)
|
||||
val md_theme_dark_onSurface = Color(0xFFFFFFFF)
|
||||
val md_theme_dark_surfaceVariant = Color(0xFF534341)
|
||||
val md_theme_dark_onSurfaceVariant = Color(0xFFD8C2BF)
|
||||
val md_theme_dark_outline = Color(0xFFA08C8A)
|
||||
val md_theme_dark_inverseOnSurface = Color(0xFF001F25)
|
||||
val md_theme_dark_inverseSurface = Color(0xFFA6EEFF)
|
||||
val md_theme_dark_inversePrimary = Color(0xFFBB171C)
|
||||
val md_theme_dark_surfaceTint = Color(0xFFFFB4AC)
|
||||
val md_theme_dark_outlineVariant = Color(0xFF534341)
|
||||
val md_theme_dark_scrim = Color(0xFF000000)
|
||||
val primaryDark = Color(0xFFFFB3AC)
|
||||
val onPrimaryDark = Color(0xFF571E1B)
|
||||
val primaryContainerDark = Color(0xFF73332F)
|
||||
val onPrimaryContainerDark = Color(0xFFFFDAD6)
|
||||
val secondaryDark = Color(0xFFE7BDB8)
|
||||
val onSecondaryDark = Color(0xFF442927)
|
||||
val secondaryContainerDark = Color(0xFF5D3F3C)
|
||||
val onSecondaryContainerDark = Color(0xFFFFDAD6)
|
||||
val tertiaryDark = Color(0xFFE1C38C)
|
||||
val onTertiaryDark = Color(0xFF402D04)
|
||||
val tertiaryContainerDark = Color(0xFF584419)
|
||||
val onTertiaryContainerDark = Color(0xFFFEDEA6)
|
||||
val errorDark = Color(0xFFFFB4AB)
|
||||
val onErrorDark = Color(0xFF690005)
|
||||
val errorContainerDark = Color(0xFF93000A)
|
||||
val onErrorContainerDark = Color(0xFFFFDAD6)
|
||||
val backgroundDark = Color(0xFF1A1110)
|
||||
val onBackgroundDark = Color(0xFFF1DEDC)
|
||||
val surfaceDark = Color(0xFF1A1110)
|
||||
val onSurfaceDark = Color(0xFFF1DEDC)
|
||||
val surfaceVariantDark = Color(0xFF534342)
|
||||
val onSurfaceVariantDark = Color(0xFFD8C2BF)
|
||||
val outlineDark = Color(0xFFA08C8A)
|
||||
val outlineVariantDark = Color(0xFF534342)
|
||||
val scrimDark = Color(0xFF000000)
|
||||
val inverseSurfaceDark = Color(0xFFF1DEDC)
|
||||
val inverseOnSurfaceDark = Color(0xFF392E2D)
|
||||
val inversePrimaryDark = Color(0xFF904A45)
|
||||
val surfaceDimDark = Color(0xFF1A1110)
|
||||
val surfaceBrightDark = Color(0xFF423735)
|
||||
val surfaceContainerLowestDark = Color(0xFF140C0B)
|
||||
val surfaceContainerLowDark = Color(0xFF231918)
|
||||
val surfaceContainerDark = Color(0xFF271D1C)
|
||||
val surfaceContainerHighDark = Color(0xFF322827)
|
||||
val surfaceContainerHighestDark = Color(0xFF3D3231)
|
||||
|
||||
@ -5,75 +5,102 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.preference.PreferenceManager
|
||||
|
||||
private val LightColors = lightColorScheme(
|
||||
primary = md_theme_light_primary,
|
||||
onPrimary = md_theme_light_onPrimary,
|
||||
primaryContainer = md_theme_light_primaryContainer,
|
||||
onPrimaryContainer = md_theme_light_onPrimaryContainer,
|
||||
secondary = md_theme_light_secondary,
|
||||
onSecondary = md_theme_light_onSecondary,
|
||||
secondaryContainer = md_theme_light_secondaryContainer,
|
||||
onSecondaryContainer = md_theme_light_onSecondaryContainer,
|
||||
tertiary = md_theme_light_tertiary,
|
||||
onTertiary = md_theme_light_onTertiary,
|
||||
tertiaryContainer = md_theme_light_tertiaryContainer,
|
||||
onTertiaryContainer = md_theme_light_onTertiaryContainer,
|
||||
error = md_theme_light_error,
|
||||
errorContainer = md_theme_light_errorContainer,
|
||||
onError = md_theme_light_onError,
|
||||
onErrorContainer = md_theme_light_onErrorContainer,
|
||||
background = md_theme_light_background,
|
||||
onBackground = md_theme_light_onBackground,
|
||||
surface = md_theme_light_surface,
|
||||
onSurface = md_theme_light_onSurface,
|
||||
surfaceVariant = md_theme_light_surfaceVariant,
|
||||
onSurfaceVariant = md_theme_light_onSurfaceVariant,
|
||||
outline = md_theme_light_outline,
|
||||
inverseOnSurface = md_theme_light_inverseOnSurface,
|
||||
inverseSurface = md_theme_light_inverseSurface,
|
||||
inversePrimary = md_theme_light_inversePrimary,
|
||||
surfaceTint = md_theme_light_surfaceTint,
|
||||
outlineVariant = md_theme_light_outlineVariant,
|
||||
scrim = md_theme_light_scrim,
|
||||
private val lightScheme = lightColorScheme(
|
||||
primary = primaryLight,
|
||||
onPrimary = onPrimaryLight,
|
||||
primaryContainer = primaryContainerLight,
|
||||
onPrimaryContainer = onPrimaryContainerLight,
|
||||
secondary = secondaryLight,
|
||||
onSecondary = onSecondaryLight,
|
||||
secondaryContainer = secondaryContainerLight,
|
||||
onSecondaryContainer = onSecondaryContainerLight,
|
||||
tertiary = tertiaryLight,
|
||||
onTertiary = onTertiaryLight,
|
||||
tertiaryContainer = tertiaryContainerLight,
|
||||
onTertiaryContainer = onTertiaryContainerLight,
|
||||
error = errorLight,
|
||||
onError = onErrorLight,
|
||||
errorContainer = errorContainerLight,
|
||||
onErrorContainer = onErrorContainerLight,
|
||||
background = backgroundLight,
|
||||
onBackground = onBackgroundLight,
|
||||
surface = surfaceLight,
|
||||
onSurface = onSurfaceLight,
|
||||
surfaceVariant = surfaceVariantLight,
|
||||
onSurfaceVariant = onSurfaceVariantLight,
|
||||
outline = outlineLight,
|
||||
outlineVariant = outlineVariantLight,
|
||||
scrim = scrimLight,
|
||||
inverseSurface = inverseSurfaceLight,
|
||||
inverseOnSurface = inverseOnSurfaceLight,
|
||||
inversePrimary = inversePrimaryLight,
|
||||
surfaceDim = surfaceDimLight,
|
||||
surfaceBright = surfaceBrightLight,
|
||||
surfaceContainerLowest = surfaceContainerLowestLight,
|
||||
surfaceContainerLow = surfaceContainerLowLight,
|
||||
surfaceContainer = surfaceContainerLight,
|
||||
surfaceContainerHigh = surfaceContainerHighLight,
|
||||
surfaceContainerHighest = surfaceContainerHighestLight,
|
||||
)
|
||||
|
||||
private val DarkColors = darkColorScheme(
|
||||
primary = md_theme_dark_primary,
|
||||
onPrimary = md_theme_dark_onPrimary,
|
||||
primaryContainer = md_theme_dark_primaryContainer,
|
||||
onPrimaryContainer = md_theme_dark_onPrimaryContainer,
|
||||
secondary = md_theme_dark_secondary,
|
||||
onSecondary = md_theme_dark_onSecondary,
|
||||
secondaryContainer = md_theme_dark_secondaryContainer,
|
||||
onSecondaryContainer = md_theme_dark_onSecondaryContainer,
|
||||
tertiary = md_theme_dark_tertiary,
|
||||
onTertiary = md_theme_dark_onTertiary,
|
||||
tertiaryContainer = md_theme_dark_tertiaryContainer,
|
||||
onTertiaryContainer = md_theme_dark_onTertiaryContainer,
|
||||
error = md_theme_dark_error,
|
||||
errorContainer = md_theme_dark_errorContainer,
|
||||
onError = md_theme_dark_onError,
|
||||
onErrorContainer = md_theme_dark_onErrorContainer,
|
||||
background = md_theme_dark_background,
|
||||
onBackground = md_theme_dark_onBackground,
|
||||
surface = md_theme_dark_surface,
|
||||
onSurface = md_theme_dark_onSurface,
|
||||
surfaceVariant = md_theme_dark_surfaceVariant,
|
||||
onSurfaceVariant = md_theme_dark_onSurfaceVariant,
|
||||
outline = md_theme_dark_outline,
|
||||
inverseOnSurface = md_theme_dark_inverseOnSurface,
|
||||
inverseSurface = md_theme_dark_inverseSurface,
|
||||
inversePrimary = md_theme_dark_inversePrimary,
|
||||
surfaceTint = md_theme_dark_surfaceTint,
|
||||
outlineVariant = md_theme_dark_outlineVariant,
|
||||
scrim = md_theme_dark_scrim,
|
||||
private val darkScheme = darkColorScheme(
|
||||
primary = primaryDark,
|
||||
onPrimary = onPrimaryDark,
|
||||
primaryContainer = primaryContainerDark,
|
||||
onPrimaryContainer = onPrimaryContainerDark,
|
||||
secondary = secondaryDark,
|
||||
onSecondary = onSecondaryDark,
|
||||
secondaryContainer = secondaryContainerDark,
|
||||
onSecondaryContainer = onSecondaryContainerDark,
|
||||
tertiary = tertiaryDark,
|
||||
onTertiary = onTertiaryDark,
|
||||
tertiaryContainer = tertiaryContainerDark,
|
||||
onTertiaryContainer = onTertiaryContainerDark,
|
||||
error = errorDark,
|
||||
onError = onErrorDark,
|
||||
errorContainer = errorContainerDark,
|
||||
onErrorContainer = onErrorContainerDark,
|
||||
background = backgroundDark,
|
||||
onBackground = onBackgroundDark,
|
||||
surface = surfaceDark,
|
||||
onSurface = onSurfaceDark,
|
||||
surfaceVariant = surfaceVariantDark,
|
||||
onSurfaceVariant = onSurfaceVariantDark,
|
||||
outline = outlineDark,
|
||||
outlineVariant = outlineVariantDark,
|
||||
scrim = scrimDark,
|
||||
inverseSurface = inverseSurfaceDark,
|
||||
inverseOnSurface = inverseOnSurfaceDark,
|
||||
inversePrimary = inversePrimaryDark,
|
||||
surfaceDim = surfaceDimDark,
|
||||
surfaceBright = surfaceBrightDark,
|
||||
surfaceContainerLowest = surfaceContainerLowestDark,
|
||||
surfaceContainerLow = surfaceContainerLowDark,
|
||||
surfaceContainer = surfaceContainerDark,
|
||||
surfaceContainerHigh = surfaceContainerHighDark,
|
||||
surfaceContainerHighest = surfaceContainerHighestDark,
|
||||
)
|
||||
|
||||
private val blackScheme = darkScheme.copy(surface = Color.Black)
|
||||
|
||||
@Composable
|
||||
fun AppTheme(useDarkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) {
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(LocalContext.current)
|
||||
val theme = sharedPreferences.getString("theme", "auto_device_theme")
|
||||
val nightTheme = sharedPreferences.getString("night_theme", "dark_theme")
|
||||
|
||||
MaterialTheme(
|
||||
colorScheme = if (useDarkTheme) DarkColors else LightColors,
|
||||
colorScheme = if (!useDarkTheme) {
|
||||
lightScheme
|
||||
} else if (theme == "black_theme" || nightTheme == "black_theme") {
|
||||
blackScheme
|
||||
} else {
|
||||
darkScheme
|
||||
},
|
||||
content = content
|
||||
)
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
import android.webkit.CookieManager;
|
||||
|
||||
import androidx.annotation.Dimension;
|
||||
import androidx.annotation.NonNull;
|
||||
@ -335,4 +336,17 @@ public final class DeviceUtils {
|
||||
&& !TX_50JXW834
|
||||
&& !HMB9213NW;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the device has support for WebView, see
|
||||
* <a href="https://stackoverflow.com/a/69626735">https://stackoverflow.com/a/69626735</a>
|
||||
*/
|
||||
public static boolean supportsWebView() {
|
||||
try {
|
||||
CookieManager.getInstance();
|
||||
return true;
|
||||
} catch (final Throwable ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -454,8 +454,12 @@ public final class NavigationHelper {
|
||||
if (fragment instanceof VideoDetailFragment && fragment.isVisible()) {
|
||||
onVideoDetailFragmentReady.run((VideoDetailFragment) fragment);
|
||||
} else {
|
||||
// Specify no url here, otherwise the VideoDetailFragment will start loading the
|
||||
// stream automatically if it's the first time it is being opened, but then
|
||||
// onVideoDetailFragmentReady will kick in and start another loading process.
|
||||
// See VideoDetailFragment.wasCleared() and its usage in doInitialLoadLogic().
|
||||
final VideoDetailFragment instance = VideoDetailFragment
|
||||
.getInstance(serviceId, url, title, playQueue);
|
||||
.getInstance(serviceId, null, title, playQueue);
|
||||
instance.setAutoPlay(autoPlay);
|
||||
|
||||
defaultTransaction(fragmentManager)
|
||||
|
||||
@ -0,0 +1,113 @@
|
||||
package org.schabi.newpipe.util.potoken
|
||||
|
||||
import com.grack.nanojson.JsonObject
|
||||
import com.grack.nanojson.JsonParser
|
||||
import com.grack.nanojson.JsonWriter
|
||||
import okio.ByteString.Companion.decodeBase64
|
||||
import okio.ByteString.Companion.toByteString
|
||||
|
||||
/**
|
||||
* Parses the raw challenge data obtained from the Create endpoint and returns an object that can be
|
||||
* embedded in a JavaScript snippet.
|
||||
*/
|
||||
fun parseChallengeData(rawChallengeData: String): String {
|
||||
val scrambled = JsonParser.array().from(rawChallengeData)
|
||||
|
||||
val challengeData = if (scrambled.size > 1 && scrambled.isString(1)) {
|
||||
val descrambled = descramble(scrambled.getString(1))
|
||||
JsonParser.array().from(descrambled)
|
||||
} else {
|
||||
scrambled.getArray(1)
|
||||
}
|
||||
|
||||
val messageId = challengeData.getString(0)
|
||||
val interpreterHash = challengeData.getString(3)
|
||||
val program = challengeData.getString(4)
|
||||
val globalName = challengeData.getString(5)
|
||||
val clientExperimentsStateBlob = challengeData.getString(7)
|
||||
|
||||
val privateDoNotAccessOrElseSafeScriptWrappedValue = challengeData.getArray(1, null)?.find { it is String }
|
||||
val privateDoNotAccessOrElseTrustedResourceUrlWrappedValue = challengeData.getArray(2, null)?.find { it is String }
|
||||
|
||||
return JsonWriter.string(
|
||||
JsonObject.builder()
|
||||
.value("messageId", messageId)
|
||||
.`object`("interpreterJavascript")
|
||||
.value("privateDoNotAccessOrElseSafeScriptWrappedValue", privateDoNotAccessOrElseSafeScriptWrappedValue)
|
||||
.value("privateDoNotAccessOrElseTrustedResourceUrlWrappedValue", privateDoNotAccessOrElseTrustedResourceUrlWrappedValue)
|
||||
.end()
|
||||
.value("interpreterHash", interpreterHash)
|
||||
.value("program", program)
|
||||
.value("globalName", globalName)
|
||||
.value("clientExperimentsStateBlob", clientExperimentsStateBlob)
|
||||
.done()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the raw integrity token data obtained from the GenerateIT endpoint to a JavaScript
|
||||
* `Uint8Array` that can be embedded directly in JavaScript code, and an [Int] representing the
|
||||
* duration of this token in seconds.
|
||||
*/
|
||||
fun parseIntegrityTokenData(rawIntegrityTokenData: String): Pair<String, Long> {
|
||||
val integrityTokenData = JsonParser.array().from(rawIntegrityTokenData)
|
||||
return base64ToU8(integrityTokenData.getString(0)) to integrityTokenData.getLong(1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string (usually the identifier used as input to `obtainPoToken`) to a JavaScript
|
||||
* `Uint8Array` that can be embedded directly in JavaScript code.
|
||||
*/
|
||||
fun stringToU8(identifier: String): String {
|
||||
return newUint8Array(identifier.toByteArray())
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a poToken encoded as a sequence of bytes represented as integers separated by commas
|
||||
* (e.g. "97,98,99" would be "abc"), which is the output of `Uint8Array::toString()` in JavaScript,
|
||||
* and converts it to the specific base64 representation for poTokens.
|
||||
*/
|
||||
fun u8ToBase64(poToken: String): String {
|
||||
return poToken.split(",")
|
||||
.map { it.toUByte().toByte() }
|
||||
.toByteArray()
|
||||
.toByteString()
|
||||
.base64()
|
||||
.replace("+", "-")
|
||||
.replace("/", "_")
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the scrambled challenge, decodes it from base64, adds 97 to each byte.
|
||||
*/
|
||||
private fun descramble(scrambledChallenge: String): String {
|
||||
return base64ToByteString(scrambledChallenge)
|
||||
.map { (it + 97).toByte() }
|
||||
.toByteArray()
|
||||
.decodeToString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a base64 string encoded in the specific base64 representation used by YouTube, and
|
||||
* returns a JavaScript `Uint8Array` that can be embedded directly in JavaScript code.
|
||||
*/
|
||||
private fun base64ToU8(base64: String): String {
|
||||
return newUint8Array(base64ToByteString(base64))
|
||||
}
|
||||
|
||||
private fun newUint8Array(contents: ByteArray): String {
|
||||
return "new Uint8Array([" + contents.joinToString(separator = ",") { it.toUByte().toString() } + "])"
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a base64 string encoded in the specific base64 representation used by YouTube.
|
||||
*/
|
||||
private fun base64ToByteString(base64: String): ByteArray {
|
||||
val base64Mod = base64
|
||||
.replace('-', '+')
|
||||
.replace('_', '/')
|
||||
.replace('.', '=')
|
||||
|
||||
return (base64Mod.decodeBase64() ?: throw PoTokenException("Cannot base64 decode"))
|
||||
.toByteArray()
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package org.schabi.newpipe.util.potoken
|
||||
|
||||
class PoTokenException(message: String) : Exception(message)
|
||||
|
||||
// to be thrown if the WebView provided by the system is broken
|
||||
class BadWebViewException(message: String) : Exception(message)
|
||||
|
||||
fun buildExceptionForJsError(error: String): Exception {
|
||||
return if (error.contains("SyntaxError"))
|
||||
BadWebViewException(error)
|
||||
else
|
||||
PoTokenException(error)
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
package org.schabi.newpipe.util.potoken
|
||||
|
||||
import android.content.Context
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import java.io.Closeable
|
||||
|
||||
/**
|
||||
* This interface was created to allow for multiple methods to generate poTokens in the future (e.g.
|
||||
* via WebView and via a local DOM implementation)
|
||||
*/
|
||||
interface PoTokenGenerator : Closeable {
|
||||
/**
|
||||
* Generates a poToken for the provided identifier, using the `integrityToken` and
|
||||
* `webPoSignalOutput` previously obtained in the initialization of [PoTokenWebView]. Can be
|
||||
* called multiple times.
|
||||
*/
|
||||
fun generatePoToken(identifier: String): Single<String>
|
||||
|
||||
/**
|
||||
* @return whether the `integrityToken` is expired, in which case all tokens generated by
|
||||
* [generatePoToken] will be invalid
|
||||
*/
|
||||
fun isExpired(): Boolean
|
||||
|
||||
interface Factory {
|
||||
/**
|
||||
* Initializes a [PoTokenGenerator] by loading the BotGuard VM, running it, and obtaining
|
||||
* an `integrityToken`. Can then be used multiple times to generate multiple poTokens with
|
||||
* [generatePoToken].
|
||||
*
|
||||
* @param context used e.g. to load the HTML asset or to instantiate a WebView
|
||||
*/
|
||||
fun newPoTokenGenerator(context: Context): Single<PoTokenGenerator>
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,131 @@
|
||||
package org.schabi.newpipe.util.potoken
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import org.schabi.newpipe.App
|
||||
import org.schabi.newpipe.BuildConfig
|
||||
import org.schabi.newpipe.extractor.NewPipe
|
||||
import org.schabi.newpipe.extractor.services.youtube.InnertubeClientRequestInfo
|
||||
import org.schabi.newpipe.extractor.services.youtube.PoTokenProvider
|
||||
import org.schabi.newpipe.extractor.services.youtube.PoTokenResult
|
||||
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper
|
||||
import org.schabi.newpipe.util.DeviceUtils
|
||||
|
||||
object PoTokenProviderImpl : PoTokenProvider {
|
||||
val TAG = PoTokenProviderImpl::class.simpleName
|
||||
private val webViewSupported by lazy { DeviceUtils.supportsWebView() }
|
||||
private var webViewBadImpl = false // whether the system has a bad WebView implementation
|
||||
|
||||
private object WebPoTokenGenLock
|
||||
private var webPoTokenVisitorData: String? = null
|
||||
private var webPoTokenStreamingPot: String? = null
|
||||
private var webPoTokenGenerator: PoTokenGenerator? = null
|
||||
|
||||
override fun getWebClientPoToken(videoId: String): PoTokenResult? {
|
||||
if (!webViewSupported || webViewBadImpl) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
return getWebClientPoToken(videoId = videoId, forceRecreate = false)
|
||||
} catch (e: RuntimeException) {
|
||||
// RxJava's Single wraps exceptions into RuntimeErrors, so we need to unwrap them here
|
||||
when (val cause = e.cause) {
|
||||
is BadWebViewException -> {
|
||||
Log.e(TAG, "Could not obtain poToken because WebView is broken", e)
|
||||
webViewBadImpl = true
|
||||
return null
|
||||
}
|
||||
null -> throw e
|
||||
else -> throw cause // includes PoTokenException
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param forceRecreate whether to force the recreation of [webPoTokenGenerator], to be used in
|
||||
* case the current [webPoTokenGenerator] threw an error last time
|
||||
* [PoTokenGenerator.generatePoToken] was called
|
||||
*/
|
||||
private fun getWebClientPoToken(videoId: String, forceRecreate: Boolean): PoTokenResult {
|
||||
// just a helper class since Kotlin does not have builtin support for 4-tuples
|
||||
data class Quadruple<T1, T2, T3, T4>(val t1: T1, val t2: T2, val t3: T3, val t4: T4)
|
||||
|
||||
val (poTokenGenerator, visitorData, streamingPot, hasBeenRecreated) =
|
||||
synchronized(WebPoTokenGenLock) {
|
||||
val shouldRecreate = webPoTokenGenerator == null || forceRecreate ||
|
||||
webPoTokenGenerator!!.isExpired()
|
||||
|
||||
if (shouldRecreate) {
|
||||
|
||||
val innertubeClientRequestInfo = InnertubeClientRequestInfo.ofWebClient()
|
||||
innertubeClientRequestInfo.clientInfo.clientVersion =
|
||||
YoutubeParsingHelper.getClientVersion()
|
||||
|
||||
webPoTokenVisitorData = YoutubeParsingHelper.getVisitorDataFromInnertube(
|
||||
innertubeClientRequestInfo,
|
||||
NewPipe.getPreferredLocalization(),
|
||||
NewPipe.getPreferredContentCountry(),
|
||||
YoutubeParsingHelper.getYouTubeHeaders(),
|
||||
YoutubeParsingHelper.YOUTUBEI_V1_URL,
|
||||
null,
|
||||
false
|
||||
)
|
||||
// close the current webPoTokenGenerator on the main thread
|
||||
webPoTokenGenerator?.let { Handler(Looper.getMainLooper()).post { it.close() } }
|
||||
|
||||
// create a new webPoTokenGenerator
|
||||
webPoTokenGenerator = PoTokenWebView
|
||||
.newPoTokenGenerator(App.instance).blockingGet()
|
||||
|
||||
// The streaming poToken needs to be generated exactly once before generating
|
||||
// any other (player) tokens.
|
||||
webPoTokenStreamingPot = webPoTokenGenerator!!
|
||||
.generatePoToken(webPoTokenVisitorData!!).blockingGet()
|
||||
}
|
||||
|
||||
return@synchronized Quadruple(
|
||||
webPoTokenGenerator!!,
|
||||
webPoTokenVisitorData!!,
|
||||
webPoTokenStreamingPot!!,
|
||||
shouldRecreate
|
||||
)
|
||||
}
|
||||
|
||||
val playerPot = try {
|
||||
// Not using synchronized here, since poTokenGenerator would be able to generate
|
||||
// multiple poTokens in parallel if needed. The only important thing is for exactly one
|
||||
// visitorData/streaming poToken to be generated before anything else.
|
||||
poTokenGenerator.generatePoToken(videoId).blockingGet()
|
||||
} catch (throwable: Throwable) {
|
||||
if (hasBeenRecreated) {
|
||||
// the poTokenGenerator has just been recreated (and possibly this is already the
|
||||
// second time we try), so there is likely nothing we can do
|
||||
throw throwable
|
||||
} else {
|
||||
// retry, this time recreating the [webPoTokenGenerator] from scratch;
|
||||
// this might happen for example if NewPipe goes in the background and the WebView
|
||||
// content is lost
|
||||
Log.e(TAG, "Failed to obtain poToken, retrying", throwable)
|
||||
return getWebClientPoToken(videoId = videoId, forceRecreate = true)
|
||||
}
|
||||
}
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(
|
||||
TAG,
|
||||
"poToken for $videoId: playerPot=$playerPot, " +
|
||||
"streamingPot=$streamingPot, visitor_data=$visitorData"
|
||||
)
|
||||
}
|
||||
|
||||
return PoTokenResult(visitorData, playerPot, streamingPot)
|
||||
}
|
||||
|
||||
override fun getWebEmbedClientPoToken(videoId: String): PoTokenResult? = null
|
||||
|
||||
override fun getAndroidClientPoToken(videoId: String): PoTokenResult? = null
|
||||
|
||||
override fun getIosClientPoToken(videoId: String): PoTokenResult? = null
|
||||
}
|
||||
@ -0,0 +1,395 @@
|
||||
package org.schabi.newpipe.util.potoken
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.webkit.ConsoleMessage
|
||||
import android.webkit.JavascriptInterface
|
||||
import android.webkit.WebChromeClient
|
||||
import android.webkit.WebView
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.webkit.WebSettingsCompat
|
||||
import androidx.webkit.WebViewFeature
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.core.SingleEmitter
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.schabi.newpipe.BuildConfig
|
||||
import org.schabi.newpipe.DownloaderImpl
|
||||
import java.time.Instant
|
||||
|
||||
class PoTokenWebView private constructor(
|
||||
context: Context,
|
||||
// to be used exactly once only during initialization!
|
||||
private val generatorEmitter: SingleEmitter<PoTokenGenerator>,
|
||||
) : PoTokenGenerator {
|
||||
private val webView = WebView(context)
|
||||
private val disposables = CompositeDisposable() // used only during initialization
|
||||
private val poTokenEmitters = mutableListOf<Pair<String, SingleEmitter<String>>>()
|
||||
private lateinit var expirationInstant: Instant
|
||||
|
||||
//region Initialization
|
||||
init {
|
||||
val webViewSettings = webView.settings
|
||||
//noinspection SetJavaScriptEnabled we want to use JavaScript!
|
||||
webViewSettings.javaScriptEnabled = true
|
||||
if (WebViewFeature.isFeatureSupported(WebViewFeature.SAFE_BROWSING_ENABLE)) {
|
||||
WebSettingsCompat.setSafeBrowsingEnabled(webViewSettings, false)
|
||||
}
|
||||
webViewSettings.userAgentString = USER_AGENT
|
||||
webViewSettings.blockNetworkLoads = true // the WebView does not need internet access
|
||||
|
||||
// so that we can run async functions and get back the result
|
||||
webView.addJavascriptInterface(this, JS_INTERFACE)
|
||||
|
||||
webView.webChromeClient = object : WebChromeClient() {
|
||||
override fun onConsoleMessage(m: ConsoleMessage): Boolean {
|
||||
if (m.message().contains("Uncaught")) {
|
||||
// There should not be any uncaught errors while executing the code, because
|
||||
// everything that can fail is guarded by try-catch. Therefore, this likely
|
||||
// indicates that there was a syntax error in the code, i.e. the WebView only
|
||||
// supports a really old version of JS.
|
||||
|
||||
val fmt = "\"${m.message()}\", source: ${m.sourceId()} (${m.lineNumber()})"
|
||||
val exception = BadWebViewException(fmt)
|
||||
Log.e(TAG, "This WebView implementation is broken: $fmt")
|
||||
|
||||
onInitializationErrorCloseAndCancel(exception)
|
||||
popAllPoTokenEmitters().forEach { (_, emitter) -> emitter.onError(exception) }
|
||||
}
|
||||
return super.onConsoleMessage(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Must be called right after instantiating [PoTokenWebView] to perform the actual
|
||||
* initialization. This will asynchronously go through all the steps needed to load BotGuard,
|
||||
* run it, and obtain an `integrityToken`.
|
||||
*/
|
||||
private fun loadHtmlAndObtainBotguard(context: Context) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, "loadHtmlAndObtainBotguard() called")
|
||||
}
|
||||
|
||||
disposables.add(
|
||||
Single.fromCallable {
|
||||
val html = context.assets.open("po_token.html").bufferedReader()
|
||||
.use { it.readText() }
|
||||
return@fromCallable html
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ html ->
|
||||
webView.loadDataWithBaseURL(
|
||||
"https://www.youtube.com",
|
||||
html.replaceFirst(
|
||||
"</script>",
|
||||
// calls downloadAndRunBotguard() when the page has finished loading
|
||||
"\n$JS_INTERFACE.downloadAndRunBotguard()</script>"
|
||||
),
|
||||
"text/html",
|
||||
"utf-8",
|
||||
null,
|
||||
)
|
||||
},
|
||||
this::onInitializationErrorCloseAndCancel
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called during initialization by the JavaScript snippet appended to the HTML page content in
|
||||
* [loadHtmlAndObtainBotguard] after the WebView content has been loaded.
|
||||
*/
|
||||
@JavascriptInterface
|
||||
fun downloadAndRunBotguard() {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, "downloadAndRunBotguard() called")
|
||||
}
|
||||
|
||||
makeBotguardServiceRequest(
|
||||
"https://www.youtube.com/api/jnn/v1/Create",
|
||||
"[ \"$REQUEST_KEY\" ]",
|
||||
) { responseBody ->
|
||||
val parsedChallengeData = parseChallengeData(responseBody)
|
||||
webView.evaluateJavascript(
|
||||
"""try {
|
||||
data = $parsedChallengeData
|
||||
runBotGuard(data).then(function (result) {
|
||||
this.webPoSignalOutput = result.webPoSignalOutput
|
||||
$JS_INTERFACE.onRunBotguardResult(result.botguardResponse)
|
||||
}, function (error) {
|
||||
$JS_INTERFACE.onJsInitializationError(error + "\n" + error.stack)
|
||||
})
|
||||
} catch (error) {
|
||||
$JS_INTERFACE.onJsInitializationError(error + "\n" + error.stack)
|
||||
}""",
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called during initialization by the JavaScript snippets from either
|
||||
* [downloadAndRunBotguard] or [onRunBotguardResult].
|
||||
*/
|
||||
@JavascriptInterface
|
||||
fun onJsInitializationError(error: String) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.e(TAG, "Initialization error from JavaScript: $error")
|
||||
}
|
||||
onInitializationErrorCloseAndCancel(buildExceptionForJsError(error))
|
||||
}
|
||||
|
||||
/**
|
||||
* Called during initialization by the JavaScript snippet from [downloadAndRunBotguard] after
|
||||
* obtaining the BotGuard execution output [botguardResponse].
|
||||
*/
|
||||
@JavascriptInterface
|
||||
fun onRunBotguardResult(botguardResponse: String) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, "botguardResponse: $botguardResponse")
|
||||
}
|
||||
makeBotguardServiceRequest(
|
||||
"https://www.youtube.com/api/jnn/v1/GenerateIT",
|
||||
"[ \"$REQUEST_KEY\", \"$botguardResponse\" ]",
|
||||
) { responseBody ->
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, "GenerateIT response: $responseBody")
|
||||
}
|
||||
val (integrityToken, expirationTimeInSeconds) = parseIntegrityTokenData(responseBody)
|
||||
|
||||
// leave 10 minutes of margin just to be sure
|
||||
expirationInstant = Instant.now().plusSeconds(expirationTimeInSeconds - 600)
|
||||
|
||||
webView.evaluateJavascript(
|
||||
"this.integrityToken = $integrityToken"
|
||||
) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, "initialization finished, expiration=${expirationTimeInSeconds}s")
|
||||
}
|
||||
generatorEmitter.onSuccess(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region Obtaining poTokens
|
||||
override fun generatePoToken(identifier: String): Single<String> =
|
||||
Single.create { emitter ->
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, "generatePoToken() called with identifier $identifier")
|
||||
}
|
||||
runOnMainThread(emitter) {
|
||||
addPoTokenEmitter(identifier, emitter)
|
||||
val u8Identifier = stringToU8(identifier)
|
||||
webView.evaluateJavascript(
|
||||
"""try {
|
||||
identifier = "$identifier"
|
||||
u8Identifier = $u8Identifier
|
||||
poTokenU8 = obtainPoToken(webPoSignalOutput, integrityToken, u8Identifier)
|
||||
poTokenU8String = ""
|
||||
for (i = 0; i < poTokenU8.length; i++) {
|
||||
if (i != 0) poTokenU8String += ","
|
||||
poTokenU8String += poTokenU8[i]
|
||||
}
|
||||
$JS_INTERFACE.onObtainPoTokenResult(identifier, poTokenU8String)
|
||||
} catch (error) {
|
||||
$JS_INTERFACE.onObtainPoTokenError(identifier, error + "\n" + error.stack)
|
||||
}""",
|
||||
) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the JavaScript snippet from [generatePoToken] when an error occurs in calling the
|
||||
* JavaScript `obtainPoToken()` function.
|
||||
*/
|
||||
@JavascriptInterface
|
||||
fun onObtainPoTokenError(identifier: String, error: String) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.e(TAG, "obtainPoToken error from JavaScript: $error")
|
||||
}
|
||||
popPoTokenEmitter(identifier)?.onError(buildExceptionForJsError(error))
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the JavaScript snippet from [generatePoToken] with the original identifier and the
|
||||
* result of the JavaScript `obtainPoToken()` function.
|
||||
*/
|
||||
@JavascriptInterface
|
||||
fun onObtainPoTokenResult(identifier: String, poTokenU8: String) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, "Generated poToken (before decoding): identifier=$identifier poTokenU8=$poTokenU8")
|
||||
}
|
||||
val poToken = try {
|
||||
u8ToBase64(poTokenU8)
|
||||
} catch (t: Throwable) {
|
||||
popPoTokenEmitter(identifier)?.onError(t)
|
||||
return
|
||||
}
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, "Generated poToken: identifier=$identifier poToken=$poToken")
|
||||
}
|
||||
popPoTokenEmitter(identifier)?.onSuccess(poToken)
|
||||
}
|
||||
|
||||
override fun isExpired(): Boolean {
|
||||
return Instant.now().isAfter(expirationInstant)
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region Handling multiple emitters
|
||||
/**
|
||||
* Adds the ([identifier], [emitter]) pair to the [poTokenEmitters] list. This makes it so that
|
||||
* multiple poToken requests can be generated invparallel, and the results will be notified to
|
||||
* the right emitters.
|
||||
*/
|
||||
private fun addPoTokenEmitter(identifier: String, emitter: SingleEmitter<String>) {
|
||||
synchronized(poTokenEmitters) {
|
||||
poTokenEmitters.add(Pair(identifier, emitter))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts and removes from the [poTokenEmitters] list a [SingleEmitter] based on its
|
||||
* [identifier]. The emitter is supposed to be used immediately after to either signal a success
|
||||
* or an error.
|
||||
*/
|
||||
private fun popPoTokenEmitter(identifier: String): SingleEmitter<String>? {
|
||||
return synchronized(poTokenEmitters) {
|
||||
poTokenEmitters.indexOfFirst { it.first == identifier }.takeIf { it >= 0 }?.let {
|
||||
poTokenEmitters.removeAt(it).second
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears [poTokenEmitters] and returns its previous contents. The emitters are supposed to be
|
||||
* used immediately after to either signal a success or an error.
|
||||
*/
|
||||
private fun popAllPoTokenEmitters(): List<Pair<String, SingleEmitter<String>>> {
|
||||
return synchronized(poTokenEmitters) {
|
||||
val result = poTokenEmitters.toList()
|
||||
poTokenEmitters.clear()
|
||||
result
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region Utils
|
||||
/**
|
||||
* Makes a POST request to [url] with the given [data] by setting the correct headers. Calls
|
||||
* [onInitializationErrorCloseAndCancel] in case of any network errors and also if the response
|
||||
* does not have HTTP code 200, therefore this is supposed to be used only during
|
||||
* initialization. Calls [handleResponseBody] with the response body if the response is
|
||||
* successful. The request is performed in the background and a disposable is added to
|
||||
* [disposables].
|
||||
*/
|
||||
private fun makeBotguardServiceRequest(
|
||||
url: String,
|
||||
data: String,
|
||||
handleResponseBody: (String) -> Unit,
|
||||
) {
|
||||
disposables.add(
|
||||
Single.fromCallable {
|
||||
return@fromCallable DownloaderImpl.getInstance().post(
|
||||
url,
|
||||
mapOf(
|
||||
// replace the downloader user agent
|
||||
"User-Agent" to listOf(USER_AGENT),
|
||||
"Accept" to listOf("application/json"),
|
||||
"Content-Type" to listOf("application/json+protobuf"),
|
||||
"x-goog-api-key" to listOf(GOOGLE_API_KEY),
|
||||
"x-user-agent" to listOf("grpc-web-javascript/0.1"),
|
||||
),
|
||||
data.toByteArray()
|
||||
)
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ response ->
|
||||
val httpCode = response.responseCode()
|
||||
if (httpCode != 200) {
|
||||
onInitializationErrorCloseAndCancel(
|
||||
PoTokenException("Invalid response code: $httpCode")
|
||||
)
|
||||
return@subscribe
|
||||
}
|
||||
val responseBody = response.responseBody()
|
||||
handleResponseBody(responseBody)
|
||||
},
|
||||
this::onInitializationErrorCloseAndCancel
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles any error happening during initialization, releasing resources and sending the error
|
||||
* to [generatorEmitter].
|
||||
*/
|
||||
private fun onInitializationErrorCloseAndCancel(error: Throwable) {
|
||||
runOnMainThread(generatorEmitter) {
|
||||
close()
|
||||
generatorEmitter.onError(error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases all [webView] and [disposables] resources.
|
||||
*/
|
||||
@MainThread
|
||||
override fun close() {
|
||||
disposables.dispose()
|
||||
|
||||
webView.clearHistory()
|
||||
// clears RAM cache and disk cache (globally for all WebViews)
|
||||
webView.clearCache(true)
|
||||
|
||||
// ensures that the WebView isn't doing anything when destroying it
|
||||
webView.loadUrl("about:blank")
|
||||
|
||||
webView.onPause()
|
||||
webView.removeAllViews()
|
||||
webView.destroy()
|
||||
}
|
||||
//endregion
|
||||
|
||||
companion object : PoTokenGenerator.Factory {
|
||||
private val TAG = PoTokenWebView::class.simpleName
|
||||
// Public API key used by BotGuard, which has been got by looking at BotGuard requests
|
||||
private const val GOOGLE_API_KEY = "AIzaSyDyT5W0Jh49F30Pqqtyfdf7pDLFKLJoAnw" // NOSONAR
|
||||
private const val REQUEST_KEY = "O43z0dpjhgX20SCx4KAo"
|
||||
private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.3"
|
||||
private const val JS_INTERFACE = "PoTokenWebView"
|
||||
|
||||
override fun newPoTokenGenerator(context: Context): Single<PoTokenGenerator> =
|
||||
Single.create { emitter ->
|
||||
runOnMainThread(emitter) {
|
||||
val potWv = PoTokenWebView(context, emitter)
|
||||
potWv.loadHtmlAndObtainBotguard(context)
|
||||
emitter.setDisposable(potWv.disposables)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs [runnable] on the main thread using `Handler(Looper.getMainLooper()).post()`, and
|
||||
* if the `post` fails emits an error on [emitterIfPostFails].
|
||||
*/
|
||||
private fun runOnMainThread(
|
||||
emitterIfPostFails: SingleEmitter<out Any>,
|
||||
runnable: Runnable,
|
||||
) {
|
||||
if (!Handler(Looper.getMainLooper()).post(runnable)) {
|
||||
emitterIfPostFails.onError(PoTokenException("Could not run on main thread"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
app/src/main/res/drawable-mdpi/volunteer_activism_ic.xml
Normal file
9
app/src/main/res/drawable-mdpi/volunteer_activism_ic.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:pathData="M640,520 L474,358q-31,-30 -52.5,-66.5T400,212q0,-55 38.5,-93.5T532,80q32,0 60,13.5t48,36.5q20,-23 48,-36.5t60,-13.5q55,0 93.5,38.5T880,212q0,43 -21,79.5T807,358L640,520ZM640,408 L749,301q19,-19 35,-40.5t16,-48.5q0,-22 -15,-37t-37,-15q-14,0 -26.5,5.5T700,182l-60,72 -60,-72q-9,-11 -21.5,-16.5T532,160q-22,0 -37,15t-15,37q0,27 16,48.5t35,40.5l109,107ZM280,740l278,76 238,-74q-5,-9 -14.5,-15.5T760,720L558,720q-27,0 -43,-2t-33,-8l-93,-31 22,-78 81,27q17,5 40,8t68,4q0,-11 -6.5,-21T578,606l-234,-86h-64v220ZM40,880v-440h304q7,0 14,1.5t13,3.5l235,87q33,12 53.5,42t20.5,66h80q50,0 85,33t35,87v40L560,900l-280,-78v58L40,880ZM120,800h80v-280h-80v280ZM640,254Z"
|
||||
android:fillColor="#FF000000"/>
|
||||
</vector>
|
||||
@ -856,4 +856,4 @@
|
||||
<string name="share_playlist">شارِك قائمة التشغيل</string>
|
||||
<string name="share_playlist_with_titles_message">شارِك قائمة التشغيل بتفاصيليها مثل اسم قائمة التشغيل وعناوين الفيديو أو كقائمة بسيطة من عناوين تشعّبيّة للفيديوهات</string>
|
||||
<string name="video_details_list_item">- %1$s: %2$s</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -83,7 +83,7 @@
|
||||
<string name="resume_on_audio_focus_gain_title">استئناف التشغيل</string>
|
||||
<string name="resume_on_audio_focus_gain_summary">متابعة التشغيل بعد المقاطعات (مثل المكالمات الهاتفية)</string>
|
||||
<string name="show_hold_to_append_title">إظهار تلميح \"اضغط للفتح\"</string>
|
||||
<string name="show_hold_to_append_summary">عرض تلميح عند الضغط على زر استخدام المشغل الخلفي أو النافذة المنبثقة في صفحة تفاصيل الفديو</string>
|
||||
<string name="show_hold_to_append_summary">إظهار التلميح عند الضغط على الخلفية أو الزر المنبثق في الفيديو \"التفاصيل:\\</string>
|
||||
<string name="settings_category_player_title">المشغل</string>
|
||||
<string name="settings_category_player_behavior_title">السلوك</string>
|
||||
<string name="popup_playing_toast">تشغيل في وضع منبثق</string>
|
||||
@ -558,7 +558,7 @@
|
||||
<string name="remove_watched">إزالة ما تمت مشاهدته</string>
|
||||
<string name="show_original_time_ago_summary">ستكون النصوص الأصلية من الخدمات مرئية في عناصر البث</string>
|
||||
<string name="show_original_time_ago_title">عرض الوقت الأصلي على العناصر</string>
|
||||
<string name="youtube_restricted_mode_enabled_title">قم بتشغيل \"وضع تقييد المحتوى\" في يوتيوب</string>
|
||||
<string name="youtube_restricted_mode_enabled_title">قم بتشغيل \"وضع تقييد المحتوى\" في يوتيوب\\</string>
|
||||
<string name="video_detail_by">بواسطة %s</string>
|
||||
<string name="channel_created_by">أنشأها %s</string>
|
||||
<string name="detail_sub_channel_thumbnail_view_description">الصورة الرمزية للقناة</string>
|
||||
@ -879,4 +879,5 @@
|
||||
\nهل تريد تمكين هذا؟</string>
|
||||
<string name="no">لا</string>
|
||||
<string name="import_settings_vulnerable_format">تستخدم الإعدادات الموجودة في عملية التصدير التي يتم استيرادها تنسيقًا عرضة للاختراق تم إهماله منذ NewPipe 0.27.0. تأكد من أن التصدير الذي يتم استيراده من مصدر موثوق به، ويفضل استخدام عمليات التصدير التي تم الحصول عليها من NewPipe 0.27.0 أو الأحدث في المستقبل فقط. سيتم قريبًا إزالة دعم استيراد الإعدادات بهذا التنسيق الضعيف تمامًا، وبعد ذلك لن تتمكن الإصدارات القديمة من NewPipe من استيراد إعدادات التصدير من الإصدارات الجديدة بعد الآن.</string>
|
||||
</resources>
|
||||
<string name="audio_track_type_secondary">الثانوي</string>
|
||||
</resources>
|
||||
|
||||
@ -602,7 +602,7 @@
|
||||
<string name="export_to">Bura ixrac et</string>
|
||||
<string name="import_file_title">Faylı idxal et</string>
|
||||
<string name="subscriptions_import_unsuccessful">Abunəlikləri idxal etmək mümkün olmadı</string>
|
||||
<string name="start_accept_privacy_policy">Avropa Ümumi Məlumat Mühafizəsi Qaydasına (GDPR) riayət etmək üçün diqqətinizi NewPipe məxfilik siyasətinə cəlb edirik. Xahiş edirik, diqqətlə oxuyun. \nXəta məlumatın bizə göndərmək üçün qəbul etməlisiniz.</string>
|
||||
<string name="start_accept_privacy_policy">Avropa Ümumi Məlumat Mühafizəsi Qaydasına (GDPR) riayət etmək üçün diqqətinizi NewPipe məxfilik siyasətinə cəlb edirik. Xahiş edirik, diqqətlə oxuyun.\nXəta məlumatın bizə göndərmək üçün qəbul etməlisiniz.</string>
|
||||
<string name="overwrite_unrelated_warning">Bu adda fayl artıq mövcuddur</string>
|
||||
<string name="download_already_pending">Bu adla gözlənilən bir endirmə var</string>
|
||||
<string name="error_path_creation">Təyinat qovluğu yaradıla bilməz</string>
|
||||
@ -667,7 +667,7 @@
|
||||
<string name="feed_use_dedicated_fetch_method_summary">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)</string>
|
||||
<string name="no_appropriate_file_manager_message">Bu əməliyyat üçün uyğun fayl meneceri tapılmadı. Zəhmət olmasa, fayl menecerini quraşdır və ya endirmə tənzimləmələrində \'%s\'-i qeyri-aktiv etməyə çalış</string>
|
||||
<string name="feed_load_error_account_info">\'%s\' üçün axın yükləmək mümkün olmadı.</string>
|
||||
<string name="no_appropriate_file_manager_message_android_10">Bu fəaliyyət üçün uyğun fayl meneceri tapılmadı.\nXahiş olunur, Yaddaş Giriş Quruluşuna uyğun fayl meneceri quraşdırın.</string>
|
||||
<string name="no_appropriate_file_manager_message_android_10">Bu fəaliyyət üçün uyğun fayl meneceri tapılmadı.\nXahiş olunur, Yaddaş Giriş Quruluşuna uyğun fayl meneceri quraşdırın</string>
|
||||
<string name="youtube_music_premium_content">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.</string>
|
||||
<string name="description_select_note">İndi açıqlamadakı mətni seçə bilərsiniz. Nəzərə alın ki, seçim rejimində səhifə titrəyə və linklər kliklənməyə bilər.</string>
|
||||
<string name="notification_scale_to_square_image_summary">Bildirişdə göstərilən video miniatürünü 16:9-dan 1:1 görünüş nisbətinə qədər kəs</string>
|
||||
@ -741,7 +741,7 @@
|
||||
<string name="feed_fetch_channel_tabs">Kanal səhifələrin əldə et</string>
|
||||
<string name="metadata_avatars">Avatarlar</string>
|
||||
<string name="metadata_subchannel_avatars">Alt kanal avatarları</string>
|
||||
<string name="feed_fetch_channel_tabs_summary">Axın yenilənərkən əldə edilən səhifələr.Kanal sürətli rejim istifadə edərək yenilənirsə, bu seçimin heç bir təsiri yoxdur.</string>
|
||||
<string name="feed_fetch_channel_tabs_summary">Axın yenilənərkən əldə edilən səhifələr. Kanal sürətli rejim istifadə edərək yenilənirsə, bu seçimin heç bir təsiri yoxdur.</string>
|
||||
<string name="metadata_uploader_avatars">Yükləyici avatarları</string>
|
||||
<string name="metadata_thumbnails">Miniatürlər</string>
|
||||
<string name="notification_actions_summary_android13">Aşağıdakı hər bildiriş fəaliyyətin ona toxunub redaktə edin. İlk üç fəaliyyət (oynatma/fasilə, əvvəlki və növbəti) sistem tərəfindən tənzimlənib və dəyişdirilə bilməz.</string>
|
||||
@ -802,4 +802,4 @@
|
||||
<string name="image_quality_summary">Məlumat və yaddaş istifadəsini azaltmaq üçün şəkillərin keyfiyyətini və ya şəkillərin əsla yüklənib-yüklənilməməsini seçin. Dəyişikliklər həm yaddaşdaxili, həm də diskdə olan təsvir qalığın təmizləyir — %s</string>
|
||||
<string name="share_playlist_with_list">URL siyahısını paylaşın</string>
|
||||
<string name="audio_track_type_secondary">ikinci dərəcəli</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -563,4 +563,4 @@
|
||||
<string name="install">Instalar</string>
|
||||
<string name="no_player_found">Nun s\'atopó nengún reproductor de fluxos. ¿Instalar VLC\?</string>
|
||||
<string name="main_bg_subtitle">Toca «Buscar» pa entamar</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -555,4 +555,4 @@
|
||||
<string name="error_unable_to_load_comments">Fikrlarni yuklab bo‘lmadi</string>
|
||||
<string name="import_settings">Sozlamalarni ham import qilmoqchimisiz\?</string>
|
||||
<string name="override_current_data">Bu sizning joriy sozlamangizni bekor qiladi.</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -60,7 +60,7 @@
|
||||
<string name="show_next_and_similar_title">Покажи „следващ“ и „подобни“</string>
|
||||
<string name="show_hold_to_append_title">Покажи съвет „Задръжте за поставяне в опашка“</string>
|
||||
<string name="unsupported_url">Непознат URL</string>
|
||||
<string name="content_language_title">Език на съдържанието по подразбиране</string>
|
||||
<string name="content_language_title">Език на съдържание по подразбиране</string>
|
||||
<string name="settings_category_player_title">Плейър</string>
|
||||
<string name="settings_category_player_behavior_title">Поведение</string>
|
||||
<string name="settings_category_video_audio_title">Видео и аудио</string>
|
||||
@ -582,9 +582,7 @@
|
||||
<string name="remove_duplicates_title">Премахни повторения?</string>
|
||||
<string name="feed_groups_header_title">Група от канали</string>
|
||||
<string name="feed_load_error_account_info">Неуспешно зареждане на емисия за \'%s\'.</string>
|
||||
<string name="feed_load_error_terminated">Акаунтът на автора е бил отстранен.
|
||||
\nNewPipe няма да може да зареди тази емисия вече.
|
||||
\nИскате ли да махнете абонамента от този канал?</string>
|
||||
<string name="feed_load_error_terminated">Профилът на автора е бил отстранен. \nNewPipe няма да може да зареди тази емисия вече. \nИскате ли да махнете абонамента от този канал?</string>
|
||||
<string name="feed_update_threshold_option_always_update">Винаги опреснявай</string>
|
||||
<string name="feed_load_error">Грешка при зареждане на емисия</string>
|
||||
<string name="feed_show_hide_streams">Покажи/Скрий потоци</string>
|
||||
@ -693,7 +691,7 @@
|
||||
<string name="card">Карта</string>
|
||||
<string name="delete_downloaded_files_confirm">Изтрий всички изтеглени файлове от диска?</string>
|
||||
<string name="enable_queue_limit_desc">Едно изтегляне ще се изпълнява едновременно</string>
|
||||
<string name="downloads_storage_ask_title">Попитайте къде да изтеглите</string>
|
||||
<string name="downloads_storage_ask_title">Подкана за папка за изтегляне</string>
|
||||
<string name="systems_language">Система по подразбиране</string>
|
||||
<string name="remove_duplicates">Премахване на дубликати</string>
|
||||
<string name="remove_duplicates_message">Искате ли да премахнете всички дублиращи се потоци в този плейлист?</string>
|
||||
@ -752,7 +750,7 @@
|
||||
<string name="enable_streams_notifications_title">Известия за нови потоци</string>
|
||||
<string name="any_network">Всяка мрежа</string>
|
||||
<string name="updates_setting_description">Покажи известие за актуализация на приложението, когато е налична нова версия</string>
|
||||
<string name="account_terminated">Акаунтът е прекратен</string>
|
||||
<string name="account_terminated">Профилът е прекратен</string>
|
||||
<string name="detail_pinned_comment_view_description">Фиксиран коментар</string>
|
||||
<string name="streams_not_yet_supported_removed">Потоци, които все още не се поддържат от програмата за изтегляне, не се показват</string>
|
||||
<string name="soundcloud_go_plus_content">Това е песен на SoundCloud Go+, поне във вашата страна, така че не може да бъде предавана поточно или изтеглена от NewPipe.</string>
|
||||
@ -812,4 +810,4 @@
|
||||
<string name="always_use_exoplayer_set_output_surface_workaround_title">Винаги използвайте заобикаляне на настройката на повърхността на видеоизхода на ExoPlayer</string>
|
||||
<string name="clear_playback_states_title">Изтрий позиции за възпроизвеждане</string>
|
||||
<string name="audio_track_type_secondary">вторичен</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -450,4 +450,4 @@
|
||||
<string name="metadata_tags">ট্যাগসমূহ</string>
|
||||
<string name="notification_colorize_summary">অ্যান্ড্রয়েডকে থাম্বনেইলের প্রধান রং অনুযায়ী রঙিন করুন (উল্লেখ্য যে, এটি সব ডিভাইসে উপলব্ধ নয়)</string>
|
||||
<string name="unknown_format">অজানা ধরন</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -307,4 +307,4 @@
|
||||
<string name="notification_scale_to_square_image_summary">বিজ্ঞপ্তিতে প্রদর্শিত ভিডিও থাম্বনেল 16:9 থেকে 1:1 অনুপাতের করুন (বিকৃতি দেখা যেতে পারে)</string>
|
||||
<string name="notification_action_shuffle">অদলবদল</string>
|
||||
<string name="notification_action_nothing">কিছু না</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -632,4 +632,4 @@
|
||||
<string name="main_page_content_swipe_remove">ভুক্তি মুছতে ডানে-বামে সরাও</string>
|
||||
<string name="loading_stream_details">সম্প্রচার বিষয়ক তথ্য প্রক্রিয়ারত…</string>
|
||||
<string name="progressive_load_interval_title">প্লেব্যাক লোড বিরতির আকার</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -726,4 +726,4 @@
|
||||
<string name="audio_track">Pista d\'àudio</string>
|
||||
<string name="no">No</string>
|
||||
<string name="no_streams">Cap emissió</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -700,4 +700,4 @@
|
||||
<string name="ignore_hardware_media_buttons_summary">بەسوودە، بۆ نموونە، ئەگەر هێدسێتێک بەکاربهێنیت لەگەڵ دوگمەی فیزیکی شکاو</string>
|
||||
<string name="progressive_load_interval_title">قەبارەی نێوان بارکردنی پەخشکردن</string>
|
||||
<string name="ignore_hardware_media_buttons_title">دوگمەی ڕووداوەکانی میدیای هاردوێر بەجێبهێڵە</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -838,4 +838,4 @@
|
||||
\nChcete tuto funkci povolit?</string>
|
||||
<string name="import_settings_vulnerable_format">Nastavení v importovaném exportu používají zranitelný formát. NewPipe používá nový formát od verze 0.27.0. Ujistěte se, že export importujete z důvěryhodného zdroje a v budoucnu upřednostňujte používání exportů získaných z NewPipe 0.27.0 nebo novějších. Podpora importu nastavení v tomto zranitelném formátu bude brzy kompletně odstraněna, kvůli čemuž staré verze NewPipe nebudou moci importovat nastavení z exportů z nových verzí.</string>
|
||||
<string name="audio_track_type_secondary">sekundární</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -823,4 +823,5 @@
|
||||
\nEr du sikker på, at du vil fortsætte?</string>
|
||||
<string name="import_settings_vulnerable_format">Indstillingerne i den eksport, der importeres, bruger et sårbart format, der er blevet forældet siden NewPipe 0.27.0. Sørg for, at den eksport, der importeres, er fra en pålidelig kilde, og brug helst kun eksport fra NewPipe 0.27.0 eller nyere i fremtiden. Understøttelse af import af indstillinger i dette sårbare format fjernes snart helt, og så vil gamle versioner af NewPipe ikke længere være i stand til at importere indstillinger fra eksport fra nye versioner.</string>
|
||||
<string name="settings_category_backup_restore_title">Sikkerhedskopiering og gendannelse</string>
|
||||
</resources>
|
||||
<string name="audio_track_type_secondary">sekundær</string>
|
||||
</resources>
|
||||
|
||||
@ -135,7 +135,7 @@
|
||||
<string name="action_history">Verlauf</string>
|
||||
<string name="subscription_change_failed">Abonnement konnte nicht geändert werden</string>
|
||||
<string name="subscription_update_failed">Abonnement konnte nicht aktualisiert werden</string>
|
||||
<string name="resume_on_audio_focus_gain_summary">Nach Unterbrechungen (z. B. Telefonaten) Wiedergabe fortsetzen</string>
|
||||
<string name="resume_on_audio_focus_gain_summary">Nach Unterbrechungen (z. B. Telefonaten) Wiedergabe fortsetzen</string>
|
||||
<string name="notification_channel_name">NewPipe-Benachrichtigung</string>
|
||||
<string name="notification_channel_description">Benachrichtigungen für den NewPipe-Player</string>
|
||||
<string name="settings_category_player_behavior_title">Verhalten</string>
|
||||
@ -319,7 +319,7 @@
|
||||
\nDu musst den Datenschutzrichtlinien zustimmen, um den Fehlerbericht an uns zu senden.</string>
|
||||
<string name="limit_data_usage_none_description">Unbegrenzt</string>
|
||||
<string name="limit_mobile_data_usage_title">Auflösung bei Verwendung mobiler Daten begrenzen</string>
|
||||
<string name="minimize_on_exit_title">Beim App-wechsel minimieren</string>
|
||||
<string name="minimize_on_exit_title">Beim App-Wechsel minimieren</string>
|
||||
<string name="minimize_on_exit_summary">Aktion beim Umschalten auf eine andere App vom Haupt-Videoplayer — %s</string>
|
||||
<string name="minimize_on_exit_none_description">Keine</string>
|
||||
<string name="minimize_on_exit_background_description">Für die Wiedergabe im Hintergrund minimieren</string>
|
||||
@ -409,7 +409,7 @@
|
||||
<string name="error_progress_lost">Vorgang abgebrochen, da die Datei gelöscht wurde</string>
|
||||
<string name="confirm_prompt">Möchtest du deinen Downloadverlauf oder alle heruntergeladenen Dateien löschen\?</string>
|
||||
<string name="enable_queue_limit">Downloadwarteschlange begrenzen</string>
|
||||
<string name="enable_queue_limit_desc">Ein Download wird zur gleichen Zeit ausgeführt</string>
|
||||
<string name="enable_queue_limit_desc">Ein Download wird gleichzeitig ausgeführt</string>
|
||||
<string name="start_downloads">Downloads starten</string>
|
||||
<string name="pause_downloads">Downloads anhalten</string>
|
||||
<string name="downloads_storage_ask_title">Downloadziel abfragen</string>
|
||||
@ -497,7 +497,7 @@
|
||||
<string name="feed_oldest_subscription_update">Feed zuletzt aktualisiert: %s</string>
|
||||
<string name="feed_update_threshold_title">Grenzwert für Feed-Aktualisierung</string>
|
||||
<string name="feed_use_dedicated_fetch_method_title">Aus fest zugeordnetem Feed abrufen wenn verfügbar</string>
|
||||
<string name="feed_use_dedicated_fetch_method_summary">Steht in manchen Diensten zur Verfügung, ist meist viel schneller, liefert aber eventuell eine eingeschränkte Anzahl an Elementen und oft unvollständige Informationen (z. B. keine Videolänge, keinen Elementtyp, keinen Live-Status)</string>
|
||||
<string name="feed_use_dedicated_fetch_method_summary">Steht in manchen Diensten zur Verfügung, ist meist viel schneller, liefert aber eventuell eine eingeschränkte Anzahl an Elementen und oft unvollständige Informationen (z. B. keine Videolänge, keinen Elementtyp, keinen Live-Status)</string>
|
||||
<string name="feed_use_dedicated_fetch_method_help_text">Glaubst du, dass das Laden von Feeds zu langsam ist? Wenn ja, versuche den Schnelllademodus einzuschalten (du kannst ihn in den Einstellungen oder über die Schaltfläche unten ändern).
|
||||
\n
|
||||
\nNewPipe bietet zwei Feed-Ladestrategien:
|
||||
@ -527,7 +527,7 @@
|
||||
<string name="remove_watched_popup_title">Gesehene Videos entfernen\?</string>
|
||||
<string name="show_original_time_ago_title">Originalzeit vor Elementen anzeigen</string>
|
||||
<string name="show_original_time_ago_summary">Originaltexte von Diensten werden in Stream-Elementen sichtbar sein</string>
|
||||
<string name="youtube_restricted_mode_enabled_title">Aktivieren des „Eingeschränkten Modus“ von YouTube</string>
|
||||
<string name="youtube_restricted_mode_enabled_title">YouTubes „Eingeschränkten Modus“ aktivieren</string>
|
||||
<string name="detail_sub_channel_thumbnail_view_description">Profilbild des Kanals</string>
|
||||
<string name="channel_created_by">Erstellt von %s</string>
|
||||
<string name="video_detail_by">Von %s</string>
|
||||
@ -657,7 +657,7 @@
|
||||
<string name="checking_updates_toast">Suche nach Aktualisierungen …</string>
|
||||
<string name="manual_update_description">Manuelle Prüfung auf neue Versionen</string>
|
||||
<string name="feed_new_items">Neue Feed-Elemente</string>
|
||||
<string name="show_crash_the_player_title">\"Player abstürzen lassen\" anzeigen</string>
|
||||
<string name="show_crash_the_player_title">„Player abstürzen lassen“ anzeigen</string>
|
||||
<string name="crash_the_player">Player abstürzen lassen</string>
|
||||
<string name="show_crash_the_player_summary">Zeigt eine Absturzoption an, wenn der Player verwendet wird</string>
|
||||
<string name="error_report_channel_name">Fehlerbericht-Benachrichtigung</string>
|
||||
@ -717,7 +717,7 @@
|
||||
<string name="card">Karte</string>
|
||||
<string name="playlist_add_stream_success_duplicate">Duplikat %d mal hinzugefügt</string>
|
||||
<string name="duplicate_in_playlist">Die ausgegrauten Wiedergabelisten enthalten dieses Element bereits.</string>
|
||||
<string name="ignore_hardware_media_buttons_summary">Nützlich, wenn z. B. ein Headset mit defekten physischen Tasten verwendet wird</string>
|
||||
<string name="ignore_hardware_media_buttons_summary">Nützlich, wenn z. B. ein Headset mit defekten physischen Tasten verwendet wird</string>
|
||||
<string name="ignore_hardware_media_buttons_title">Ereignisse der Hardware-Medientasten ignorieren</string>
|
||||
<string name="remove_duplicates">Duplikate entfernen</string>
|
||||
<string name="remove_duplicates_title">Duplikate entfernen\?</string>
|
||||
@ -824,4 +824,4 @@
|
||||
\nMöchtest du wirklich fortfahren?</string>
|
||||
<string name="import_settings_vulnerable_format">Die Einstellungen in dem zu importierenden Export verwenden ein angreifbares Format, das seit NewPipe 0.27.0 veraltet ist. Stellen Sie sicher, dass der zu importierende Export aus einer vertrauenswürdigen Quelle stammt, und verwenden Sie in Zukunft nur noch Exporte, die aus NewPipe 0.27.0 oder neuer stammen. Die Unterstützung für den Import von Einstellungen in diesem angreifbaren Format wird bald vollständig entfernt werden, und dann werden alte Versionen von NewPipe nicht mehr in der Lage sein, Einstellungen von Exporten aus neuen Versionen zu importieren.</string>
|
||||
<string name="audio_track_type_secondary">Sekundär</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
<string name="download">Λήψη</string>
|
||||
<string name="search">Αναζήτηση</string>
|
||||
<string name="settings">Ρυθμίσεις</string>
|
||||
<string name="did_you_mean">Μήπως εννοείτε «%1$s»;</string>
|
||||
<string name="did_you_mean">Μήπως εννοείτε \"%1$s\";</string>
|
||||
<string name="share_dialog_title">Κοινοποίηση με</string>
|
||||
<string name="use_external_video_player_title">Χρήση εξωτερικής εφαρμογής αναπαραγωγής βίντεο</string>
|
||||
<string name="use_external_audio_player_title">Χρήση εξωτερικής συσκευής αναπαραγωγής ήχου</string>
|
||||
@ -103,7 +103,7 @@
|
||||
<string name="resume_on_audio_focus_gain_title">Ανάκτηση αναπαραγωγής</string>
|
||||
<string name="resume_on_audio_focus_gain_summary">Συνέχιση της αναπαραγωγής έπειτα από διακοπές (π.χ. κλήσεις)</string>
|
||||
<string name="show_hold_to_append_title">Εμφάνιση επεξήγησης του «Πιέστε παρατεταμένα για προσθήκη στην ουρά»</string>
|
||||
<string name="show_hold_to_append_summary">Εμφάνιση υπόδειξης όταν πατηθεί το κουμπί παρασκηνίου ή αναδυόμενου παραθύρου στη σελίδα λεπτομερειών του βίντεο</string>
|
||||
<string name="show_hold_to_append_summary">Εμφάνιση υπόδειξης όταν πατηθεί το κουμπί παρασκηνίου ή αναδυόμενου παραθύρου στις \"Λεπτομέρειες:\\ στο βίντεο</string>
|
||||
<string name="default_content_country_title">Προεπιλεγμένη χώρα περιεχομένου</string>
|
||||
<string name="settings_category_player_title">Αναπαραγωγός</string>
|
||||
<string name="settings_category_player_behavior_title">Συμπεριφορά</string>
|
||||
@ -472,7 +472,7 @@
|
||||
<string name="restricted_video">Αυτό το βίντεο έχει περιορισμό ηλικίας.
|
||||
\n
|
||||
\nΕνεργοποιήστε το «%1$s» στις ρυθμίσεις εάν θέλετε να το δείτε.</string>
|
||||
<string name="youtube_restricted_mode_enabled_title">Λειτουργία περιορισμένης πρόσβασης του YouTube</string>
|
||||
<string name="youtube_restricted_mode_enabled_title">Ενεργοποίηση \"Περιορισμένη Λειτουργία\\ του YouTube</string>
|
||||
<string name="unsupported_url_dialog_message">Δεν ήταν δυνατή η αναγνώριση της διεύθυνσης URL. Άνοιγμα με άλλη εφαρμογή;</string>
|
||||
<string name="auto_queue_toggle">Αυτόματη προσθήκη στην ουρά</string>
|
||||
<string name="clear_queue_confirmation_description">Η ουρά του ενεργού αναπαραγωγού θα αντικατασταθεί</string>
|
||||
@ -654,7 +654,7 @@
|
||||
<string name="manual_update_description">Χειροκίνητος έλεγχος για νέα έκδοση</string>
|
||||
<string name="check_for_updates">Έλεγχος αναβάθμισης</string>
|
||||
<string name="feed_new_items">Νέα αντικείμενα τροφοδοσίας</string>
|
||||
<string name="show_crash_the_player_title">Εμφάνιση «Κατάρρευσης αναπαραγωγέα»</string>
|
||||
<string name="show_crash_the_player_title">Εμφάνιση «Κατάρρευση αναπαραγωγέα\\</string>
|
||||
<string name="show_crash_the_player_summary">Εμφανίζει μια επιλογή κατάρρευσης κατά τη χρήση του αναπαραγωγέα</string>
|
||||
<string name="crash_the_player">Κατάρρευση αναπαραγωγέα</string>
|
||||
<string name="error_report_channel_name">Ειδοποίηση αναφοράς σφάλματος</string>
|
||||
@ -824,4 +824,4 @@
|
||||
\nΕίστε βέβαιοι ότι θέλετε να συνεχίσετε;</string>
|
||||
<string name="import_settings_vulnerable_format">Οι ρυθμίσεις στην εξαγωγή που εισάγεται χρησιμοποιούν μια ευάλωτη μορφή που είχε καταργηθεί από το NewPipe 0.27.0. Βεβαιωθείτε ότι η εξαγωγή που εισάγεται προέρχεται από αξιόπιστη πηγή και προτιμήστε να χρησιμοποιείτε μόνο εξαγωγές που λαμβάνονται από το NewPipe 0.27.0 ή νεότερο στο μέλλον. Η υποστήριξη για εισαγωγή ρυθμίσεων σε αυτήν την ευάλωτη μορφή θα καταργηθεί σύντομα εντελώς και, στη συνέχεια, οι παλιές εκδόσεις του NewPipe δεν θα μπορούν πλέον να εισάγουν ρυθμίσεις εξαγωγών από νέες εκδόσεις.</string>
|
||||
<string name="audio_track_type_secondary">δευτερεύων</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -84,4 +84,4 @@
|
||||
<string name="ok">Okay</string>
|
||||
<string name="open_in_browser">Open in browser</string>
|
||||
<string name="no_player_found_toast">No stream player found (you can install VLC to play it).</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -12,11 +12,11 @@
|
||||
<string name="share_dialog_title">Konigi kun</string>
|
||||
<string name="use_external_video_player_title">Uzi eksteran filmetoludilon</string>
|
||||
<string name="use_external_audio_player_title">Uzi eksteran sonludilon</string>
|
||||
<string name="default_resolution_title">Defaŭlta rezolucio</string>
|
||||
<string name="default_resolution_title">Preferata distingumo</string>
|
||||
<string name="play_with_kodi_title">Ludi per Kodi</string>
|
||||
<string name="show_play_with_kodi_title">Montri \"Ludi per Kodi\"-opcion</string>
|
||||
<string name="play_audio">Sono</string>
|
||||
<string name="default_audio_format_title">Defaŭlta sondosierformo</string>
|
||||
<string name="default_audio_format_title">Preferata sona dosierformo</string>
|
||||
<string name="theme_title">Etoso</string>
|
||||
<string name="dark_theme_title">Malhela</string>
|
||||
<string name="light_theme_title">Hela</string>
|
||||
@ -24,7 +24,7 @@
|
||||
<string name="download_dialog_title">Elŝuti</string>
|
||||
<string name="unsupported_url">Ligilo ne subtenita</string>
|
||||
<string name="content_language_title">Preferata enhavlingvo</string>
|
||||
<string name="settings_category_video_audio_title">Filmeto kaj sono</string>
|
||||
<string name="settings_category_video_audio_title">Filmo kaj sono</string>
|
||||
<string name="settings_category_appearance_title">Apero</string>
|
||||
<string name="background_player_playing_toast">Ludanta fone</string>
|
||||
<string name="general_error">Eraro</string>
|
||||
@ -32,17 +32,17 @@
|
||||
<string name="content_not_available">Enhavo malhavebla</string>
|
||||
<string name="detail_likes_img_view_description">Ŝatoj</string>
|
||||
<string name="detail_dislikes_img_view_description">Malŝatoj</string>
|
||||
<string name="no_player_found">Neniu elsendlflua ludilo trovita. Ĉu instali la aplikaĵon VLC\?</string>
|
||||
<string name="kore_not_found">Ĉu instali la mankan aplikaĵon Kore\?</string>
|
||||
<string name="show_next_and_similar_title">Montri \'Sekvajn\' kaj \'Similajn\' videojn</string>
|
||||
<string name="no_player_found">Neniu ludilo de elsendoj trovita. Ĉu instalu la programon VLC?</string>
|
||||
<string name="kore_not_found">Ĉu instalu la mankatan programon Kore?</string>
|
||||
<string name="show_next_and_similar_title">Montri ‹Sekvajn› kaj ‹Similajn› filmetojn</string>
|
||||
<string name="could_not_load_thumbnails">Ĉiuj bildetoj ne ŝargeblas</string>
|
||||
<string name="parsing_error">La retejo ne analizeblas</string>
|
||||
<string name="detail_thumbnail_view_description">Ludi filmeton, daŭro:</string>
|
||||
<string name="detail_uploader_thumbnail_view_description">Bildeto de la alŝutinto</string>
|
||||
<string name="download_path_title">Elŝutujo por filmetoj</string>
|
||||
<string name="download_path_audio_title">Elŝutujo por aŭdio</string>
|
||||
<string name="show_play_with_kodi_summary">Montri opcion por ludi filmeton per la aplikaĵo Kodi</string>
|
||||
<string name="download_path_summary">Elŝutitaj filmetoj estas konservitaj tie</string>
|
||||
<string name="show_play_with_kodi_summary">Montri opcion por ludi filmeton per la programo Kodi</string>
|
||||
<string name="download_path_summary">Elŝutitaj filmetoj konserviĝas ĉi tie</string>
|
||||
<string name="download_path_audio_summary">Dosierujo por konservi elŝutitajn muzikojn</string>
|
||||
<string name="download_path_dialog_title">Elektu lokon por konservi elŝutitajn filmetojn</string>
|
||||
<string name="download_path_audio_dialog_title">Elektu lokon por konservi elŝutitajn muzikojn</string>
|
||||
@ -57,7 +57,7 @@
|
||||
<string name="main_bg_subtitle">Premi \"Serĉi\" por komenci.</string>
|
||||
<string name="no_player_found_toast">Neniu elsendlflua ludilo trovita (instalu VLC por ludi ĝin).</string>
|
||||
<string name="open_in_popup_mode">Malfermi en ŝprucfenestran modon</string>
|
||||
<string name="use_external_video_player_summary">Forigas aŭdon ĉe kelkaj rezolucioj</string>
|
||||
<string name="use_external_video_player_summary">Forigas aŭdon ĉe kelkaj distingumoj</string>
|
||||
<string name="subscribe_button_title">Aboni</string>
|
||||
<string name="subscribed_button_title">Abonita</string>
|
||||
<string name="channel_unsubscribed">Kanalo malabonita</string>
|
||||
@ -71,10 +71,10 @@
|
||||
<string name="controls_background_title">Fono</string>
|
||||
<string name="controls_popup_title">Ŝprucfenestro</string>
|
||||
<string name="controls_add_to_playlist_title">Aldonu al</string>
|
||||
<string name="default_popup_resolution_title">Defaŭlta rezolucio de la ŝprucfenestra ludilo</string>
|
||||
<string name="show_higher_resolutions_title">Montri pli altajn rezoluciojn</string>
|
||||
<string name="show_higher_resolutions_summary">Nur kelkaj aparatoj povas ludi 2K / 4K filmetojn</string>
|
||||
<string name="default_video_format_title">Defaŭlta filmetdosierformo</string>
|
||||
<string name="default_popup_resolution_title">Komenca distingumo de la ŝprucfenestra ludilo</string>
|
||||
<string name="show_higher_resolutions_title">Montri pli altajn distingumojn</string>
|
||||
<string name="show_higher_resolutions_summary">Nur kelkaj aparatoj povas ludi filmetojn je distingumoj 2K / 4K</string>
|
||||
<string name="default_video_format_title">Preferata filma dosierformo</string>
|
||||
<string name="popup_remember_size_pos_title">Memori ecojn de ŝprucfenestro</string>
|
||||
<string name="popup_remember_size_pos_summary">Memori lastan grandon kaj pozicion de ŝprucfenestro</string>
|
||||
<string name="use_inexact_seek_title">Uzi rapidan malekzaktan serĉon</string>
|
||||
@ -100,7 +100,7 @@
|
||||
<string name="clear">Forviŝi</string>
|
||||
<string name="show_search_suggestions_title">Serĉi sugestojn</string>
|
||||
<string name="show_search_suggestions_summary">Montri sugestojn kiam serĉanto</string>
|
||||
<string name="best_resolution">Plej bona rezolucio</string>
|
||||
<string name="best_resolution">Plej bona distingumo</string>
|
||||
<string name="app_description">Libera malpeza torentado ĉe Android.</string>
|
||||
<string name="settings_category_downloads_title">Elŝuti</string>
|
||||
<string name="charset_letters_and_digits">Leteroj kaj ciferoj</string>
|
||||
@ -110,7 +110,7 @@
|
||||
<string name="enable_search_history_title">Serĉa historio</string>
|
||||
<string name="enable_search_history_summary">Konservi la historio de serĉo lokale</string>
|
||||
<string name="enable_watch_history_title">Rigardu historion</string>
|
||||
<string name="enable_watch_history_summary">Spuri la viditajn filmetojn</string>
|
||||
<string name="enable_watch_history_summary">Protokoli spektitajn filmetojn</string>
|
||||
<string name="notification_channel_name">Sciigo de NewPipe</string>
|
||||
<string name="notification_channel_description">Sciigoj por ludilo de NewPipe</string>
|
||||
<string name="settings_category_player_title">Ludilo</string>
|
||||
@ -121,7 +121,7 @@
|
||||
<string name="top_50">Supro 50</string>
|
||||
<string name="new_and_hot">Nova kaj varma</string>
|
||||
<string name="show_hold_to_append_title">Montri la indiko « Tenu por aldoni »</string>
|
||||
<string name="show_hold_to_append_summary">Montri indikon premante la fona aŭ la ŝprucfenestra butono en filmeta \"Detaloj:\"</string>
|
||||
<string name="show_hold_to_append_summary">Montri indikon premante la fonon aŭ la ŝprucfenestran butonon en filmeta «Detaloj:»</string>
|
||||
<string name="play_all">Ludi ĉiujn</string>
|
||||
<string name="player_stream_failure">Ne povis ludi tion torenton</string>
|
||||
<string name="player_unrecoverable_failure">Neatendebla eraro de ludilo okazis</string>
|
||||
@ -138,7 +138,7 @@
|
||||
<string name="switch_to_main">Ŝangi al Ĉefa</string>
|
||||
<string name="always">Ĉiam</string>
|
||||
<string name="just_once">Nur unufoje</string>
|
||||
<string name="video_streams_empty">Neniuj filmeta torentoj trovitaj</string>
|
||||
<string name="video_streams_empty">Neniu filmofluo trovita</string>
|
||||
<string name="audio_streams_empty">Neniuj sonaj torentoj trovis</string>
|
||||
<string name="popup_player">Ŝprucfenestra ludilo</string>
|
||||
<string name="import_data_title">Importi la datumbazon</string>
|
||||
@ -191,7 +191,7 @@
|
||||
<string name="clear_search_history_summary">Forviŝi la serĉajn ŝlosilvortojn</string>
|
||||
<string name="delete_search_history_alert">Ĉu vi volas forviŝi la totalon de la historio de serĉo \?</string>
|
||||
<string name="search_history_deleted">Historio de serĉo forviŝita</string>
|
||||
<string name="limit_mobile_data_usage_title">Limigi rezolucio kiam uzanta moveblan datumon</string>
|
||||
<string name="limit_mobile_data_usage_title">Limigi distingumon uzante telefonan retkonekton</string>
|
||||
<string name="minimize_on_exit_popup_description">Minimumigi al ŝprucfenestra ludilo</string>
|
||||
<string name="channels">Kanaloj</string>
|
||||
<string name="playlists">Ludlistoj</string>
|
||||
@ -258,8 +258,8 @@
|
||||
<string name="msg_copied">Enpoŝigita</string>
|
||||
<string name="no_available_dir">Bonvolu difini elŝutan dosierujon poste en agordoj</string>
|
||||
<plurals name="views">
|
||||
<item quantity="one">%s spektaĵo</item>
|
||||
<item quantity="other">%s spektaĵoj</item>
|
||||
<item quantity="one">%s spekto</item>
|
||||
<item quantity="other">%s spektoj</item>
|
||||
</plurals>
|
||||
<string name="short_thousand">k</string>
|
||||
<string name="short_million">M</string>
|
||||
@ -286,7 +286,7 @@
|
||||
<item quantity="one">%s abonanto</item>
|
||||
<item quantity="other">%s abonantoj</item>
|
||||
</plurals>
|
||||
<string name="no_views">Neniuj spektaĵoj</string>
|
||||
<string name="no_views">Neniuj spektoj</string>
|
||||
<string name="no_videos">Neniu filmeto</string>
|
||||
<string name="delete_item_search_history">Ĉu vi volas forviŝi tion eron el la historio de serĉo \?</string>
|
||||
<string name="main_page_content">Enhavo de la ĉefpaĝo</string>
|
||||
@ -305,7 +305,7 @@
|
||||
<string name="drawer_open">Malfermi la tirkeston</string>
|
||||
<string name="drawer_close">Fermi la tirtekston</string>
|
||||
<string name="external_player_unsupported_link_type">Ekstaraj ludantoj ne suportas tiajn ligilojn</string>
|
||||
<string name="video_player">Filmetoludilo</string>
|
||||
<string name="video_player">Filmludilo</string>
|
||||
<string name="background_player">Fona ludilo</string>
|
||||
<string name="preferred_player_fetcher_notification_title">Akiranta informoj…</string>
|
||||
<string name="preferred_player_fetcher_notification_message">Ŝarĝante petita enhavo</string>
|
||||
@ -347,7 +347,7 @@
|
||||
<string name="playback_pitch">Ludkampo</string>
|
||||
<string name="unhook_checkbox">Malligi (povas kaŭzi distordon)</string>
|
||||
<string name="preferred_open_action_settings_title">Preferita \'malfermi\' ago</string>
|
||||
<string name="preferred_open_action_settings_summary">Defaŭlta ago malfermante enhavo — %s</string>
|
||||
<string name="preferred_open_action_settings_summary">Implicita ago malfermante vidaŭdaĵon — %s</string>
|
||||
<string name="caption_setting_title">Subtitoloj</string>
|
||||
<string name="caption_setting_description">Modifi la dimension de la teksto kaj la fonajn stilojn de la subtitoloj de la ludilo. Ĝi bezonas restarto de la apo por efektiviĝi.</string>
|
||||
<string name="one_item_deleted">1 ero forviŝita.</string>
|
||||
@ -369,9 +369,9 @@
|
||||
<string name="skip_silence_checkbox">Plirapidigi dum silentoj</string>
|
||||
<string name="playback_step">Paŝo</string>
|
||||
<string name="playback_reset">Restarigi</string>
|
||||
<string name="saved_tabs_invalid_json">Ne povis legi konservitajn ongletoj, tial uzante la defaŭltajn</string>
|
||||
<string name="restore_defaults">Restaŭri la defaŭltojn</string>
|
||||
<string name="restore_defaults_confirmation">Ĉu vi volas restaŭri la defaŭltojn valorojn\?</string>
|
||||
<string name="saved_tabs_invalid_json">Ne povis legi agorditajn langetojn, do uzos la implicitajn</string>
|
||||
<string name="restore_defaults">Restarigi implicitajn valorojn</string>
|
||||
<string name="restore_defaults_confirmation">Ĉu vi volas restarigi la implicitajn valorojn?</string>
|
||||
<string name="subscribers_count_not_available">Abonantoj kalkulo malhaveblas</string>
|
||||
<string name="main_page_content_summary">Kioj ongletoj estas montritaj en la ĉefpaĝo</string>
|
||||
<string name="updates_setting_title">Ĝisdatigoj</string>
|
||||
@ -410,7 +410,7 @@
|
||||
<string name="error_progress_lost">Progreso perdita, ĉar la dosiero estis forviŝita</string>
|
||||
<string name="error_timeout">Eltempiĝo de Konekto</string>
|
||||
<string name="drawer_header_description">Ŝangi la servon, nuntempe elektita:</string>
|
||||
<string name="default_kiosk_page_summary">Defaŭlta Kiosko</string>
|
||||
<string name="default_kiosk_page_summary">Implicita Kiosko</string>
|
||||
<string name="no_one_watching">Neniu spektas</string>
|
||||
<plurals name="watching">
|
||||
<item quantity="one">%s spektanto</item>
|
||||
@ -443,7 +443,7 @@
|
||||
<string name="videos_string">Filmetoj</string>
|
||||
<string name="permission_display_over_apps">Doni la permeson por afiŝiĝi supre aliaj apoj</string>
|
||||
<string name="app_language_title">Preferata aplingvo</string>
|
||||
<string name="systems_language">Sistemnormo</string>
|
||||
<string name="systems_language">Sistema</string>
|
||||
<string name="subtitle_activity_recaptcha">Premu “Finita” kiam solvita</string>
|
||||
<string name="done">Finita</string>
|
||||
<plurals name="seconds">
|
||||
@ -499,9 +499,7 @@
|
||||
<string name="notification_action_repeat">Ripeti</string>
|
||||
<string name="search_showing_result_for">Montrante rezultojn pri: %s</string>
|
||||
<string name="open_with">Malfermi per</string>
|
||||
<string name="restricted_video">Tiu ĉi filmeto havas aĝlimon.
|
||||
\n
|
||||
\nŜalti \"%1$s\" en la agordoj, se vi volas vidi ĝin.</string>
|
||||
<string name="restricted_video">Tiu ĉi filmeto havas aĝminimumon. \n \nŜaltu «%1$s» en la agordoj, se vi volus vidi ĝin.</string>
|
||||
<string name="night_theme_title">Malhela etoso</string>
|
||||
<string name="notification_colorize_title">farbi sciigon</string>
|
||||
<string name="notification_action_buffering">Alŝuto</string>
|
||||
@ -530,11 +528,11 @@
|
||||
<string name="description_tab_description">Priskribo</string>
|
||||
<string name="comments_tab_description">Komentoj</string>
|
||||
<string name="show_description_title">Montri priskribon</string>
|
||||
<string name="notification_scale_to_square_image_summary">Stuci la bildeton de la video en la sciigo de 16:9 ĝis 1:1 propocio</string>
|
||||
<string name="notification_scale_to_square_image_summary">Stuci la antaŭvidan bildeton de la filmo en la sciigo de proporcio 16:9 al 1:1</string>
|
||||
<string name="show_meta_info_title">Montri metadatumojn</string>
|
||||
<string name="mark_as_watched">Marki spektita</string>
|
||||
<string name="show_meta_info_summary">Malŝatu por kaŝi metadatumujojn kio havas aldonajn informojn pri la elsendfluisto, enhavo de la fluo, aŭ serĉpto</string>
|
||||
<string name="show_description_summary">Malŝaltu por kaŝi la videan priskribon kaj aldonan informon</string>
|
||||
<string name="show_description_summary">Malŝaltu por kaŝi priskribojn kaj aldonajn informojn de filmetoj</string>
|
||||
<string name="clear_queue_confirmation_title">Peti por konfirmo antaŭ vakigado atendvico</string>
|
||||
<string name="clear_queue_confirmation_summary">Via atendvico povas anstataŭigi se vi ŝanĝi al malsama ludilo</string>
|
||||
<string name="clear_queue_confirmation_description">La aktiva ludila atendvico anstataŭigos</string>
|
||||
@ -549,7 +547,7 @@
|
||||
<string name="volume">Laŭteco</string>
|
||||
<string name="none">Neniu</string>
|
||||
<string name="notification_colorize_summary">Permesi al Android agordi koloron de sciigo laŭ la precipa koloro de videaĵminiaturo (noti, ke ĉi tio ne disponeblas en ĉiuj iloj)</string>
|
||||
<string name="auto_queue_toggle">Aŭtomata vicigado</string>
|
||||
<string name="auto_queue_toggle">Memaga vicigado</string>
|
||||
<string name="right_gesture_control_title">Ago de dekstra gesto</string>
|
||||
<string name="notification_actions_summary">Redakti ĉiun agon de sciigo per tuŝi gin. Elekti maksimume tri agon por montri en la kompakta sciigo per markobutonoj dekstre.</string>
|
||||
<string name="prefer_original_audio_summary">Elekti la originalan aŭdiotrakon malgraŭ lingvo</string>
|
||||
@ -574,20 +572,19 @@
|
||||
<string name="remote_search_suggestions">Foraj serĉsugestoj</string>
|
||||
<string name="error_report_channel_description">Sciigoj por raporti erarojn</string>
|
||||
<string name="loading_metadata_title">Ŝargante metadatumoj…</string>
|
||||
<string name="hash_channel_description">Sciigo por kreado de haketaĵoj de videoj</string>
|
||||
<string name="hash_channel_description">Sciigo por kreado de haketaĵoj de filmetoj</string>
|
||||
<string name="youtube_restricted_mode_enabled_summary">YouTube provizas \"Limigitan Reĝimon\", kiu kaŝas enhavon, kiu potence maltaŭgas por infanoj</string>
|
||||
<string name="restricted_video_no_stream">Ĉi tiu video estas aĝo-limigita.
|
||||
\nPro novaj reguloj de YouTube, kiuj aplikas al aĝo-limigitaj videoj, NewPipe ne povas atingi iun ajn video-fluoj de ĉi tiu video kaj konsekvence ne povas ludi ĝin.</string>
|
||||
<string name="restricted_video_no_stream">Ĉi tiu filmeto havas aĝminimumon. \nPro novaj reguloj de YouTube, kiuj rilatas filmojn kun minimuma aĝo, NewPipe ne povas atingi ajnan fluon de ĉi tiu filmo kaj tial ne povas ludi ĝin.</string>
|
||||
<string name="error_report_channel_name">Sciigo por erar-raportoj</string>
|
||||
<string name="notifications">Sciigoj</string>
|
||||
<string name="settings_category_player_notification_title">Ludila sciigo</string>
|
||||
<string name="hash_channel_name">Sciigo por haketado de videoj</string>
|
||||
<string name="hash_channel_name">Sciigo por haketado de filmetoj</string>
|
||||
<string name="local_search_suggestions">Lokaj serĉsugestoj</string>
|
||||
<string name="start_main_player_fullscreen_title">Ŝalti ĉefan ludilon plenekrane</string>
|
||||
<string name="streams_notification_channel_description">Sciigo por novaj fluoj de abonoj</string>
|
||||
<string name="clear_cookie_title">Forigi kuketojn de reCAPTCHA</string>
|
||||
<string name="settings_category_player_notification_summary">Agordi la sciigon por ĉi-momente ludantaj datumtorentoj</string>
|
||||
<string name="start_main_player_fullscreen_summary">Ne komenci ludi videojn en la mini-ludilo, sed ŝalti plenekranan reĝimon rekte, se aŭtomata rotacio ŝlositas. Vi ankoraŭ povus atingi mini-ludilon, se vi elirus plenekranan reĝimon.</string>
|
||||
<string name="start_main_player_fullscreen_summary">Ne ekludu filmojn en la etludilo, sed ŝalti plenekranan reĝimon rekte, se memaga turniĝo estas ŝaltita. Vi ankoraŭ povus aliri la etludilon elirinte la plenekranan reĝimon.</string>
|
||||
<string name="clear_cookie_summary">Forigi kuketojn, kiujn NewPipe konservas, kiam vi solvas reCAPTCHA-taskojn</string>
|
||||
<string name="error_report_notification_title">NewPipe renkontis eraron, tuŝi por raporti</string>
|
||||
<string name="main_tabs_position_title">Pozicio de la ĉefaj langetoj</string>
|
||||
@ -605,7 +602,7 @@
|
||||
<string name="import_subscriptions_hint">Importi aŭ eksporti abonojn per la tri-punkta menuo</string>
|
||||
<string name="msg_calculating_hash">Kalkulado de haketaĵo</string>
|
||||
<string name="faq_title">Oftaj demandoj</string>
|
||||
<string name="no_dir_yet">Neniu dosierujo por elŝutoj agordita, bonvolu elekti la defaŭltan elŝuto-dosierujon nun</string>
|
||||
<string name="no_dir_yet">Neniu dosierujo por elŝutoj agordita, bonvolu elekti la preferatan elŝuto-dosierujon nun</string>
|
||||
<string name="error_report_open_github_notice">Bonvolu certigi, ĉu erarraporto, kiu diskutas pri via eraro, jam ekzistas. Kreado de duoblaĵaj erarraportoj forprenas tempon el ni, kiun ni povus uzi por ripari la veran eraron.</string>
|
||||
<string name="related_items_tab_description">Rilatajn erojn</string>
|
||||
<string name="recaptcha_solve">Solvi</string>
|
||||
@ -613,4 +610,13 @@
|
||||
<string name="downloads_storage_ask_summary_no_saf_notice">Oni petos al vi kien salvi ĉiujn elŝutojn</string>
|
||||
<string name="yes">Jes</string>
|
||||
<string name="no">Ne</string>
|
||||
</resources>
|
||||
<string name="remove_watched_popup_title">Ĉu forigu spektitajn filmetojn?</string>
|
||||
<string name="youtube_music_premium_content">Ĉu tiu filmeto sole haveblas al abonantoj de YouTube Music Premium, do ĝi ne spekteblas nek elŝuteblas de NewPipe.</string>
|
||||
<string name="audio_track_present_in_video">Sono devus jam esti en ĉi tiu fluo</string>
|
||||
<string name="no_video_streams_available_for_external_players">Neniu filmofluo ludeblas por ekstera ludilo</string>
|
||||
<string name="channel_tab_videos">Filmetoj</string>
|
||||
<string name="remove_watched_popup_warning">Filmetoj kiuj spektiĝis antaŭ aŭ post sia aldoniĝo al la ludlisto foriĝus.. \nĈu vi certas? Ĉi tio nemalfareblus!</string>
|
||||
<string name="share_playlist_with_titles_message">Kunhavigus ludliston inkluzivante informojn kiel la nomoj de listeroj, aŭ kiel simpla listo de ligiloj</string>
|
||||
<string name="reset_settings_summary">Restarigi implicitajn agordojn</string>
|
||||
<string name="remove_watched_popup_yes_and_partially_watched_videos">Jes, kaj ankaŭ parte spektitajn filmetojn</string>
|
||||
</resources>
|
||||
|
||||
@ -47,7 +47,7 @@
|
||||
<string name="detail_uploader_thumbnail_view_description">Miniatura del avatar del usuario</string>
|
||||
<string name="content">Contenido</string>
|
||||
<string name="show_age_restricted_content_title">Mostrar contenido con restricción de edad</string>
|
||||
<string name="main_bg_subtitle">Toca la lupa para empezar.</string>
|
||||
<string name="main_bg_subtitle">Toca la lupa para comenzar..</string>
|
||||
<string name="duration_live">En directo</string>
|
||||
<string name="downloads">Descargas</string>
|
||||
<string name="downloads_title">Descargas</string>
|
||||
@ -839,4 +839,4 @@
|
||||
\n¿Quieres habilitar esto?</string>
|
||||
<string name="import_settings_vulnerable_format">La configuración de la exportación que se importa utiliza un formato vulnerable que quedó obsoleto desde NewPipe 0.27.0. Asegúrese de que la exportación que se está importando provenga de una fuente confiable y prefiera usar solo exportaciones obtenidas de NewPipe 0.27.0 o posterior en el futuro. La compatibilidad con la importación de configuraciones en este formato vulnerable pronto se eliminará por completo y, luego, las versiones antiguas de NewPipe ya no podrán importar configuraciones de exportaciones desde las nuevas versiones.</string>
|
||||
<string name="audio_track_type_secondary">secundaria</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -89,7 +89,7 @@
|
||||
<string name="duration_live">Otse</string>
|
||||
<string name="downloads">Allalaadimised</string>
|
||||
<string name="downloads_title">Allalaadimised</string>
|
||||
<string name="error_report_title">Vea teatamine</string>
|
||||
<string name="error_report_title">Veateade</string>
|
||||
<string name="all">Kõik</string>
|
||||
<string name="disabled">Keelatud</string>
|
||||
<string name="clear">Kustuta</string>
|
||||
@ -180,7 +180,7 @@
|
||||
<string name="dismiss">Loobu</string>
|
||||
<string name="rename">Muuda nime</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="msg_name">Faili nimi</string>
|
||||
<string name="msg_name">Failinimi</string>
|
||||
<string name="msg_threads">Lõimed</string>
|
||||
<string name="msg_error">Viga</string>
|
||||
<string name="msg_running">NewPipe allalaadimine</string>
|
||||
@ -369,7 +369,7 @@
|
||||
<string name="error_file_creation">Faili ei saa luua</string>
|
||||
<string name="error_ssl_exception">Turvalist ühendust ei suudetud luua</string>
|
||||
<string name="error_unknown_host">Serverit ei leitud</string>
|
||||
<string name="error_connect_host">Serveriga ei saadud ühendust</string>
|
||||
<string name="error_connect_host">Serveriga ei saa ühendust</string>
|
||||
<string name="error_http_no_content">Server ei saada andmeid</string>
|
||||
<string name="error_http_unsupported_range">Server ei toeta mitmelõimelisi allalaadimisi. Proovi uuesti kasutades @string/msg_threads = 1</string>
|
||||
<string name="error_http_not_found">Ei leitud</string>
|
||||
@ -453,7 +453,7 @@
|
||||
<item quantity="other">%s kuulajat</item>
|
||||
</plurals>
|
||||
<string name="hash_channel_description">Teavitused video räsimise edenemise kohta</string>
|
||||
<string name="youtube_restricted_mode_enabled_title">Võta kasutusele YouTube\'i „Piiratud režiim“</string>
|
||||
<string name="youtube_restricted_mode_enabled_title">Võta kasutusele YouTube\'i „Piiratud režiim“\\</string>
|
||||
<string name="missing_file">Faili asukoht on muutunud või on ta kustutatud</string>
|
||||
<string name="watch_history_states_deleted">Taasesituste asukohad on kustutatud</string>
|
||||
<string name="delete_playback_states_alert">Kas kustutame kõik taasesituste asukohad\?</string>
|
||||
@ -654,7 +654,7 @@
|
||||
<string name="check_for_updates">Kontrolli uuendusi</string>
|
||||
<string name="manual_update_description">Kontrolli uuendusi käsitsi</string>
|
||||
<string name="feed_new_items">Uued andmevoo kirjed</string>
|
||||
<string name="show_crash_the_player_title">Näita „Jooksuta meediamängija kokku“ nupukest</string>
|
||||
<string name="show_crash_the_player_title">Näita „Jooksuta meediamängija kokku“ nupukest\\</string>
|
||||
<string name="show_crash_the_player_summary">Näitab valikut meediamängija kokkujooksutamiseks</string>
|
||||
<string name="error_report_notification_title">NewPipe töös tekkis viga, sellest teavitamiseks toksa</string>
|
||||
<string name="crash_the_player">Jooksuta meediamängija kokku</string>
|
||||
@ -824,4 +824,4 @@
|
||||
<string name="no">Ei</string>
|
||||
<string name="import_settings_vulnerable_format">Imporditavad andmed kasutavad turvaprobleemidega vormingut, mida alates versioonist 0.27.0 NewPipe enam luua ei suuda. Palun kontrolli, et impordifail on loodud usaldusväärse osapoole poolt ning edaspidi loo ekspordifailid NewPipe versiooniga 0.27.0 või uuemaga. Tugi sellise vana vormingu kasutamisele kaob õige pea ja seejärel NewPipe uuemad ja vanemad versioonid ei saa omavahel andmeid enam vahetada.</string>
|
||||
<string name="audio_track_type_secondary">täiendav</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -824,4 +824,4 @@
|
||||
</plurals>
|
||||
<string name="show_less">Erakutsi gutxiago</string>
|
||||
<string name="audio_track_type_secondary">bigarren mailako</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -762,4 +762,4 @@
|
||||
<string name="duration">مدّت</string>
|
||||
<string name="rewind">پسروی</string>
|
||||
<string name="question_mark">؟</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -808,4 +808,4 @@
|
||||
<string name="auto_update_check_description">NewPipe voi automaattisesti tarkistaa päivitysten saatavuuden silloin tällöin ja ilmoittaa kun niitä on saatavilla.
|
||||
\nHaluatko ottaa tämän käyttöön?</string>
|
||||
<string name="error_insufficient_storage">Laitteella ei ole riittävästi vapaata tilaa</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -262,4 +262,4 @@
|
||||
<string name="no_one_watching">Walang nanonood</string>
|
||||
<string name="empty_list_subtitle">Wala dito</string>
|
||||
<string name="invalid_directory">Walang folder na ganoon</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -197,7 +197,7 @@
|
||||
<string name="switch_to_main">Basculer en principal</string>
|
||||
<string name="drawer_open">Ouvrir le menu</string>
|
||||
<string name="drawer_close">Fermer le menu</string>
|
||||
<string name="no_player_found_toast">Aucun lecteur multimédia trouvé (vous pouvez installer VLC pour continuer).</string>
|
||||
<string name="no_player_found_toast">Aucun lecteur de flux trouvé (vous pouvez installer VLC pour le lire).</string>
|
||||
<string name="always">Toujours</string>
|
||||
<string name="just_once">Une seule fois</string>
|
||||
<string name="external_player_unsupported_link_type">Les lecteurs externes ne prennent pas en charge ces types de liens</string>
|
||||
@ -825,7 +825,7 @@
|
||||
<item quantity="other">%s réponses</item>
|
||||
</plurals>
|
||||
<string name="notification_actions_summary_android13">Modifiez chaque action de notification ci-dessous en appuyant dessus. Les trois premières actions (lire/pause, précédent, suivant) sont définies par le système et ne peuvent pas être personnalisées.</string>
|
||||
<string name="show_more">Afficher plus</string>
|
||||
<string name="show_more">Voir plus</string>
|
||||
<string name="show_less">Afficher moins</string>
|
||||
<string name="reset_settings_summary">Réinitialiser tous les paramètres à leurs valeurs par défaut</string>
|
||||
<string name="no">Non</string>
|
||||
@ -839,4 +839,5 @@
|
||||
<string name="reset_settings_title">Réinitialiser les paramètres</string>
|
||||
<string name="error_insufficient_storage">Pas assez d\'espace disponible sur l\'appareil</string>
|
||||
<string name="import_settings_vulnerable_format">Les paramètres de l\'export en cours d\'importation utilisent un format vulnérable qui a été déprécié depuis NewPipe 0.27.0. Assurez-vous que l\'export en cours d\'importation provient d\'une source fiable. Privilégiez les exports obtenues à partir de NewPipe 0.27.0 ou des versions plus récentes à l\'avenir. Le support pour l\'importation des paramètres dans ce format vulnérable sera bientôt complètement supprimé et les anciennes versions de NewPipe ne pourront plus importer les paramètres des exports des nouvelles versions.</string>
|
||||
<string name="audio_track_type_secondary">secondaire</string>
|
||||
</resources>
|
||||
@ -819,4 +819,4 @@
|
||||
<string name="channel_tab_tracks">Pistas</string>
|
||||
<string name="feed_fetch_channel_tabs_summary">Lapelas a recuperar ao actualizar o feed. Esta opción non ten efecto se a canle se actualiza no modo rápido.</string>
|
||||
<string name="always_use_exoplayer_set_output_surface_workaround_summary">Esta solución alternativa libera os códecs de video e os re-instancia cando muda a máscara, no canto de configurar a máscara directamente no códec. ExoPlayer xa emprega esta configuración nalgúns dispositivos con este problema e só afecta a Android 6 e versións posteriores.\n\nActivar esta opción pode minimizar erros de reprodución ao mudar o reprodutor de video actual ou mudar ao modo de pantalla completa</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<string name="black_theme_title">કાળું</string>
|
||||
<string name="dark_theme_title">અંધારિયું</string>
|
||||
<string name="light_theme_title">પ્રકાશ</string>
|
||||
<string name="night_theme_title">નાઇટ થીમ</string>
|
||||
<string name="night_theme_title">રાત્રિ થીમ</string>
|
||||
<string name="theme_title">થીમ</string>
|
||||
<string name="default_video_format_title">ડિફોલ્ટ વિડિઓ ફોર્મેટ</string>
|
||||
<string name="default_audio_format_title">ડિફોલ્ટ ઓડિયો ફોર્મેટ</string>
|
||||
@ -15,16 +15,16 @@
|
||||
<string name="notification_action_shuffle">શફલ</string>
|
||||
<string name="notification_action_repeat">પુનરાવર્તન કરો</string>
|
||||
<string name="notification_actions_at_most_three">તમે કોમ્પેક્ટ સૂચનામાં બતાવવા માટે ઓછામાં ઓછી ત્રણ ક્રિયાઓ પસંદ કરી શકો છો!</string>
|
||||
<string name="notification_actions_summary">નીચેની દરેક સૂચના ક્રિયાને તેના પર ટેપ કરીને સંપાદિત કરો. જમણી બાજુના ચેકબોક્સેસનો ઉપયોગ કરીને કોમ્પેક્ટ સૂચનામાં બતાવવા માટે તેમાંથી ત્રણ સુધી પસંદ કરો</string>
|
||||
<string name="notification_actions_summary">નીચેની દરેક સૂચના ક્રિયાને તેના પર ટેપ કરીને સંપાદિત કરો. જમણી બાજુના ચેકબોક્સેસનો ઉપયોગ કરીને કોમ્પેક્ટ સૂચનામાં બતાવવા માટે તેમાંથી ત્રણ સુધી પસંદ કરો.</string>
|
||||
<string name="notification_action_4_title">પાંચમો ક્રિયા બટન</string>
|
||||
<string name="notification_action_3_title">ચોથું ક્રિયા બટન</string>
|
||||
<string name="notification_action_2_title">ત્રીજી ક્રિયા બટન</string>
|
||||
<string name="notification_action_1_title">બીજું ક્રિયા બટન</string>
|
||||
<string name="notification_action_0_title">પ્રથમ ક્રિયા બટન</string>
|
||||
<string name="notification_scale_to_square_image_summary">સૂચનામાં 16: 9 થી 1: 1 અસ્પેક્ટ રેશિયોમાં બતાવેલ વિડિઓ થંબનેલને સ્કેલ કરો (વિકૃતિ રજૂ કરી શકે છે)</string>
|
||||
<string name="notification_scale_to_square_image_title">સ્કેલ થંબનેલથી 1: 1 પાસા રેશિયો</string>
|
||||
<string name="notification_scale_to_square_image_summary">સૂચનામાં બતાવેલ વિડિઓ થંબનેલને ૧૬:૯ થી ૧:૧ સાપેક્ષ ગુણોત્તરમાં કાપો</string>
|
||||
<string name="notification_scale_to_square_image_title">થંબનેલને ૧:૧ સાપેક્ષ ગુણોત્તરમાં કાપો</string>
|
||||
<string name="show_play_with_kodi_summary">કોડી મીડિયા સેન્ટર દ્વારા વિડિઓ ચલાવવાનો વિકલ્પ દર્શાવો</string>
|
||||
<string name="kore_not_found">અનુપસ્થિત Kode એપ્લિકેશન ઇન્સ્ટોલ કરીએ\?</string>
|
||||
<string name="kore_not_found">અનુપસ્થિત Kore અનુપ્રયોગ સ્થાપિત કરીએ?</string>
|
||||
<string name="show_higher_resolutions_summary">ફક્ત થોડા ઉપકરણો 2K / 4K વિડિઓઝ ચલાવી શકે છે</string>
|
||||
<string name="show_higher_resolutions_title">ઉચ્ચ રીઝોલ્યુશન બતાવો</string>
|
||||
<string name="default_popup_resolution_title">ડિફોલ્ટ પોપઅપ રીઝોલ્યુશન</string>
|
||||
@ -68,4 +68,17 @@
|
||||
<string name="no_player_found">કોઈ સ્ટ્રીમ પ્લેયર મળ્યો નથી. વીએલસી સ્થાપિત કરીએ\?</string>
|
||||
<string name="upload_date_text">%1$s પર પ્રકાશિત</string>
|
||||
<string name="main_bg_subtitle">પ્રારંભ કરવા માટે વિપુલ - દર્શક કાચને ટેપ કરો.</string>
|
||||
<string name="mark_as_watched">જોયેલું તરીકે ચિહ્નિત કરો</string>
|
||||
<string name="ok">ઠીક છે</string>
|
||||
<string name="yes">હા</string>
|
||||
<string name="no">ના</string>
|
||||
<string name="trending">વલણમાં છે</string>
|
||||
<string name="auto_queue_toggle">આપોઆપ કતારબદ્ધતા</string>
|
||||
<string name="crash_the_player">પ્લેયરને ક્રેશ કરો</string>
|
||||
<string name="action_history">ઇતિહાસ</string>
|
||||
<string name="play_with_kodi_title">કોટિથી ચલાવો</string>
|
||||
<string name="show_play_with_kodi_title">કોટિથી ચલાવવાનો વિકલ્પ દેખાટો</string>
|
||||
<string name="download_dialog_title">ડાઉનલોડ કરો</string>
|
||||
<string name="autoplay_title">આપમેળે ચલાવો</string>
|
||||
<string name="fragment_feed_title">નવું શું છે</string>
|
||||
</resources>
|
||||
@ -850,4 +850,4 @@
|
||||
\nלהמשיך?</string>
|
||||
<string name="error_insufficient_storage">אין מספיק מקום פנוי במכשיר</string>
|
||||
<string name="import_settings_vulnerable_format">ההגדרות בייצוא המיובא משתמשות בתסדיר פגיע שהוצא משימוש מאז NewPipe 0.27.0. יש לוודא שהייצוא המיובא הוא ממקור מהימן, ועדיף להשתמש רק בייצוא שהושג מ־NewPipe 0.27.0 ומעלה בעתיד. תמיכה בייבוא הגדרות בתסדיר פגיע זה תוסר בקרוב לחלוטין, ואז גרסאות ישנות של NewPipe לא יוכלו לייבא עוד הגדרות של ייצוא מגרסאות חדשות.</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -824,4 +824,4 @@
|
||||
<string name="settings_category_backup_restore_title">बैकअप और रिस्टोर</string>
|
||||
<string name="import_settings_vulnerable_format">आयात किए जा रहे निर्यात में सेटिंग्स एक कमजोर प्रारूप का उपयोग करती हैं जिसे न्यूपाइप 0.27.0 के बाद से हटा दिया गया था। सुनिश्चित करें कि आयात किया जा रहा निर्यात किसी विश्वसनीय स्रोत से है, और भविष्य में केवल न्यूपाइप 0.27.0 या नए से प्राप्त निर्यात का उपयोग करना पसंद करें। इस असुरक्षित प्रारूप में सेटिंग्स आयात करने के लिए समर्थन जल्द ही पूरी तरह से हटा दिया जाएगा, और फिर न्यूपाइप के पुराने संस्करण अब नए संस्करणों से निर्यात की सेटिंग्स आयात नहीं कर पाएंगे।</string>
|
||||
<string name="audio_track_type_secondary">सेकेंडरी</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -831,4 +831,4 @@
|
||||
<string name="reset_all_settings">Obnavljanje svih postavki odbacit će sve tvoje postavljene postavke i aplikacija će se ponovo pokrenuti.
|
||||
\n
|
||||
\nStvarno želiš nastaviti?</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -324,7 +324,7 @@
|
||||
<string name="app_update_notification_channel_name">Alkalmazásfrissítés értesítése</string>
|
||||
<string name="file_deleted">Fájl törölve</string>
|
||||
<string name="settings_category_updates_title">Frissítések</string>
|
||||
<string name="show_hold_to_append_summary">Tipp megjelenítése, ha megnyomja a hátteret vagy a felugró gombot a videó részleteinél</string>
|
||||
<string name="show_hold_to_append_summary">Tipp megjelenítése a háttér vagy a felugró gomb megnyomásakor a videó „Részletek:\\” lehetőségnél</string>
|
||||
<string name="autoplay_title">Automatikus lejátszás</string>
|
||||
<string name="settings_category_clear_data_title">Adatok törlése</string>
|
||||
<string name="enable_playback_state_lists_summary">Lejátszási pozíciók megjelenítése a listákban</string>
|
||||
@ -534,7 +534,7 @@
|
||||
<string name="hash_channel_description">Értesítések a videók ujjlenyomatkészítési folyamatához</string>
|
||||
<string name="hash_channel_name">Videó ujjlenyomat-készítési értesítése</string>
|
||||
<string name="youtube_restricted_mode_enabled_summary">A YouTube biztosít egy „Korlátozott módot”, amely elrejti a lehetséges felnőtteknek szóló tartalmat</string>
|
||||
<string name="youtube_restricted_mode_enabled_title">A YouTube „Korlátozott módjának” bekapcsolása</string>
|
||||
<string name="youtube_restricted_mode_enabled_title">A YouTube „Korlátozott mód\\” bekapcsolása</string>
|
||||
<string name="peertube_instance_add_exists">A példány már létezik</string>
|
||||
<string name="peertube_instance_add_fail">A példány érvényesítése nem sikerült</string>
|
||||
<string name="peertube_instance_add_help">Adja meg a példány webcímét</string>
|
||||
@ -656,7 +656,8 @@
|
||||
\nBiztos benne\? Ez nem vonható vissza!</string>
|
||||
<string name="show_original_time_ago_summary">A szolgáltatásokból származó eredeti szövegek láthatók lesznek a közvetítési elemeken</string>
|
||||
<string name="crash_the_player">Lejátszó összeomlasztása</string>
|
||||
<string name="show_crash_the_player_title">A „lejátszó összeomlasztása” lehetőség megjelenítése</string>
|
||||
<string name="show_image_indicators_title">Képjelölők megjelenítése</string>
|
||||
<string name="show_crash_the_player_title">A „lejátszó összeomlasztása\\” lehetőség megjelenítése</string>
|
||||
<string name="show_crash_the_player_summary">Megjeleníti az összeomlasztási lehetőséget a lejátszó használatakor</string>
|
||||
<string name="unhook_checkbox">Hangmagasság megtartása (torzítást okozhat)</string>
|
||||
<string name="check_for_updates">Frissítések keresése</string>
|
||||
@ -824,4 +825,4 @@
|
||||
\nBiztosan folytatja?</string>
|
||||
<string name="import_settings_vulnerable_format">Az importálandó exportban lévő beállítások sérülékeny formátumot használnak, amely a NewPipe 0.27.0-s verziója óta elavult. Győződjön meg arról, hogy megbízható forrásból importálja, és a jövőben csak a NewPipe 0.27.0-s vagy újabb verziójából származó exportokat használjon. A beállítások ebből a sérülékeny forrásból történő importálása hamarosan végleg el lesz távolítva, és a NewPipe régi verziói nem fogják tudni importálni az újabb verziókból származó exportokat.</string>
|
||||
<string name="audio_track_type_secondary">másodlagos</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -252,4 +252,4 @@
|
||||
<string name="settings_category_player_notification_summary">Configurar le notification del fluxo in reproduction</string>
|
||||
<string name="settings_category_player_notification_title">Notification de reproductor</string>
|
||||
<string name="settings_category_backup_restore_title">Facer un copia de securitate e restaurar</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -138,7 +138,7 @@
|
||||
<string name="auto_queue_summary">Melanjutkan akhir dari antrean pemutaran (tak berulang) dengan menambahkan video terkait</string>
|
||||
<string name="enable_watch_history_summary">Simpan daftar video yang telah ditonton</string>
|
||||
<string name="show_hold_to_append_title">Tip \"Tahan untuk menambahkan\"</string>
|
||||
<string name="show_hold_to_append_summary">Tampilkan tip ketika menekan tombol latar belakang atau popup di dalam \"Detail:\" video</string>
|
||||
<string name="show_hold_to_append_summary">Tampilkan tip ketika menekan tombol latar belakang atau popup di dalam video \"Detail:\\</string>
|
||||
<string name="default_content_country_title">Lokasi Konten</string>
|
||||
<string name="settings_category_player_title">Pemutar</string>
|
||||
<string name="settings_category_player_behavior_title">Perilaku</string>
|
||||
@ -508,7 +508,7 @@
|
||||
\nJadi pilihlah yang sesuai yang Anda inginkan: kecepatan atau kelengkapan informasi.</string>
|
||||
<string name="show_original_time_ago_summary">Teks asli dari layanan akan ditampilkan di dalam video</string>
|
||||
<string name="show_original_time_ago_title">Tampilkan waktu yang lalu sebenarnya pada item</string>
|
||||
<string name="youtube_restricted_mode_enabled_title">Aktifkan \"Mode Terbatas\" YouTube</string>
|
||||
<string name="youtube_restricted_mode_enabled_title">Aktifkan \"Mode Terbatas\\</string>
|
||||
<string name="video_detail_by">Oleh %s</string>
|
||||
<string name="channel_created_by">Dibuat oleh %s</string>
|
||||
<string name="detail_sub_channel_thumbnail_view_description">Thumbnail avatar channel</string>
|
||||
@ -810,4 +810,4 @@
|
||||
<string name="settings_category_backup_restore_title">Cadangkan dan pulihkan</string>
|
||||
<string name="import_settings_vulnerable_format">Pengaturan dalam ekspor yang diimpor menggunakan format rentan yang tidak digunakan lagi sejak NewPipe 0.27.0. Pastikan ekspor yang diimpor berasal dari sumber tepercaya, dan lebih memilih hanya menggunakan ekspor yang diperoleh dari NewPipe 0.27.0 atau yang lebih baru di masa mendatang. Dukungan untuk mengimpor pengaturan dalam format rentan ini akan segera dihapus sepenuhnya, dan NewPipe versi lama tidak akan dapat lagi mengimpor pengaturan ekspor dari versi baru.</string>
|
||||
<string name="audio_track_type_secondary">sekunder</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -607,7 +607,7 @@
|
||||
<string name="enable_playback_state_lists_title">Spilunarstöður í listum</string>
|
||||
<string name="enable_playback_state_lists_summary">Sýna spilunarstöður í listum</string>
|
||||
<string name="show_hold_to_append_title">Sýna ábendinguna „Haltu niðri til að bæta við spilunarröð“</string>
|
||||
<string name="show_hold_to_append_summary">Sýna ábendingu þegar ýtt er á bakgrunninn eða sprettihnappinn í „Nánar:“ á myndskeiðinu</string>
|
||||
<string name="show_hold_to_append_summary">Sýna ábendingu þegar ýtt er á bakgrunninn eða sprettihnappinn á myndskeiðinu í „Nánar:\\</string>
|
||||
<string name="unsupported_url_dialog_message">Óþekkt slóð. Opna með öðru forriti\?</string>
|
||||
<string name="peertube_instance_url_summary">Veldu uppáhalds PeerTube tilvik þín</string>
|
||||
<string name="peertube_instance_url_help">Þú mátt finna tilviki á %s</string>
|
||||
@ -645,7 +645,7 @@
|
||||
<string name="show_original_time_ago_summary">Upprunalegir textar frá þjónustu verða sýnilegir í streymisatriðum</string>
|
||||
<string name="disable_media_tunneling_title">Slökkva á margmiðlagöngum</string>
|
||||
<string name="disable_media_tunneling_summary">Slökktu á margmiðlunargöngum (media tunneling) ef vart verður við svartan skjá eða hökt við spilun myndskeiða.</string>
|
||||
<string name="show_crash_the_player_title">Sýna „Hrynja spilara“</string>
|
||||
<string name="show_crash_the_player_title">Sýna „Láta spilara hrynja\\</string>
|
||||
<string name="show_crash_the_player_summary">Sýna valkost til að hrynja spilara</string>
|
||||
<string name="crash_the_app">Hrynja forrit</string>
|
||||
<string name="create_error_notification">Búа til villutilkynningu</string>
|
||||
@ -728,7 +728,7 @@
|
||||
<string name="feed_show_upcoming">Á næstunni</string>
|
||||
<string name="channel_tab_videos">Myndskeið</string>
|
||||
<string name="channel_tab_tracks">Spor</string>
|
||||
<string name="channel_tab_shorts">Stuttmyndir</string>
|
||||
<string name="channel_tab_shorts">Símamyndir</string>
|
||||
<string name="channel_tab_playlists">Spilunarlistar</string>
|
||||
<string name="channel_tab_albums">Albúm</string>
|
||||
<string name="channel_tab_about">Um hugbúnaðinn</string>
|
||||
@ -803,4 +803,4 @@
|
||||
<string name="show_error_snackbar">Sýna villustiku</string>
|
||||
<string name="image_quality_summary">Veldu gæði mynda og hvort eigi að hlaða myndum inn yfirhöfuð, til að minnka notun gagna og minnis. Breytingar munu hreinsa bæði vinnsluminni og diskminni - %s</string>
|
||||
<string name="audio_track_type_secondary">auka</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -96,8 +96,8 @@
|
||||
<string name="show_higher_resolutions_title">Mostra risoluzioni più elevate</string>
|
||||
<string name="show_higher_resolutions_summary">Solo alcuni dispositivi possono riprodurre video 2K/4K</string>
|
||||
<string name="default_video_format_title">Formato video predefinito</string>
|
||||
<string name="popup_remember_size_pos_title">Ricorda proprietà lettore popup</string>
|
||||
<string name="popup_remember_size_pos_summary">Ricorda dimensione e posizione del lettore popup</string>
|
||||
<string name="popup_remember_size_pos_title">Ricorda proprietà del popup</string>
|
||||
<string name="popup_remember_size_pos_summary">Ricorda ultima dimensione e posizione del popup</string>
|
||||
<string name="show_search_suggestions_title">Suggerimenti di ricerca</string>
|
||||
<string name="show_search_suggestions_summary">Scegli suggerimenti di ricerca</string>
|
||||
<string name="clear">Cancella</string>
|
||||
@ -139,7 +139,7 @@
|
||||
<string name="settings_category_player_title">Lettore multimediale</string>
|
||||
<string name="settings_category_player_behavior_title">Comportamento</string>
|
||||
<string name="settings_category_history_title">Cronologia e cache</string>
|
||||
<string name="undo">Annulla</string>
|
||||
<string name="undo">Rifai</string>
|
||||
<string name="notification_channel_name">Notifica NewPipe</string>
|
||||
<string name="notification_channel_description">Notifiche per il lettore multimediale di NewPipe</string>
|
||||
<string name="search_no_results">Nessun risultato</string>
|
||||
@ -837,4 +837,5 @@
|
||||
<string name="auto_update_check_description">NewPipe può cercare automaticamente nuove versioni di tanto in tanto e avvisarti quando sono disponibili.
|
||||
\nVuoi attivarlo?</string>
|
||||
<string name="import_settings_vulnerable_format">Le impostazioni nell\'export che viene importato usano un formato vulnerabile che è stato deprecato dalla versione 0.27.0 di NewPipe. Assicuratevi che l\'export importato venga da una fonte fidata, sarebbe preferibile usare solo exports ottenuti da NewPipe 0.27.0 o superiori, nel futuro. Il supporto all\'importazione di Impostazioni in questo formato vulnerabile sarà presto rimosso completamente, da quel momento le versioni di NewPipe più vecchie non saranno più in grado di importare impostazioni tramite export di versioni più recenti.</string>
|
||||
</resources>
|
||||
<string name="audio_track_type_secondary">secondaria</string>
|
||||
</resources>
|
||||
|
||||
@ -809,4 +809,4 @@
|
||||
\n
|
||||
\n続行しますか?</string>
|
||||
<string name="import_settings_vulnerable_format">インポートされているエクスポートの設定は、NewPipe 0.27.0以降は非推奨であった脆弱な形式を使用します。 インポートされているエクスポートは信頼できる情報源からであり、将来的にはNewPipe 0.27.0かこれより新しいバージョンから得られるエクスポートのみを優先して使用します。 この脆弱な形式で設定をインポートするための対応はすぐに完全に削除され、新しいバージョンからエクスポートの設定をインポートすることは出来ません。</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -740,4 +740,4 @@
|
||||
<string name="prefer_original_audio_title">არჩიე თავდაპირველი ხმის ჩანაწერი</string>
|
||||
<string name="play_queue_audio_track">ხმა: %s</string>
|
||||
<string name="audio_track">ხმის ჩანაწერი</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -145,4 +145,83 @@
|
||||
<string name="metadata_category">Taggayt</string>
|
||||
<string name="metadata_privacy">Tabaḍnit</string>
|
||||
<string name="metadata_licence">Turagt</string>
|
||||
<string name="no_videos">Ula d yiwet n tvidyutt</string>
|
||||
<string name="volume">Ableɣ</string>
|
||||
<string name="peertube_instance_add_title">Rnu tummant</string>
|
||||
<string name="albums">Ilbumen</string>
|
||||
<string name="recent">Melmi kan</string>
|
||||
<string name="drawer_close">Mdel umuɣ</string>
|
||||
<string name="open_with">Ldi s</string>
|
||||
<string name="show_description_title">Sken-d aglam</string>
|
||||
<string name="content_language_title">Tutlayt n ugbur amezwaru</string>
|
||||
<string name="network_error">Tuccḍa deg uẓeṭṭa</string>
|
||||
<string name="infinite_videos">∞ tividyutin</string>
|
||||
<string name="msg_wait">Ttxil arǧu…</string>
|
||||
<string name="title_activity_about">Ɣef NewPipe</string>
|
||||
<string name="video_player">Imeɣri n uvidyu</string>
|
||||
<string name="channel_tab_shorts">Shorts</string>
|
||||
<string name="channel_tab_livestreams">Srid</string>
|
||||
<string name="channel_tab_channels">Ibuda</string>
|
||||
<string name="duration">Tanzagt</string>
|
||||
<string name="question_mark">\?</string>
|
||||
<string name="image_quality_medium">Taɣara talemmast</string>
|
||||
<string name="image_quality_high">Taɣara tafellayt</string>
|
||||
<string name="show_more">Sken-d ugar</string>
|
||||
<string name="show_less">Sken-d drus</string>
|
||||
<string name="songs">Tizlatin</string>
|
||||
<string name="channel_tab_videos">Tividyutin</string>
|
||||
<string name="channel_tab_playlists">Tibdarin n tɣuri</string>
|
||||
<string name="channel_tab_albums">Ilbumen</string>
|
||||
<string name="channel_tab_about">Ɣef</string>
|
||||
<string name="rewind">Tuɣalin ɣer deffir</string>
|
||||
<string name="image_quality_title">Taɣara n tugna</string>
|
||||
<string name="sort">Smizzwer</string>
|
||||
<string name="audio_track_name">%1$s %2$s</string>
|
||||
<string name="import_file_title">Kter afaylu</string>
|
||||
<string name="feed_show_upcoming">Itteddu-d</string>
|
||||
<string name="drawer_open">Ldi umuɣ</string>
|
||||
<string name="none">Ula d yiwen</string>
|
||||
<string name="share_playlist_content_details">%1$s\n%2$s</string>
|
||||
<string name="yes">Ih</string>
|
||||
<string name="no">Uhu</string>
|
||||
<string name="play_with_kodi_title">Ɣeṛ-it-id s Kodi</string>
|
||||
<string name="brightness">Tafat</string>
|
||||
<string name="default_content_country_title">Tamurt n ugbur amezwaru</string>
|
||||
<string name="artists">Inaẓuren</string>
|
||||
<string name="notifications">Ilɣa</string>
|
||||
<string name="play_all">Ɣeṛ-itent-id akk</string>
|
||||
<string name="notification_channel_name">Ilɣa n NewPipe</string>
|
||||
<string name="comments_tab_description">Iwenniten</string>
|
||||
<string name="description_tab_description">Asnummel</string>
|
||||
<string name="recaptcha_solve">Fru</string>
|
||||
<string name="card">Takarḍa</string>
|
||||
<string name="chapters">Ixfawen</string>
|
||||
<string name="app_language_title">Tutlayt n wesnas</string>
|
||||
<string name="metadata_host">Asenneftaɣ</string>
|
||||
<string name="metadata_privacy_public">Azayez</string>
|
||||
<string name="metadata_privacy_internal">Tadigant</string>
|
||||
<string name="on">Irmed</string>
|
||||
<string name="off">Yensa</string>
|
||||
<string name="enumeration_comma">,</string>
|
||||
<string name="open_website_license">Ldi asmel Web</string>
|
||||
<string name="unknown_audio_track">Arussin</string>
|
||||
<string name="audio_track_type_original">aneṣli</string>
|
||||
<string name="unbookmark_playlist">Kkes ticreḍt</string>
|
||||
<string name="import_from">Kter seg</string>
|
||||
<string name="app_license_title">Turagt n NewPipe</string>
|
||||
<string name="read_full_license">Ɣeṛ turagt</string>
|
||||
<string name="error_http_not_found">Ur yettwaf ara</string>
|
||||
<string name="more_options">Ugar n tnefrunin</string>
|
||||
<string name="share_dialog_title">Bḍu d</string>
|
||||
<string name="error_details_headline">Talqayt :</string>
|
||||
<string name="title_most_played">Tid yettwaɣran s waṭas</string>
|
||||
<string name="tab_choose">Fren iccer</string>
|
||||
<string name="metadata_language">Tutlayt</string>
|
||||
<string name="unknown_content">[Arussin]</string>
|
||||
<string name="play">Ɣeṛ</string>
|
||||
<string name="what_device_headline">Talɣut:</string>
|
||||
<string name="metadata_support">Tadhalt</string>
|
||||
<string name="radio">Amaṭṭaf</string>
|
||||
<string name="metadata_tags">Tibzimin</string>
|
||||
<string name="blank_page_summary">Asebter d ilem</string>
|
||||
</resources>
|
||||
@ -578,4 +578,4 @@
|
||||
<string name="copyright">© %1$s bi %2$s di bin %3$s de</string>
|
||||
<string name="title_licenses">Lîsansên partiya sêyemîn</string>
|
||||
<string name="crash_the_app">Serlêdanê kilît bikin</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -809,4 +809,4 @@
|
||||
<string name="yes">네</string>
|
||||
<string name="reset_settings_title">설정 초기화</string>
|
||||
<string name="reset_settings_summary">모든 설정을 기본값으로 초기화</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -557,4 +557,4 @@
|
||||
<string name="show_description_summary">ناکارای بکە بۆ شاردنەوەی وەسف و زانیارییەکانی ڤیدیۆ</string>
|
||||
<string name="show_description_title">پیشاندانی وەسف</string>
|
||||
<string name="open_with">کردنەوە بە</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -42,7 +42,7 @@
|
||||
<string name="did_you_mean">Intendi \"%1$s\"\?</string>
|
||||
<string name="settings">Optiones</string>
|
||||
<string name="main_bg_subtitle">Tange \"Quaere\" ad initium</string>
|
||||
<string name="search">Quaere</string>
|
||||
<string name="search">Quaerere</string>
|
||||
<string name="download">Scarica</string>
|
||||
<string name="share">Condividi</string>
|
||||
<string name="open_with">Aperi apud</string>
|
||||
@ -121,4 +121,4 @@
|
||||
<string name="resize_zoom">Retrahe</string>
|
||||
<string name="resize_fill">Satiatus</string>
|
||||
<string name="resize_fit">Aptus</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -837,4 +837,4 @@
|
||||
\n%2$s</string>
|
||||
<string name="question_mark">\?</string>
|
||||
<string name="video_details_list_item">- %1$s: %2$s</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -645,4 +645,4 @@
|
||||
<string name="crash_the_player">പ്ലേയർ തകർക്കുക</string>
|
||||
<string name="prefer_original_audio_title">യഥാർത്ഥ ശബ്ദത്തിന് പരിഗണന കൊടുക്കുക</string>
|
||||
<string name="prefer_original_audio_summary">ഭാഷ ഏതായാലും യഥാർത്ഥ ശബ്ദം തിരഞ്ഞെടുക്കുക</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -514,4 +514,4 @@
|
||||
<string name="metadata_thumbnails">Lakaran kecil</string>
|
||||
<string name="metadata_uploader_avatars">Avatar pemuat naik</string>
|
||||
<string name="metadata_subchannel_avatars">Avatar saluran kecil</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -796,4 +796,4 @@
|
||||
<string name="use_exoplayer_decoder_fallback_title">Bruk ExoPlayers dekodings-tilbakefall</string>
|
||||
<string name="settings_category_exoplayer_summary">Håndter noen ExoPlayer-innstillinger. Disse endringeren krever omstart av avspilleren.</string>
|
||||
<string name="show_channel_tabs_summary">Hvilke faner som vises på kanalsidene</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -535,4 +535,4 @@
|
||||
<string name="search_showing_result_for">%s : का लागि परिणामहरु देखाइँदैछ</string>
|
||||
<string name="open_with">सँँग खोल्नुहोस्</string>
|
||||
<string name="mark_as_watched">हेरेको भनि चिन्ह लगाउनुहोस्</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -628,4 +628,4 @@
|
||||
<string name="error_report_channel_description">Meldingen om fouten te rapporteren</string>
|
||||
<string name="processing_may_take_a_moment">Verwerken... Dit kan even duren</string>
|
||||
<string name="leak_canary_not_available">LeakCanary is niet beschikbaar</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -823,4 +823,5 @@
|
||||
\nWilt u dit inschakelen?</string>
|
||||
<string name="reset_settings_title">Instellingen resetten</string>
|
||||
<string name="import_settings_vulnerable_format">De instellingen in de export die wordt geïmporteerd, gebruiken een kwetsbaar formaat dat verouderd is sinds NewPipe 0.27.0. Zorg ervoor dat de export die wordt geïmporteerd afkomstig is van een vertrouwde bron, en geef er de voorkeur aan om in de toekomst alleen exporten te gebruiken die zijn verkregen van NewPipe 0.27.0 of nieuwer. Ondersteuning voor het importeren van instellingen in dit kwetsbare formaat zal binnenkort volledig worden verwijderd, en oude versies van NewPipe zullen dan geen exportinstellingen meer uit nieuwe versies kunnen importeren.</string>
|
||||
</resources>
|
||||
<string name="audio_track_type_secondary">secundair</string>
|
||||
</resources>
|
||||
|
||||
@ -152,4 +152,4 @@
|
||||
<string name="brightness">Ljosstyrke</string>
|
||||
<string name="volume">Ljodstyrke</string>
|
||||
<string name="settings_category_updates_title">Oppdateringar</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -730,4 +730,4 @@
|
||||
<string name="prefer_original_audio_summary">ߡߍ߲ߕߊ߫ ߜߙߋ ߛߎ߲ߞߎ߲ߡߊ ߛߎ߲ߥߊ߲ߘߌ߫߸ ߊ߬ ߞߍߘߊ߫ ߞߊ߲ ߛߎ߯-ߛߎ߯ ߘߌ߫</string>
|
||||
<string name="prefer_descriptive_audio_title">ߡߍ߲ߕߊ߫ ߞߊ߲߬ߛߓߍ߬ߡߊ ߟߊߝߌ߬ߛߊ߬ߦߊ߫</string>
|
||||
<string name="unknown_audio_track">ߡߊߟߐ߲ߓߊߟߌ</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -823,4 +823,4 @@
|
||||
\n
|
||||
\nଆପଣ ଆଗକୁ ବଢିବାକୁ ଚାହୁଁଛନ୍ତି କି?</string>
|
||||
<string name="import_settings_vulnerable_format">ଆମଦାନୀ ହେଉଥିବା ରପ୍ତାନିରେ ଥିବା ସେଟିଂସମୂହ ଏକ ଅସୁରକ୍ଷିତ ଫର୍ମାଟ୍ ବ୍ୟବହାର କରେ ଯାହା NewPipe 0.27.0 ପରଠାରୁ ପୁରୁଣା ହୋଇଯାଇଥିଲା । ନିଶ୍ଚିତ କରନ୍ତୁ ଯେ ଆମଦାନୀ ହେଉଥିବା ରପ୍ତାନି ଏକ ବିଶ୍ୱସ୍ତ ଉତ୍ସରୁ ଆସିଛି ଏବଂ ଭବିଷ୍ୟତରେ କେବଳ NewPipe 0.27.0 କିମ୍ବା ନୂତନରୁ ପ୍ରାପ୍ତ ରପ୍ତାନି ବ୍ୟବହାର କରିବାକୁ ପସନ୍ଦ କରନ୍ତୁ । ଏହି ଅସୁରକ୍ଷିତ ଫର୍ମାଟରେ ସେଟିଂସମୂହ ଆମଦାନି ପାଇଁ ସମର୍ଥନ ଶୀଘ୍ର ସମ୍ପୂର୍ଣ୍ଣ ରୂପେ ଅପସାରିତ ହେବ, ଏବଂ ତା’ପରେ ନୂତନ ପାଇପ୍ ର ପୁରୁଣା ସଂସ୍କରଣଗୁଡ଼ିକ ନୂତନ ସଂସ୍କରଣରୁ ରପ୍ତାନୀର ସେଟିଂସମୂହ ଆମଦାନୀ କରିବାକୁ ସମର୍ଥ ହେବ ନାହିଁ ।</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -824,4 +824,4 @@
|
||||
<string name="no">ਨਹੀਂ</string>
|
||||
<string name="import_settings_vulnerable_format">ਇੰਪੋਰਟ ਕੀਤੇ ਜਾ ਰਹੇ ਐਕਸਪੋਰਟ ਵਿੱਚ ਸੈਟਿੰਗਾਂ ਇੱਕ ਕਮਜ਼ੋਰ ਫਾਰਮੈਟ ਦੀ ਵਰਤੋਂ ਕਰਦੀਆਂ ਹਨ ਜੋ ਨਿਊਪਾਈਪ 0.27.0 ਤੋਂ ਬਰਤਰਫ਼ ਕੀਤਾ ਗਿਆ ਸੀ। ਯਕੀਨੀ ਬਣਾਓ ਕਿ ਇੰਪੋਰਟ ਕੀਤਾ ਜਾ ਰਿਹਾ ਨਿਰਯਾਤ ਇੱਕ ਭਰੋਸੇਯੋਗ ਸਰੋਤ ਤੋਂ ਹੈ, ਅਤੇ ਸਿਰਫ਼ ਨਿਊਪਾਈਪ 0.27.0 ਜਾਂ ਇਸਤੋਂ ਨਵੇਂ ਤੋਂ ਪ੍ਰਾਪਤ ਕੀਤੇ ਐਕਸਪੋਰਟ ਦੀ ਵਰਤੋਂ ਕਰਨ ਨੂੰ ਤਰਜੀਹ ਦਿਓ। ਇਸ ਕਮਜ਼ੋਰ ਫਾਰਮੈਟ ਵਿੱਚ ਸੈਟਿੰਗਾਂ ਨੂੰ ਆਯਾਤ ਕਰਨ ਲਈ ਸਮਰਥਨ ਜਲਦੀ ਹੀ ਪੂਰੀ ਤਰ੍ਹਾਂ ਹਟਾ ਦਿੱਤਾ ਜਾਵੇਗਾ ਅਤੇ ਫਿਰ ਨਿਊਪਾਈਪ ਦੇ ਪੁਰਾਣੇ ਸੰਸਕਰਣ ਹੁਣ ਨਵੇਂ ਸੰਸਕਰਣਾਂ ਤੋਂ ਐਕਸਪੋਰਟ ਦੀਆਂ ਸੈਟਿੰਗਾਂ ਨੂੰ ਇੰਪੋਰਟ ਕਰਨ ਦੇ ਯੋਗ ਨਹੀਂ ਹੋਣਗੇ।</string>
|
||||
<string name="audio_track_type_secondary">ਸੈਕੰਡਰੀ</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -55,7 +55,7 @@
|
||||
<string name="error_snackbar_action">Zgłoś</string>
|
||||
<string name="what_device_headline">Informacje:</string>
|
||||
<string name="what_happened_headline">Co się stało:</string>
|
||||
<string name="info_labels">Co:\\nŻądanie:\\nJęzyk treści:\\nKraj treści:\\nJęzyk aplikacji:\\nUsługa:\\nCzas GMT:\\nPakiet:\\nWersja:\\nWersja systemu:</string>
|
||||
<string name="info_labels">Co:\\nŻądanie:\\nJęzyk treści:\\nKraj treści:\\nJęzyk aplikacji:\\nUsługa:\\nZnacznik czasu:\\nPakiet:\\nWersja:\\nWersja systemu:</string>
|
||||
<string name="your_comment">Twój komentarz (po angielsku):</string>
|
||||
<string name="error_details_headline">Szczegóły:</string>
|
||||
<string name="detail_thumbnail_view_description">Odtwarzane wideo, czas trwania:</string>
|
||||
@ -847,4 +847,4 @@
|
||||
\nCzy chcesz to włączyć?</string>
|
||||
<string name="import_settings_vulnerable_format">Ustawienia w importowanym eksporcie korzystają z podatnego na ataki formatu, który został wycofany od wersji NewPipe 0.27.0. Upewnij się, że importowany eksport pochodzi z zaufanego źródła, i w przyszłości używaj wyłącznie eksportów uzyskanych z NewPipe 0.27.0 lub nowszego. Obsługa importowania ustawień w tym podatnym formacie zostanie wkrótce całkowicie usunięta, a wtedy starsze wersje NewPipe nie będą już mogły importować ustawień z eksportu z nowych wersji.</string>
|
||||
<string name="audio_track_type_secondary">dodatkowa</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -174,7 +174,7 @@
|
||||
<string name="top_50">Top 50</string>
|
||||
<string name="new_and_hot">Novos e tendências</string>
|
||||
<string name="show_hold_to_append_title">Mostrar dica \"Segure para pôr na fila\"</string>
|
||||
<string name="show_hold_to_append_summary">Mostra dica ao tocar no botão segundo plano ou Popup em \"Detalhes:\" do vídeo</string>
|
||||
<string name="show_hold_to_append_summary">Mostra dica ao tocar no botão segundo plano ou Popup em \"Detalhes:\\ do vídeo</string>
|
||||
<string name="play_all">Reproduzir tudo</string>
|
||||
<string name="player_stream_failure">Não é possível reproduzir este vídeo</string>
|
||||
<string name="player_unrecoverable_failure">Ocorreu um erro irrecuperável na reprodução</string>
|
||||
@ -528,7 +528,7 @@
|
||||
<string name="remove_watched">Remover assistidos</string>
|
||||
<string name="show_original_time_ago_summary">Textos originais dos serviços serão visíveis nos itens de transmissão</string>
|
||||
<string name="show_original_time_ago_title">Mostrar tempo original nos itens</string>
|
||||
<string name="youtube_restricted_mode_enabled_title">Ativar o \"Modo Restrito\" do YouTube</string>
|
||||
<string name="youtube_restricted_mode_enabled_title">Ativar o \"Modo Restrito\\ do YouTube</string>
|
||||
<string name="video_detail_by">Por %s</string>
|
||||
<string name="channel_created_by">Criado por %s</string>
|
||||
<string name="detail_sub_channel_thumbnail_view_description">Foto de perfil do canal</string>
|
||||
@ -838,4 +838,4 @@
|
||||
\nTem certeza de que deseja continuar?</string>
|
||||
<string name="import_settings_vulnerable_format">As configurações na exportação que está sendo importada usam um formato vulnerável que foi descontinuado desde o NewPipe 0.27.0. Certifique-se de que a exportação que está sendo importada seja de uma fonte confiável e prefira usar apenas exportações obtidas do NewPipe 0.27.0 ou mais recente no futuro. O suporte para importação de configurações neste formato vulnerável será completamente removido em breve e as versões antigas do NewPipe não poderão mais importar configurações de exportações de novas versões.</string>
|
||||
<string name="audio_track_type_secondary">secundário</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@ -837,4 +837,5 @@
|
||||
<string name="reset_settings_title">Repor definições</string>
|
||||
<string name="error_insufficient_storage">Não há espaço suficiente no aparelho</string>
|
||||
<string name="import_settings_vulnerable_format">As configurações na exportação a serem importadas usam um formato vulnerável depreciado desde NewPipe 0.27.0. Certifique-se de que a exportação que é importada é de uma fonte confiável e prefira usar apenas as exportações obtidas do NewPipe 0.27.0 ou mais recentes no futuro. O suporte para importar configurações neste formato vulnerável será removido em breve completamente e, em seguida, versões antigas do NewPipe não serão capazes de importar configurações de exportações de novas versões.</string>
|
||||
</resources>
|
||||
<string name="audio_track_type_secondary">secundário</string>
|
||||
</resources>
|
||||
|
||||
@ -837,5 +837,5 @@
|
||||
<string name="auto_update_check_description">O NewPipe pode verificar automaticamente se há novas versões de tempos em tempos e notificá-lo quando elas estiverem disponíveis.
|
||||
\nDeseja ativar essa opção?</string>
|
||||
<string name="import_settings_vulnerable_format">As configurações na exportação a serem importadas usam um formato vulnerável depreciado desde NewPipe 0.27.0. Certifique-se de que a exportação que é importada é de uma fonte confiável e prefira usar apenas as exportações obtidas do NewPipe 0.27.0 ou mais recentes no futuro. O suporte para importar configurações neste formato vulnerável será removido em breve completamente e, em seguida, versões antigas do NewPipe não serão capazes de importar configurações de exportações de novas versões.</string>
|
||||
<string name="audio_track_type_secondary">secundario</string>
|
||||
</resources>
|
||||
<string name="audio_track_type_secondary">secundário</string>
|
||||
</resources>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user