Merge pull request #318 from nitehu/feature/bluetooth_connection_settings

Implemented Bluetooth connection settings
This commit is contained in:
Nite 2020-09-29 18:10:25 +02:00 committed by GitHub
commit 60b6a5dbde
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 364 additions and 91 deletions

View File

@ -126,14 +126,6 @@
android:name=".service.MediaPlayerService"
android:label="Ultrasonic Download Service"
android:exported="false">
<intent-filter>
<action android:name="org.moire.ultrasonic.CMD_TOGGLEPAUSE"/>
<action android:name="org.moire.ultrasonic.CMD_PLAY"/>
<action android:name="org.moire.ultrasonic.CMD_PAUSE"/>
<action android:name="org.moire.ultrasonic.CMD_NEXT"/>
<action android:name="org.moire.ultrasonic.CMD_PREVIOUS"/>
<action android:name="org.moire.ultrasonic.CMD_STOP"/>
</intent-filter>
</service>
<receiver android:name=".receiver.MediaButtonIntentReceiver">
@ -141,12 +133,24 @@
<action android:name="android.intent.action.MEDIA_BUTTON"/>
</intent-filter>
</receiver>
<receiver android:name=".receiver.UltrasonicIntentReceiver">
<intent-filter>
<action android:name="org.moire.ultrasonic.CMD_TOGGLEPAUSE"/>
<action android:name="org.moire.ultrasonic.CMD_PLAY"/>
<action android:name="org.moire.ultrasonic.CMD_RESUME_OR_PLAY"/>
<action android:name="org.moire.ultrasonic.CMD_PAUSE"/>
<action android:name="org.moire.ultrasonic.CMD_NEXT"/>
<action android:name="org.moire.ultrasonic.CMD_PREVIOUS"/>
<action android:name="org.moire.ultrasonic.CMD_STOP"/>
<action android:name="org.moire.ultrasonic.CMD_PROCESS_KEYCODE"/>
</intent-filter>
</receiver>
<receiver android:name=".receiver.BluetoothIntentReceiver">
<intent-filter>
<action android:name="android.bluetooth.device.action.ACL_CONNECTED"/>
<action android:name="android.bluetooth.device.action.ACL_DISCONNECTED"/>
<action android:name="android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED"/>
<action android:name="android.bluetooth.a2dp.action.SINK_STATE_CHANGED"/>
<action android:name="android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED"/>
</intent-filter>
</receiver>
<receiver

View File

@ -1,5 +1,7 @@
package org.moire.ultrasonic.fragment;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
@ -7,6 +9,8 @@ import android.os.Bundle;
import android.preference.*;
import android.provider.SearchRecentSuggestions;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import android.util.Log;
import android.view.View;
@ -19,6 +23,7 @@ import org.moire.ultrasonic.featureflags.FeatureStorage;
import org.moire.ultrasonic.filepicker.FilePickerDialog;
import org.moire.ultrasonic.filepicker.OnFileSelectedListener;
import org.moire.ultrasonic.provider.SearchSuggestionProvider;
import org.moire.ultrasonic.service.Consumer;
import org.moire.ultrasonic.service.MediaPlayerController;
import org.moire.ultrasonic.util.*;
@ -64,6 +69,8 @@ public class SettingsFragment extends PreferenceFragment
private EditTextPreference sharingDefaultGreeting;
private TimeSpanPreference sharingDefaultExpiration;
private PreferenceCategory serversCategory;
private Preference resumeOnBluetoothDevice;
private Preference pauseOnBluetoothDevice;
private SharedPreferences settings;
@ -110,12 +117,15 @@ public class SettingsFragment extends PreferenceFragment
sharingDefaultGreeting = (EditTextPreference) findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_GREETING);
sharingDefaultExpiration = (TimeSpanPreference) findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_EXPIRATION);
serversCategory = (PreferenceCategory) findPreference(Constants.PREFERENCES_KEY_SERVERS_KEY);
resumeOnBluetoothDevice = findPreference(Constants.PREFERENCES_KEY_RESUME_ON_BLUETOOTH_DEVICE);
pauseOnBluetoothDevice = findPreference(Constants.PREFERENCES_KEY_PAUSE_ON_BLUETOOTH_DEVICE);
sharingDefaultGreeting.setText(Util.getShareGreeting(getActivity()));
setupClearSearchPreference();
setupGaplessControlSettingsV14();
setupFeatureFlagsPreferences();
setupCacheLocationPreference();
setupBluetoothDevicePreferences();
// After API26 foreground services must be used for music playback, and they must have a notification
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@ -199,6 +209,87 @@ public class SettingsFragment extends PreferenceFragment
});
}
private void setupBluetoothDevicePreferences() {
final int resumeSetting = Util.getResumeOnBluetoothDevice(getActivity());
final int pauseSetting = Util.getPauseOnBluetoothDevice(getActivity());
resumeOnBluetoothDevice.setSummary(bluetoothDevicePreferenceToString(resumeSetting));
pauseOnBluetoothDevice.setSummary(bluetoothDevicePreferenceToString(pauseSetting));
resumeOnBluetoothDevice.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
showBluetoothDevicePreferenceDialog(
R.string.settings_playback_resume_on_bluetooth_device,
Util.getResumeOnBluetoothDevice(getActivity()),
new Consumer<Integer>() {
@Override
public void accept(Integer choice) {
SharedPreferences.Editor editor = resumeOnBluetoothDevice.getEditor();
editor.putInt(Constants.PREFERENCES_KEY_RESUME_ON_BLUETOOTH_DEVICE, choice);
editor.commit();
resumeOnBluetoothDevice.setSummary(bluetoothDevicePreferenceToString(choice));
}
});
return true;
}
});
pauseOnBluetoothDevice.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
showBluetoothDevicePreferenceDialog(
R.string.settings_playback_pause_on_bluetooth_device,
Util.getPauseOnBluetoothDevice(getActivity()),
new Consumer<Integer>() {
@Override
public void accept(Integer choice) {
SharedPreferences.Editor editor = pauseOnBluetoothDevice.getEditor();
editor.putInt(Constants.PREFERENCES_KEY_PAUSE_ON_BLUETOOTH_DEVICE, choice);
editor.commit();
pauseOnBluetoothDevice.setSummary(bluetoothDevicePreferenceToString(choice));
}
});
return true;
}
});
}
private void showBluetoothDevicePreferenceDialog(@StringRes int title, int defaultChoice, final Consumer<Integer> onChosen) {
final int[] choice = {defaultChoice};
new AlertDialog.Builder(getActivity()).setTitle(title)
.setSingleChoiceItems(R.array.bluetoothDeviceSettingNames, defaultChoice,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
choice[0] = i;
}
})
.setNegativeButton(R.string.common_cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.cancel();
}
})
.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
onChosen.accept(choice[0]);
dialogInterface.dismiss();
}
})
.create().show();
}
private String bluetoothDevicePreferenceToString(int preferenceValue) {
switch (preferenceValue) {
case Constants.PREFERENCE_VALUE_ALL: return getString(R.string.settings_playback_bluetooth_all);
case Constants.PREFERENCE_VALUE_A2DP: return getString(R.string.settings_playback_bluetooth_a2dp);
case Constants.PREFERENCE_VALUE_DISABLED: return getString(R.string.settings_playback_bluetooth_disabled);
default: return "";
}
}
private void setupClearSearchPreference() {
Preference clearSearchPreference = findPreference(Constants.PREFERENCES_KEY_CLEAR_SEARCH_HISTORY);

View File

@ -19,17 +19,10 @@ public class A2dpIntentReceiver extends BroadcastReceiver
@Override
public void onReceive(Context context, Intent intent)
{
if (mediaPlayerControllerLazy.getValue().getCurrentPlaying() == null)
{
return;
}
if (mediaPlayerControllerLazy.getValue().getCurrentPlaying() == null) return;
Entry song = mediaPlayerControllerLazy.getValue().getCurrentPlaying().getSong();
if (song == null)
{
return;
}
if (song == null) return;
Intent avrcpIntent = new Intent(PLAYSTATUS_RESPONSE);

View File

@ -19,6 +19,7 @@
package org.moire.ultrasonic.receiver;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@ -34,44 +35,72 @@ import org.moire.ultrasonic.util.Util;
*/
public class BluetoothIntentReceiver extends BroadcastReceiver
{
private static final String TAG = BluetoothIntentReceiver.class.getSimpleName();
@Override
public void onReceive(Context context, Intent intent)
{
int state = intent.getIntExtra("android.bluetooth.a2dp.extra.SINK_STATE", -1);
int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
String action = intent.getAction();
String name = device != null ? device.getName() : "None";
String name = device != null ? device.getName() : "Unknown";
String address = device != null ? device.getAddress() : "Unknown";
Log.d(TAG, String.format("Sink State: %d; Action: %s; Device: %s", state, action, name));
Log.d(TAG, String.format("A2DP State: %d; Action: %s; Device: %s; Address: %s", state, action, name, address));
boolean actionConnected = false;
boolean actionDisconnected = false;
boolean actionBluetoothDeviceConnected = false;
boolean actionBluetoothDeviceDisconnected = false;
boolean actionA2dpConnected = false;
boolean actionA2dpDisconnected = false;
if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action))
{
actionConnected = true;
actionBluetoothDeviceConnected = true;
}
else if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action) || BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED.equals(action))
{
actionDisconnected = true;
actionBluetoothDeviceDisconnected = true;
}
boolean connected = state == android.bluetooth.BluetoothA2dp.STATE_CONNECTED || actionConnected;
boolean disconnected = state == android.bluetooth.BluetoothA2dp.STATE_DISCONNECTED || actionDisconnected;
if (state == android.bluetooth.BluetoothA2dp.STATE_CONNECTED) actionA2dpConnected = true;
else if (state == android.bluetooth.BluetoothA2dp.STATE_DISCONNECTED) actionA2dpDisconnected = true;
boolean connected = actionA2dpConnected || actionBluetoothDeviceConnected;
boolean resume = false;
boolean pause = false;
switch (Util.getResumeOnBluetoothDevice(context))
{
case Constants.PREFERENCE_VALUE_ALL: resume = actionA2dpConnected || actionBluetoothDeviceConnected;
break;
case Constants.PREFERENCE_VALUE_A2DP: resume = actionA2dpConnected;
break;
}
switch (Util.getPauseOnBluetoothDevice(context))
{
case Constants.PREFERENCE_VALUE_ALL: pause = actionA2dpDisconnected || actionBluetoothDeviceDisconnected;
break;
case Constants.PREFERENCE_VALUE_A2DP: pause = actionA2dpDisconnected;
break;
}
if (connected)
{
Log.i(TAG, "Connected to Bluetooth device, requesting media button focus.");
Log.i(TAG, String.format("Connected to Bluetooth device %s address %s, requesting media button focus.", name, address));
Util.registerMediaButtonEventReceiver(context, false);
}
if (disconnected)
if (resume)
{
Log.i(TAG, "Disconnected from Bluetooth device, requesting pause.");
context.sendBroadcast(new Intent(Constants.CMD_PAUSE));
Log.i(TAG, String.format("Connected to Bluetooth device %s address %s, resuming playback.", name, address));
context.sendBroadcast(new Intent(Constants.CMD_RESUME_OR_PLAY).setPackage(context.getPackageName()));
}
if (pause)
{
Log.i(TAG, String.format("Disconnected from Bluetooth device %s address %s, requesting pause.", name, address));
context.sendBroadcast(new Intent(Constants.CMD_PAUSE).setPackage(context.getPackageName()));
}
}
}

View File

@ -0,0 +1,39 @@
package org.moire.ultrasonic.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import org.moire.ultrasonic.service.MediaPlayerLifecycleSupport;
import kotlin.Lazy;
import static org.koin.java.KoinJavaComponent.inject;
public class UltrasonicIntentReceiver extends BroadcastReceiver
{
private static final String TAG = UltrasonicIntentReceiver.class.getSimpleName();
private Lazy<MediaPlayerLifecycleSupport> lifecycleSupport = inject(MediaPlayerLifecycleSupport.class);
@Override
public void onReceive(Context context, Intent intent)
{
String intentAction = intent.getAction();
Log.i(TAG, String.format("Received Ultrasonic Intent: %s", intentAction));
try
{
lifecycleSupport.getValue().receiveIntent(intent);
if (isOrderedBroadcast())
{
abortBroadcast();
}
}
catch (Exception x)
{
// Ignored.
}
}
}

View File

@ -89,7 +89,7 @@ public class Downloader
public void stop()
{
executorService.shutdown();
if (executorService != null) executorService.shutdown();
Log.i(TAG, "Downloader stopped");
}

View File

@ -170,6 +170,16 @@ public class MediaPlayerControllerImpl implements MediaPlayerController
});
}
public synchronized void resumeOrPlay()
{
MediaPlayerService.executeOnStartedMediaPlayerService(context, new Consumer<MediaPlayerService>() {
@Override
public void accept(MediaPlayerService mediaPlayerService) {
mediaPlayerService.resumeOrPlay();
}
});
}
@Override
public synchronized void togglePlayPause()
{

View File

@ -81,17 +81,6 @@ public class MediaPlayerLifecycleSupport
// React to media buttons.
Util.registerMediaButtonEventReceiver(context, true);
// Register the handler for outside intents.
IntentFilter commandFilter = new IntentFilter();
commandFilter.addAction(Constants.CMD_PLAY);
commandFilter.addAction(Constants.CMD_TOGGLEPAUSE);
commandFilter.addAction(Constants.CMD_PAUSE);
commandFilter.addAction(Constants.CMD_STOP);
commandFilter.addAction(Constants.CMD_PREVIOUS);
commandFilter.addAction(Constants.CMD_NEXT);
commandFilter.addAction(Constants.CMD_PROCESS_KEYCODE);
context.registerReceiver(intentReceiver, commandFilter);
mediaPlayerController.onCreate();
if (autoPlay) mediaPlayerController.preload();
@ -120,7 +109,6 @@ public class MediaPlayerLifecycleSupport
downloader.getCurrentPlayingIndex(), mediaPlayerController.getPlayerPosition());
mediaPlayerController.clear(false);
context.unregisterReceiver(headsetEventReceiver);
context.unregisterReceiver(intentReceiver);
mediaPlayerController.onDestroy();
created = false;
Log.i(TAG, "LifecycleSupport destroyed");
@ -128,19 +116,33 @@ public class MediaPlayerLifecycleSupport
public void receiveIntent(Intent intent)
{
Log.i(TAG, "Received intent");
if (intent != null && intent.getExtras() != null)
{
KeyEvent event = (KeyEvent) intent.getExtras().get(Intent.EXTRA_KEY_EVENT);
if (event != null)
{
handleKeyEvent(event);
if (intent == null) return;
String intentAction = intent.getAction();
if (intentAction == null || intentAction.isEmpty()) return;
Log.i(TAG, String.format("Received intent: %s", intentAction));
if (intentAction.equals(Constants.CMD_PROCESS_KEYCODE)) {
if (intent.getExtras() != null) {
KeyEvent event = (KeyEvent) intent.getExtras().get(Intent.EXTRA_KEY_EVENT);
if (event != null) {
handleKeyEvent(event);
}
}
}
else
{
handleUltrasonicIntent(intentAction);
}
}
/**
* The Headset Intent Receiver is responsible for resuming playback when a headset is inserted
* and pausing it when it is removed.
* Unfortunately this Intent can't be registered in the AndroidManifest, so it works only
* while Ultrasonic is running.
*/
private void registerHeadsetReceiver() {
// Pause when headset is unplugged.
final SharedPreferences sp = Util.getPreferences(context);
final String spKey = context
.getString(R.string.settings_playback_resume_play_on_headphones_plug);
@ -255,43 +257,53 @@ public class MediaPlayerLifecycleSupport
}
/**
* This receiver manages the intent that could come from other applications.
* This function processes the intent that could come from other applications.
*/
private BroadcastReceiver intentReceiver = new BroadcastReceiver()
private void handleUltrasonicIntent(final String intentAction)
{
@Override
public void onReceive(Context context, Intent intent)
{
String action = intent.getAction();
if (action == null) return;
Log.i(TAG, "intentReceiver.onReceive: " + action);
final boolean isRunning = created;
// If Ultrasonic is not running, do nothing to stop or pause
if (!isRunning && (intentAction.equals(Constants.CMD_PAUSE) ||
intentAction.equals(Constants.CMD_STOP))) return;
switch(action)
{
case Constants.CMD_PLAY:
mediaPlayerController.play();
break;
case Constants.CMD_NEXT:
mediaPlayerController.next();
break;
case Constants.CMD_PREVIOUS:
mediaPlayerController.previous();
break;
case Constants.CMD_TOGGLEPAUSE:
mediaPlayerController.togglePlayPause();
break;
case Constants.CMD_STOP:
// TODO: There is a stop() function, shouldn't we use that?
mediaPlayerController.pause();
mediaPlayerController.seekTo(0);
break;
case Constants.CMD_PAUSE:
mediaPlayerController.pause();
break;
case Constants.CMD_PROCESS_KEYCODE:
receiveIntent(intent);
break;
boolean autoStart = (intentAction.equals(Constants.CMD_PLAY) ||
intentAction.equals(Constants.CMD_RESUME_OR_PLAY) ||
intentAction.equals(Constants.CMD_TOGGLEPAUSE) ||
intentAction.equals(Constants.CMD_PREVIOUS) ||
intentAction.equals(Constants.CMD_NEXT));
// We can receive intents when everything is stopped, so we need to start
onCreate(autoStart, new Runnable() {
@Override
public void run() {
switch(intentAction)
{
case Constants.CMD_PLAY:
mediaPlayerController.play();
break;
case Constants.CMD_RESUME_OR_PLAY:
// If Ultrasonic wasn't running, the autoStart is enough to resume, no need to call anything
if (isRunning) mediaPlayerController.resumeOrPlay();
break;
case Constants.CMD_NEXT:
mediaPlayerController.next();
break;
case Constants.CMD_PREVIOUS:
mediaPlayerController.previous();
break;
case Constants.CMD_TOGGLEPAUSE:
mediaPlayerController.togglePlayPause();
break;
case Constants.CMD_STOP:
// TODO: There is a stop() function, shouldn't we use that?
mediaPlayerController.pause();
mediaPlayerController.seekTo(0);
break;
case Constants.CMD_PAUSE:
mediaPlayerController.pause();
break;
}
}
}
};
});
}
}

View File

@ -361,6 +361,18 @@ public class MediaPlayerService extends Service
}
}
public synchronized void resumeOrPlay()
{
if (localMediaPlayer.playerState == PAUSED || localMediaPlayer.playerState == COMPLETED || localMediaPlayer.playerState == STOPPED)
{
start();
}
else if (localMediaPlayer.playerState == IDLE)
{
play();
}
}
/**
* Plays either the current song (resume) or the first/next one in queue.
*/

View File

@ -63,6 +63,7 @@ public final class Constants
// Names for Intent Actions
public static final String CMD_PROCESS_KEYCODE = "org.moire.ultrasonic.CMD_PROCESS_KEYCODE";
public static final String CMD_PLAY = "org.moire.ultrasonic.CMD_PLAY";
public static final String CMD_RESUME_OR_PLAY = "org.moire.ultrasonic.CMD_RESUME_OR_PLAY";
public static final String CMD_TOGGLEPAUSE = "org.moire.ultrasonic.CMD_TOGGLEPAUSE";
public static final String CMD_PAUSE = "org.moire.ultrasonic.CMD_PAUSE";
public static final String CMD_STOP = "org.moire.ultrasonic.CMD_STOP";
@ -131,6 +132,12 @@ public final class Constants
public static final String PREFERENCES_KEY_USE_FIVE_STAR_RATING = "use_five_star_rating";
public static final String PREFERENCES_KEY_CATEGORY_NOTIFICATIONS = "notificationsCategory";
public static final String PREFERENCES_KEY_FIRST_RUN_EXECUTED = "firstRunExecuted";
public static final String PREFERENCES_KEY_RESUME_ON_BLUETOOTH_DEVICE = "resumeOnBluetoothDevice";
public static final String PREFERENCES_KEY_PAUSE_ON_BLUETOOTH_DEVICE = "pauseOnBluetoothDevice";
public static final int PREFERENCE_VALUE_ALL = 0;
public static final int PREFERENCE_VALUE_A2DP = 1;
public static final int PREFERENCE_VALUE_DISABLED = 2;
// Number of free trial days for non-licensed servers.
public static final int FREE_TRIAL_DAYS = 30;

View File

@ -1447,4 +1447,16 @@ public class Util
editor.apply();
return true;
}
public static int getResumeOnBluetoothDevice(Context context)
{
SharedPreferences preferences = getPreferences(context);
return preferences.getInt(Constants.PREFERENCES_KEY_RESUME_ON_BLUETOOTH_DEVICE, Constants.PREFERENCE_VALUE_DISABLED);
}
public static int getPauseOnBluetoothDevice(Context context)
{
SharedPreferences preferences = getPreferences(context);
return preferences.getInt(Constants.PREFERENCES_KEY_PAUSE_ON_BLUETOOTH_DEVICE, Constants.PREFERENCE_VALUE_A2DP);
}
}

View File

@ -396,6 +396,12 @@
<string name="settings.image_loader_concurrency_12">12</string>
<string name="albumArt">Album Cover</string>
<string name="common_multiple_years">Mehrere Jahre</string>
<string name="settings.playback.resume_on_bluetooth_device">Resume when a Bluetooth device is connected</string>
<string name="settings.playback.pause_on_bluetooth_device">Pause when a Bluetooth device is disconnected</string>
<string name="settings.playback.bluetooth_all">All Bluetooth devices</string>
<string name="settings.playback.bluetooth_a2dp">Only audio (A2DP) devices</string>
<string name="settings.playback.bluetooth_disabled">Disabled</string>
<string name="permissions.access_error">Ultrasonic can\'t access the music file cache. Cache location was reset to the default path.</string>
<string name="permissions.message_box_title">Warning</string>
<string name="permissions.permission_missing">Ultrasonic needs read/write permission to the music cache directory. Cache directory was reset to its default value.</string>

View File

@ -397,6 +397,12 @@
<string name="settings.image_loader_concurrency_12">12</string>
<string name="albumArt">Caratula del Álbum</string>
<string name="common_multiple_years">Múltiples años</string>
<string name="settings.playback.resume_on_bluetooth_device">Reanudar al conectar un dispositivo Bluetooth</string>
<string name="settings.playback.pause_on_bluetooth_device">Pausar al desconectar un dispositivo Bluetooth</string>
<string name="settings.playback.bluetooth_all">Todos los dispositivos Bluetooth</string>
<string name="settings.playback.bluetooth_a2dp">Solo dispositivos de audio (A2DP)</string>
<string name="settings.playback.bluetooth_disabled">Deshabilitado</string>
<string name="permissions.access_error">Ultrasonic no puede acceder a la caché de los ficheros de música. La ubicación de la caché se restableció a la ruta predeterminada.</string>
<string name="permissions.message_box_title">Atención</string>
<string name="permissions.permission_missing">Ultrasonic necesita permiso de lectura / escritura para el directorio caché de música. El directorio caché se restableció a su valor predeterminado.</string>

View File

@ -397,6 +397,12 @@
<string name="settings.image_loader_concurrency_12">12</string>
<string name="albumArt">albumArt</string>
<string name="common_multiple_years">Multiple Years</string>
<string name="settings.playback.resume_on_bluetooth_device">Resume when a Bluetooth device is connected</string>
<string name="settings.playback.pause_on_bluetooth_device">Pause when a Bluetooth device is disconnected</string>
<string name="settings.playback.bluetooth_all">All Bluetooth devices</string>
<string name="settings.playback.bluetooth_a2dp">Only audio (A2DP) devices</string>
<string name="settings.playback.bluetooth_disabled">Disabled</string>
<string name="permissions.access_error">Ultrasonic can\'t access the music file cache. Cache location was reset to the default path.</string>
<string name="permissions.message_box_title">Warning</string>
<string name="permissions.permission_missing">Ultrasonic needs read/write permission to the music cache directory. Cache directory was reset to its default value.</string>

View File

@ -229,7 +229,7 @@
<string name="settings.max_bitrate_unlimited">Korlátlan</string>
<string name="settings.max_bitrate_wifi">Max. bitráta - Wi-Fi kapcsolat</string>
<string name="settings.max_songs">Dalok max. találati száma</string>
<string name="settings.media_button_summary">Telefon irányítása a bluetooth eszköz, vagy a fülhallgató vezérlőgombjaival.</string>
<string name="settings.media_button_summary">Telefon irányítása a Bluetooth eszköz, vagy a fülhallgató vezérlőgombjaival.</string>
<string name="settings.media_button_title">Média vezérlőgombok</string>
<string name="settings.network_timeout">Hálózati időtúllépés</string>
<string name="settings.network_timeout_105000">105 másodperc</string>
@ -396,7 +396,13 @@
<string name="settings.image_loader_concurrency_11">11</string>
<string name="settings.image_loader_concurrency_12">12</string>
<string name="albumArt">albumArt</string>
<string name="common_multiple_years">Multiple Years</string>
<string name="common_multiple_years">Több év</string>
<string name="settings.playback.resume_on_bluetooth_device">Folytatás Bluetooth eszköz csatlakozásakor</string>
<string name="settings.playback.pause_on_bluetooth_device">Szünet Bluetooth eszköz kikapcsolásakor</string>
<string name="settings.playback.bluetooth_all">Minden Bluetooth eszköz</string>
<string name="settings.playback.bluetooth_a2dp">Csak audio (A2DP) eszközök</string>
<string name="settings.playback.bluetooth_disabled">Kikapcsolva</string>
<string name="permissions.access_error">Az Ultrasonic nem éri el a zenei fájl gyorsítótárat. A gyorsítótár helye visszaállítva az alapbeállításra.</string>
<string name="permissions.message_box_title">Figyelem</string>
<string name="permissions.permission_missing">Az Ultrasonic működéséhez írás/olvasás hozzáférés szükséges a zenei fájl gyorsítótárhoz. A gyorsítótár helye visszaállítva az alapbeállításra.</string>

View File

@ -397,6 +397,12 @@
<string name="settings.image_loader_concurrency_12">12</string>
<string name="albumArt">Albumhoes</string>
<string name="common_multiple_years">Meerdere jaren</string>
<string name="settings.playback.resume_on_bluetooth_device">Resume when a Bluetooth device is connected</string>
<string name="settings.playback.pause_on_bluetooth_device">Pause when a Bluetooth device is disconnected</string>
<string name="settings.playback.bluetooth_all">All Bluetooth devices</string>
<string name="settings.playback.bluetooth_a2dp">Only audio (A2DP) devices</string>
<string name="settings.playback.bluetooth_disabled">Disabled</string>
<string name="permissions.access_error">Ultrasonic can\'t access the music file cache. Cache location was reset to the default path.</string>
<string name="permissions.message_box_title">Warning</string>
<string name="permissions.permission_missing">Ultrasonic needs read/write permission to the music cache directory. Cache directory was reset to its default value.</string>

View File

@ -397,6 +397,12 @@ ponieważ api Subsonic nie wspiera nowego sposobu autoryzacji dla użytkowników
<string name="settings.image_loader_concurrency_12">12</string>
<string name="albumArt">Okładka</string>
<string name="common_multiple_years">Z różnych lat</string>
<string name="settings.playback.resume_on_bluetooth_device">Resume when a Bluetooth device is connected</string>
<string name="settings.playback.pause_on_bluetooth_device">Pause when a Bluetooth device is disconnected</string>
<string name="settings.playback.bluetooth_all">All Bluetooth devices</string>
<string name="settings.playback.bluetooth_a2dp">Only audio (A2DP) devices</string>
<string name="settings.playback.bluetooth_disabled">Disabled</string>
<string name="permissions.access_error">Ultrasonic can\'t access the music file cache. Cache location was reset to the default path.</string>
<string name="permissions.message_box_title">Warning</string>
<string name="permissions.permission_missing">Ultrasonic needs read/write permission to the music cache directory. Cache directory was reset to its default value.</string>

View File

@ -397,6 +397,12 @@
<string name="settings.image_loader_concurrency_12">12</string>
<string name="albumArt">albumArt</string>
<string name="common_multiple_years">Múltiplos Anos</string>
<string name="settings.playback.resume_on_bluetooth_device">Resume when a Bluetooth device is connected</string>
<string name="settings.playback.pause_on_bluetooth_device">Pause when a Bluetooth device is disconnected</string>
<string name="settings.playback.bluetooth_all">All Bluetooth devices</string>
<string name="settings.playback.bluetooth_a2dp">Only audio (A2DP) devices</string>
<string name="settings.playback.bluetooth_disabled">Disabled</string>
<string name="permissions.access_error">Ultrasonic can\'t access the music file cache. Cache location was reset to the default path.</string>
<string name="permissions.message_box_title">Warning</string>
<string name="permissions.permission_missing">Ultrasonic needs read/write permission to the music cache directory. Cache directory was reset to its default value.</string>

View File

@ -397,6 +397,12 @@
<string name="settings.image_loader_concurrency_12">12</string>
<string name="albumArt">albumArt</string>
<string name="common_multiple_years">Múltiplos Anos</string>
<string name="settings.playback.resume_on_bluetooth_device">Resume when a Bluetooth device is connected</string>
<string name="settings.playback.pause_on_bluetooth_device">Pause when a Bluetooth device is disconnected</string>
<string name="settings.playback.bluetooth_all">All Bluetooth devices</string>
<string name="settings.playback.bluetooth_a2dp">Only audio (A2DP) devices</string>
<string name="settings.playback.bluetooth_disabled">Disabled</string>
<string name="permissions.access_error">Ultrasonic can\'t access the music file cache. Cache location was reset to the default path.</string>
<string name="permissions.message_box_title">Warning</string>
<string name="permissions.permission_missing">Ultrasonic needs read/write permission to the music cache directory. Cache directory was reset to its default value.</string>

View File

@ -291,5 +291,10 @@
<item>11</item>
<item>12</item>
</string-array>
<string-array name="bluetoothDeviceSettingNames" translatable="false">
<item>@string/settings.playback.bluetooth_all</item>
<item>@string/settings.playback.bluetooth_a2dp</item>
<item>@string/settings.playback.bluetooth_disabled</item>
</string-array>
</resources>

View File

@ -254,7 +254,7 @@
<string name="settings.preload_5">5 songs</string>
<string name="settings.preload_unlimited">Unlimited</string>
<string name="settings.playback.resume_play_on_headphones_plug.title">Resume on headphones insertion</string>
<string name="settings.playback.resume_play_on_headphones_plug.summary">App will resume paused playback on headphones insertion into device.</string>
<string name="settings.playback.resume_play_on_headphones_plug.summary">App will resume paused playback on wired headphones insertion into device.</string>
<string name="settings.screen_lit_summary">Keeping the screen on while downloading improves download speed.</string>
<string name="settings.screen_lit_title">Keep Screen On</string>
<string name="settings.scrobble_summary">Remember to set up your Last.fm user and password on the Subsonic server</string>
@ -400,6 +400,11 @@
<string name="albumArt">albumArt</string>
<string name="common_multiple_years">Multiple Years</string>
<string name="settings.server_address_unset" translatable="false">http://example.com</string>
<string name="settings.playback.resume_on_bluetooth_device">Resume when a Bluetooth device is connected</string>
<string name="settings.playback.pause_on_bluetooth_device">Pause when a Bluetooth device is disconnected</string>
<string name="settings.playback.bluetooth_all">All Bluetooth devices</string>
<string name="settings.playback.bluetooth_a2dp">Only audio (A2DP) devices</string>
<string name="settings.playback.bluetooth_disabled">Disabled</string>
<string name="permissions.access_error">Ultrasonic can\'t access the music file cache. Cache location was reset to the default path.</string>
<string name="permissions.message_box_title">Warning</string>

View File

@ -106,6 +106,12 @@
a:title="@string/settings.playback.resume_play_on_headphones_plug.title"
a:summary="@string/settings.playback.resume_play_on_headphones_plug.summary"
/>
<Preference
a:key="resumeOnBluetoothDevice"
a:title="@string/settings.playback.resume_on_bluetooth_device"/>
<Preference
a:key="pauseOnBluetoothDevice"
a:title="@string/settings.playback.pause_on_bluetooth_device"/>
</PreferenceCategory>
<PreferenceCategory
a:title="@string/settings.notifications_title"