some improvements for background player
|
@ -3,6 +3,7 @@ package org.schabi.newpipe;
|
|||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
|
@ -40,7 +41,10 @@ class ActionBarHandler {
|
|||
private static final String TAG = ActionBarHandler.class.toString();
|
||||
private static final String KORE_PACKET = "org.xbmc.kore";
|
||||
|
||||
private int serviceId;
|
||||
private String websiteUrl = "";
|
||||
private Bitmap videoThumbnail = null;
|
||||
private String channelName = "";
|
||||
private AppCompatActivity activity;
|
||||
private VideoInfo.VideoStream[] videoStreams = null;
|
||||
private VideoInfo.AudioStream audioStream = null;
|
||||
|
@ -73,6 +77,18 @@ class ActionBarHandler {
|
|||
}
|
||||
}
|
||||
|
||||
public void setServiceId(int id) {
|
||||
serviceId = id;
|
||||
}
|
||||
|
||||
public void setSetVideoThumbnail(Bitmap bitmap) {
|
||||
videoThumbnail = bitmap;
|
||||
}
|
||||
|
||||
public void setChannelName(String name) {
|
||||
channelName = name;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public void setStreams(VideoInfo.VideoStream[] videoStreams, VideoInfo.AudioStream[] audioStreams) {
|
||||
this.videoStreams = videoStreams;
|
||||
|
@ -143,17 +159,17 @@ class ActionBarHandler {
|
|||
}
|
||||
|
||||
public boolean onItemSelected(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
switch(id) {
|
||||
case R.id.menu_item_share:
|
||||
if(!videoTitle.isEmpty()) {
|
||||
int id = item.getItemId();
|
||||
switch (id) {
|
||||
case R.id.menu_item_share: {
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_SEND);
|
||||
intent.putExtra(Intent.EXTRA_TEXT, websiteUrl);
|
||||
intent.setType("text/plain");
|
||||
activity.startActivity(Intent.createChooser(intent, activity.getString(R.string.shareDialogTitle)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case R.id.menu_item_openInBrowser: {
|
||||
openInBrowser();
|
||||
}
|
||||
|
@ -175,6 +191,10 @@ class ActionBarHandler {
|
|||
default:
|
||||
Log.e(TAG, "Menu Item not known");
|
||||
}
|
||||
} else {
|
||||
// That line may not be necessary.
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -302,18 +322,24 @@ class ActionBarHandler {
|
|||
.getBoolean(activity.getString(R.string.useExternalAudioPlayer), false);
|
||||
Intent intent;
|
||||
|
||||
if (!externalAudioPlayer && android.os.Build.VERSION.SDK_INT >= 18)//internal music player: explicit intent
|
||||
{
|
||||
if (!externalAudioPlayer && android.os.Build.VERSION.SDK_INT >= 18) {
|
||||
//internal music player: explicit intent
|
||||
if (!BackgroundPlayer.isRunning && videoThumbnail != null) {
|
||||
ActivityCommunicator.getCommunicator()
|
||||
.backgroundPlayerThumbnail = videoThumbnail;
|
||||
intent = new Intent(activity, BackgroundPlayer.class);
|
||||
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
Log.i(TAG, "audioStream is null:" + (audioStream == null));
|
||||
Log.i(TAG, "audioStream.url is null:"+(audioStream.url==null));
|
||||
Log.i(TAG, "audioStream.url is null:" + (audioStream.url == null));
|
||||
intent.setDataAndType(Uri.parse(audioStream.url),
|
||||
MediaFormat.getMimeById(audioStream.format));
|
||||
intent.putExtra(Intent.EXTRA_TITLE, videoTitle);
|
||||
intent.putExtra("title", videoTitle);
|
||||
intent.putExtra(BackgroundPlayer.TITLE, videoTitle);
|
||||
intent.putExtra(BackgroundPlayer.WEB_URL, websiteUrl);
|
||||
intent.putExtra(BackgroundPlayer.SERVICE_ID, serviceId);
|
||||
intent.putExtra(BackgroundPlayer.CHANNEL_NAME, channelName);
|
||||
activity.startService(intent);
|
||||
}
|
||||
} else {
|
||||
intent = new Intent();
|
||||
try {
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
package org.schabi.newpipe;
|
||||
|
||||
/**
|
||||
* Created by Christian Schabesberger on 24.12.15.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
|
||||
* ActivityCommunicator.java is part of NewPipe.
|
||||
*
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
/**
|
||||
* Singleton:
|
||||
* Used to send data between certain Activity/Services within the same process.
|
||||
* This can be considered as hack inside the Android universe. **/
|
||||
public class ActivityCommunicator {
|
||||
|
||||
private static ActivityCommunicator activityCommunicator = null;
|
||||
|
||||
public static ActivityCommunicator getCommunicator() {
|
||||
if(activityCommunicator == null) {
|
||||
activityCommunicator = new ActivityCommunicator();
|
||||
}
|
||||
return activityCommunicator;
|
||||
}
|
||||
|
||||
// Thumbnail send from VideoItemDetailFragment to BackgroundPlayer
|
||||
public volatile Bitmap backgroundPlayerThumbnail;
|
||||
}
|
|
@ -8,6 +8,8 @@ import android.content.BroadcastReceiver;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaPlayer;
|
||||
import android.net.wifi.WifiManager;
|
||||
|
@ -43,8 +45,22 @@ import java.io.IOException;
|
|||
public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPreparedListener*/ {
|
||||
|
||||
private static final String TAG = BackgroundPlayer.class.toString();
|
||||
private static final String ACTION_STOP = TAG+".STOP";
|
||||
private static final String ACTION_PLAYPAUSE = TAG+".PLAYPAUSE";
|
||||
private static final String ACTION_STOP = TAG + ".STOP";
|
||||
private static final String ACTION_PLAYPAUSE = TAG + ".PLAYPAUSE";
|
||||
|
||||
// Extra intent arguments
|
||||
public static final String TITLE = "title";
|
||||
public static final String WEB_URL = "web_url";
|
||||
public static final String SERVICE_ID = "service_id";
|
||||
public static final String CHANNEL_NAME="channel_name";
|
||||
|
||||
private volatile String webUrl = "";
|
||||
private volatile int serviceId = -1;
|
||||
private volatile String channelName = "";
|
||||
|
||||
// Determines if the service is already running.
|
||||
// Prevents launching the service twice.
|
||||
public static volatile boolean isRunning = false;
|
||||
|
||||
public BackgroundPlayer() {
|
||||
super();
|
||||
|
@ -58,16 +74,22 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
|
|||
}
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
Toast.makeText(this, "Playing in background", Toast.LENGTH_SHORT).show();//todo:translation string
|
||||
Toast.makeText(this, R.string.backgroundPlayerStartPlayingToast,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
|
||||
String source = intent.getDataString();
|
||||
//Log.i(TAG, "backgroundPLayer source:"+source);
|
||||
String videoTitle = intent.getStringExtra("title");
|
||||
String videoTitle = intent.getStringExtra(TITLE);
|
||||
webUrl = intent.getStringExtra(WEB_URL);
|
||||
serviceId = intent.getIntExtra(SERVICE_ID, -1);
|
||||
channelName = intent.getStringExtra(CHANNEL_NAME);
|
||||
|
||||
//do nearly everything in a separate thread
|
||||
PlayerThread player = new PlayerThread(source, videoTitle, this);
|
||||
player.start();
|
||||
|
||||
isRunning = true;
|
||||
|
||||
// If we get killed after returning here, don't restart
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
@ -81,6 +103,7 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
|
|||
@Override
|
||||
public void onDestroy() {
|
||||
//Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
|
||||
isRunning = false;
|
||||
}
|
||||
|
||||
private class PlayerThread extends Thread {
|
||||
|
@ -92,6 +115,7 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
|
|||
private NotificationManager noteMgr;
|
||||
private NotificationCompat.Builder noteBuilder;
|
||||
private WifiManager.WifiLock wifiLock;
|
||||
private Bitmap videoThumbnail = null;
|
||||
|
||||
public PlayerThread(String src, String title, BackgroundPlayer owner) {
|
||||
this.source = src;
|
||||
|
@ -102,11 +126,14 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
|
|||
}
|
||||
@Override
|
||||
public void run() {
|
||||
Resources res = getApplicationContext().getResources();
|
||||
|
||||
mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);//cpu lock
|
||||
try {
|
||||
mediaPlayer.setDataSource(source);
|
||||
mediaPlayer.prepare(); //We are already in a separate worker thread,
|
||||
//We are already in a separate worker thread,
|
||||
//so calling the blocking prepare() method should be ok
|
||||
mediaPlayer.prepare();
|
||||
|
||||
//alternatively:
|
||||
//mediaPlayer.setOnPreparedListener(this);
|
||||
|
@ -119,10 +146,18 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
|
|||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
videoThumbnail = ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Could not get video thumbnail from ActivityCommunicator");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
WifiManager wifiMgr = ((WifiManager)getSystemService(Context.WIFI_SERVICE));
|
||||
wifiLock = wifiMgr.createWifiLock(WifiManager.WIFI_MODE_FULL, TAG);
|
||||
|
||||
mediaPlayer.setOnCompletionListener(new EndListener(wifiLock));//listen for end of video
|
||||
//listen for end of video
|
||||
mediaPlayer.setOnCompletionListener(new EndListener(wifiLock));
|
||||
|
||||
//get audio focus
|
||||
/*
|
||||
|
@ -142,43 +177,65 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
|
|||
filter.addAction(ACTION_STOP);
|
||||
registerReceiver(broadcastReceiver, filter);
|
||||
|
||||
PendingIntent playPI = PendingIntent.getBroadcast(owner, noteID, new Intent(ACTION_PLAYPAUSE), PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
PendingIntent playPI = PendingIntent.getBroadcast(owner, noteID,
|
||||
new Intent(ACTION_PLAYPAUSE), PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
NotificationCompat.Action playButton = new NotificationCompat.Action.Builder
|
||||
(R.drawable.ic_play_arrow_white_48dp, "Play", playPI).build();
|
||||
|
||||
/*
|
||||
NotificationCompat.Action pauseButton = new NotificationCompat.Action.Builder
|
||||
(R.drawable.ic_play_arrow_white_48dp, "Pause", playPI).build();
|
||||
(R.drawable.ic_pause_white_24dp, "Pause", playPI).build();
|
||||
*/
|
||||
|
||||
PendingIntent stopPI = PendingIntent.getBroadcast(owner, noteID,
|
||||
new Intent(ACTION_STOP), PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
//todo: make it so that tapping the notification brings you back to the Video's DetailActivity
|
||||
//using setContentIntent
|
||||
noteBuilder = new NotificationCompat.Builder(owner);
|
||||
noteBuilder
|
||||
.setPriority(Notification.PRIORITY_LOW)
|
||||
.setCategory(Notification.CATEGORY_TRANSPORT)
|
||||
.setContentTitle(title)
|
||||
.setContentText("NewPipe is playing in the background")//todo: translation string
|
||||
//really? Id like to put something more helpful here.
|
||||
//.setContentText("NewPipe is playing in the background")
|
||||
.setContentText(channelName)
|
||||
//.setAutoCancel(!mediaPlayer.isPlaying())
|
||||
.setOngoing(true)
|
||||
.setDeleteIntent(stopPI)
|
||||
//.setProgress(vidLength, 0, false) //doesn't fit with Notification.MediaStyle
|
||||
.setSmallIcon(R.mipmap.ic_launcher)
|
||||
.setTicker(title + " - NewPipe")
|
||||
//doesn't fit with Notification.MediaStyle
|
||||
//.setProgress(vidLength, 0, false)
|
||||
.setSmallIcon(R.drawable.ic_play_circle_filled_white_24dp)
|
||||
.setLargeIcon(videoThumbnail)
|
||||
.setTicker(
|
||||
String.format(res.getString(
|
||||
R.string.backgroundPlayerTickerText), title))
|
||||
.addAction(playButton);
|
||||
/* .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setLargeIcon(cover)*/
|
||||
//.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
//.setLargeIcon(cover)
|
||||
if(android.os.Build.VERSION.SDK_INT >= 16)
|
||||
noteBuilder.setPriority(Notification.PRIORITY_LOW);
|
||||
if(android.os.Build.VERSION.SDK_INT >= 21)
|
||||
noteBuilder.setCategory(Notification.CATEGORY_TRANSPORT);
|
||||
|
||||
noteBuilder.setStyle(new NotificationCompat.MediaStyle()
|
||||
//.setMediaSession(mMediaSession.getSessionToken())
|
||||
.setShowActionsInCompactView(new int[] {0})
|
||||
.setShowActionsInCompactView(new int[]{0})
|
||||
.setShowCancelButton(true)
|
||||
.setCancelButtonIntent(stopPI)
|
||||
);
|
||||
.setCancelButtonIntent(stopPI));
|
||||
if(videoThumbnail != null) {
|
||||
noteBuilder.setLargeIcon(videoThumbnail);
|
||||
}
|
||||
|
||||
startForeground(noteID, noteBuilder.build());
|
||||
Notification note = noteBuilder.build();
|
||||
|
||||
Intent openDetailView = new Intent(getApplicationContext(),
|
||||
VideoItemDetailActivity.class);
|
||||
openDetailView.putExtra(VideoItemDetailFragment.STREAMING_SERVICE, serviceId);
|
||||
openDetailView.putExtra(VideoItemDetailFragment.VIDEO_URL, webUrl);
|
||||
openDetailView.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
note.contentIntent = PendingIntent.getActivity(getApplicationContext(),
|
||||
noteID, openDetailView,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
startForeground(noteID, note);
|
||||
|
||||
//currently decommissioned progressbar looping update code - works, but doesn't fit inside
|
||||
//Notification.MediaStyle Notification layout.
|
||||
|
@ -202,7 +259,7 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
|
|||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
Log.i(TAG, "received broadcast action:"+action);
|
||||
//Log.i(TAG, "received broadcast action:"+action);
|
||||
if(action.equals(ACTION_PLAYPAUSE)) {
|
||||
if(mediaPlayer.isPlaying()) {
|
||||
mediaPlayer.pause();
|
||||
|
@ -214,7 +271,8 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
|
|||
}
|
||||
}
|
||||
else if(action.equals(ACTION_STOP)) {
|
||||
mediaPlayer.stop();//this auto-releases CPU lock
|
||||
//this auto-releases CPU lock
|
||||
mediaPlayer.stop();
|
||||
afterPlayCleanup();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ public class VideoItemDetailFragment extends Fragment {
|
|||
* The fragment argument representing the item ID that this fragment
|
||||
* represents.
|
||||
*/
|
||||
public static final String ARG_ITEM_ID = "item_id";
|
||||
//public static final String ARG_ITEM_ID = "item_id";
|
||||
public static final String VIDEO_URL = "video_url";
|
||||
public static final String STREAMING_SERVICE = "streaming_service";
|
||||
public static final String AUTO_PLAY = "auto_play";
|
||||
|
@ -81,6 +81,8 @@ public class VideoItemDetailFragment extends Fragment {
|
|||
private AppCompatActivity activity;
|
||||
private ActionBarHandler actionBarHandler;
|
||||
|
||||
private int streamingServiceId = -1;
|
||||
|
||||
private boolean autoPlayEnabled = false;
|
||||
private VideoInfo currentVideoInfo = null;
|
||||
private boolean showNextVideoItem = false;
|
||||
|
@ -105,6 +107,7 @@ public class VideoItemDetailFragment extends Fragment {
|
|||
this.service = service;
|
||||
this.videoUrl = videoUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
|
@ -176,6 +179,7 @@ public class VideoItemDetailFragment extends Fragment {
|
|||
switch (id) {
|
||||
case SetThumbnailRunnable.VIDEO_THUMBNAIL:
|
||||
thumbnailView = (ImageView) a.findViewById(R.id.detailThumbnailView);
|
||||
actionBarHandler.setSetVideoThumbnail(thumbnail);
|
||||
break;
|
||||
case SetThumbnailRunnable.CHANNEL_THUMBNAIL:
|
||||
thumbnailView = (ImageView) a.findViewById(R.id.detailUploaderThumbnailView);
|
||||
|
@ -238,6 +242,7 @@ public class VideoItemDetailFragment extends Fragment {
|
|||
case VideoInfo.VIDEO_AVAILABLE: {
|
||||
videoTitleView.setText(info.title);
|
||||
uploaderView.setText(info.uploader);
|
||||
actionBarHandler.setChannelName(info.uploader);
|
||||
|
||||
Locale locale = getPreferredLocale();
|
||||
NumberFormat nf = NumberFormat.getInstance(locale);
|
||||
|
@ -266,6 +271,7 @@ public class VideoItemDetailFragment extends Fragment {
|
|||
descriptionView.setText(Html.fromHtml(info.description));
|
||||
descriptionView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
|
||||
actionBarHandler.setServiceId(streamingServiceId);
|
||||
actionBarHandler.setVideoInfo(info.webpage_url, info.title);
|
||||
actionBarHandler.setStartPosition(info.startPosition);
|
||||
|
||||
|
@ -288,12 +294,12 @@ public class VideoItemDetailFragment extends Fragment {
|
|||
public void onClick(View v) {
|
||||
Intent detailIntent =
|
||||
new Intent(getActivity(), VideoItemDetailActivity.class);
|
||||
detailIntent.putExtra(
|
||||
VideoItemDetailFragment.ARG_ITEM_ID, currentVideoInfo.nextVideo.id);
|
||||
/*detailIntent.putExtra(
|
||||
VideoItemDetailFragment.ARG_ITEM_ID, currentVideoInfo.nextVideo.id); */
|
||||
detailIntent.putExtra(
|
||||
VideoItemDetailFragment.VIDEO_URL, currentVideoInfo.nextVideo.webpage_url);
|
||||
//todo: make id dynamic the following line is crap
|
||||
detailIntent.putExtra(VideoItemDetailFragment.STREAMING_SERVICE, 0);
|
||||
detailIntent.putExtra(VideoItemDetailFragment.STREAMING_SERVICE, streamingServiceId);
|
||||
startActivity(detailIntent);
|
||||
}
|
||||
});
|
||||
|
@ -369,8 +375,8 @@ public class VideoItemDetailFragment extends Fragment {
|
|||
// Otherwise the applications would crash.
|
||||
if(playVideoButton != null) {
|
||||
try {
|
||||
StreamingService streamingService = ServiceList.getService(
|
||||
getArguments().getInt(STREAMING_SERVICE));
|
||||
streamingServiceId = getArguments().getInt(STREAMING_SERVICE);
|
||||
StreamingService streamingService = ServiceList.getService(streamingServiceId);
|
||||
Thread videoExtractorThread = new Thread(new VideoExtractorRunnable(
|
||||
getArguments().getString(VIDEO_URL), streamingService));
|
||||
|
||||
|
|
|
@ -190,7 +190,7 @@ public class VideoItemListActivity extends AppCompatActivity
|
|||
// adding or replacing the detail fragment using a
|
||||
// fragment transaction.
|
||||
Bundle arguments = new Bundle();
|
||||
arguments.putString(VideoItemDetailFragment.ARG_ITEM_ID, id);
|
||||
//arguments.putString(VideoItemDetailFragment.ARG_ITEM_ID, id);
|
||||
arguments.putString(VideoItemDetailFragment.VIDEO_URL, webpage_url);
|
||||
arguments.putInt(VideoItemDetailFragment.STREAMING_SERVICE, currentStreamingServiceId);
|
||||
videoFragment = new VideoItemDetailFragment();
|
||||
|
@ -209,7 +209,7 @@ public class VideoItemListActivity extends AppCompatActivity
|
|||
// In single-pane mode, simply start the detail activity
|
||||
// for the selected item ID.
|
||||
Intent detailIntent = new Intent(this, VideoItemDetailActivity.class);
|
||||
detailIntent.putExtra(VideoItemDetailFragment.ARG_ITEM_ID, id);
|
||||
//detailIntent.putExtra(VideoItemDetailFragment.ARG_ITEM_ID, id);
|
||||
detailIntent.putExtra(VideoItemDetailFragment.VIDEO_URL, webpage_url);
|
||||
detailIntent.putExtra(VideoItemDetailFragment.STREAMING_SERVICE, currentStreamingServiceId);
|
||||
startActivity(detailIntent);
|
||||
|
|
After Width: | Height: | Size: 105 B |
After Width: | Height: | Size: 358 B |
After Width: | Height: | Size: 83 B |
After Width: | Height: | Size: 255 B |
After Width: | Height: | Size: 90 B |
After Width: | Height: | Size: 464 B |
After Width: | Height: | Size: 92 B |
After Width: | Height: | Size: 666 B |
After Width: | Height: | Size: 94 B |
After Width: | Height: | Size: 883 B |
|
@ -50,6 +50,8 @@
|
|||
<string name="settingsCategoryVideoAudioTitle">VIDEO & AUDIO</string>
|
||||
<string name="settingsCategoryVideoInfoTittle">INFO</string>
|
||||
<string name="settingsCategoryEtcTitle">ETC</string>
|
||||
<string name="backgroundPlayerTickerText" translatable="false">%1$s - NewPipe</string>
|
||||
<string name="backgroundPlayerStartPlayingToast">Playing in background</string>
|
||||
|
||||
<!-- Content descriptions (for better accessibility) -->
|
||||
<string name="itemThumbnailViewDescription">Video preview thumbnail</string>
|
||||
|
|