mirror of
https://github.com/clangen/musikcube.git
synced 2025-01-29 21:32:41 +00:00
- fixed core library external id generation (use uuids instead of an
auto-incrementing int) - ensure the web server plugin sends 'external_id' to clients - allow user to configure streaming disk cache size on the android client - some other android client ui cleanup
This commit is contained in:
parent
646742214d
commit
f8e201cb80
@ -76,6 +76,7 @@ namespace key {
|
||||
static const std::string playing_current_time = "playing_current_time";
|
||||
static const std::string playing_track = "playing_track";
|
||||
static const std::string title = "title";
|
||||
static const std::string external_id = "external_id";
|
||||
static const std::string filename = "filename";
|
||||
static const std::string artist = "artist";
|
||||
static const std::string album = "album";
|
||||
|
@ -288,15 +288,16 @@ int HttpServer::HandleRequest(
|
||||
if (parts.size() > 0) {
|
||||
if (parts.at(0) == fragment::audio && parts.size() == 3) {
|
||||
IRetainedTrack* track = nullptr;
|
||||
bool byExternalId = (parts.at(1) == fragment::external_id);
|
||||
|
||||
if (parts.at(1) == fragment::id) {
|
||||
uint64_t id = std::stoull(urlDecode(parts.at(2)));
|
||||
track = server->context.dataProvider->QueryTrackById(id);
|
||||
}
|
||||
else if (parts.at(1) == fragment::external_id) {
|
||||
if (byExternalId) {
|
||||
std::string externalId = urlDecode(parts.at(2));
|
||||
track = server->context.dataProvider->QueryTrackByExternalId(externalId.c_str());
|
||||
}
|
||||
else if (parts.at(1) == fragment::id) {
|
||||
uint64_t id = std::stoull(urlDecode(parts.at(2)));
|
||||
track = server->context.dataProvider->QueryTrackById(id);
|
||||
}
|
||||
|
||||
if (track) {
|
||||
std::string filename = GetMetadataString(track, key::filename);
|
||||
@ -360,6 +361,13 @@ int HttpServer::HandleRequest(
|
||||
MHD_add_response_header(response, "Accept-Ranges", "bytes");
|
||||
}
|
||||
|
||||
if (byExternalId) {
|
||||
/* if we're using an on-demand transcoder, ensure the client does not cache the
|
||||
result because we have to guess the content length. */
|
||||
std::string value = isOnDemandTranscoder ? "no-cache" : "public, max-age=31536000";
|
||||
MHD_add_response_header(response, "Cache-Control", value.c_str());
|
||||
}
|
||||
|
||||
MHD_add_response_header(response, "Content-Type", contentType(filename).c_str());
|
||||
MHD_add_response_header(response, "Server", "musikcube websocket_remote");
|
||||
|
||||
|
@ -727,6 +727,7 @@ void WebSocketServer::BroadcastPlayQueueChanged() {
|
||||
json WebSocketServer::WebSocketServer::ReadTrackMetadata(IRetainedTrack* track) {
|
||||
return {
|
||||
{ key::id, track->GetId() },
|
||||
{ key::external_id, GetMetadataString(track, key::external_id) },
|
||||
{ key::title, GetMetadataString(track, key::title) },
|
||||
{ key::album, GetMetadataString(track, key::album) },
|
||||
{ key::album_id, track->GetInt64(key::album_id.c_str()) },
|
||||
|
@ -65,7 +65,6 @@
|
||||
static const std::string TAG = "Indexer";
|
||||
static const int MAX_THREADS = 2;
|
||||
static const size_t TRANSACTION_INTERVAL = 300;
|
||||
static std::atomic<int64_t> nextExternalId;
|
||||
|
||||
using namespace musik::core;
|
||||
using namespace musik::core::sdk;
|
||||
@ -246,16 +245,6 @@ void Indexer::Synchronize(const SyncContext& context, boost::asio::io_service* i
|
||||
|
||||
/* process local files */
|
||||
if (type == SyncType::All || type == SyncType::Local) {
|
||||
/* resolve our next external id. we do this once before starting so
|
||||
we don't need to make a bunch of additional queries while indexing. */
|
||||
{
|
||||
db::Statement stmt("SELECT MAX(id) FROM tracks", this->dbConnection);
|
||||
if (stmt.Step() == db::Row) {
|
||||
auto id = std::max((int64_t) 1, stmt.ColumnInt64(0));
|
||||
nextExternalId.store(id);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> paths;
|
||||
std::vector<int64_t> pathIds;
|
||||
|
||||
@ -376,9 +365,6 @@ void Indexer::ReadMetadataFromFile(
|
||||
|
||||
/* write it to the db, if read successfully */
|
||||
if (saveToDb) {
|
||||
std::string externalId = "local://" + std::to_string(nextExternalId.fetch_add(1));
|
||||
track.SetValue("external_id", externalId.c_str());
|
||||
|
||||
track.SetValue("path_id", pathId.c_str());
|
||||
track.Save(this->dbConnection, this->libraryPath);
|
||||
|
||||
|
@ -50,7 +50,7 @@ using namespace musik::core;
|
||||
using namespace musik::core::library;
|
||||
using namespace musik::core::runtime;
|
||||
|
||||
#define DATABASE_VERSION 3
|
||||
#define DATABASE_VERSION 4
|
||||
#define VERBOSE_LOGGING 0
|
||||
#define MESSAGE_QUERY_COMPLETED 5000
|
||||
|
||||
@ -299,6 +299,11 @@ static void upgradeV2ToV3(db::Connection& db) {
|
||||
scheduleSyncDueToDbUpgrade = true;
|
||||
}
|
||||
|
||||
static void upgradeV3ToV4(db::Connection& db) {
|
||||
db.Execute("DELETE from tracks");
|
||||
scheduleSyncDueToDbUpgrade = true;
|
||||
}
|
||||
|
||||
static void setVersion(db::Connection& db, int version) {
|
||||
db.Execute("DELETE FROM version");
|
||||
|
||||
@ -481,6 +486,10 @@ void LocalLibrary::CreateDatabase(db::Connection &db){
|
||||
upgradeV2ToV3(db);
|
||||
}
|
||||
|
||||
if (lastVersion >= 1 && lastVersion < 4) {
|
||||
upgradeV3ToV4(db);
|
||||
}
|
||||
|
||||
/* ensure our version is set correctly */
|
||||
setVersion(db, DATABASE_VERSION);
|
||||
|
||||
|
@ -43,7 +43,9 @@
|
||||
#include <core/io/DataStreamFactory.h>
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
#include <boost/uuid/uuid.hpp>
|
||||
#include <boost/uuid/uuid_generators.hpp>
|
||||
#include <boost/uuid/uuid_io.hpp>
|
||||
#include <unordered_map>
|
||||
|
||||
using namespace musik::core;
|
||||
@ -60,6 +62,7 @@ using namespace musik::core;
|
||||
|
||||
static std::mutex trackWriteLock;
|
||||
static std::unordered_map<std::string, int64_t> metadataIdCache;
|
||||
static auto uuids = boost::uuids::random_generator();
|
||||
|
||||
void IndexerTrack::ResetIdCache() {
|
||||
metadataIdCache.clear();
|
||||
@ -615,6 +618,10 @@ bool IndexerTrack::Save(db::Connection &dbConnection, std::string libraryDirecto
|
||||
this->SetValue("album_artist", this->GetValue("artist").c_str());
|
||||
}
|
||||
|
||||
if (this->GetValue("external_id") == "") {
|
||||
this->SetValue("external_id", boost::uuids::to_string(uuids()).c_str());
|
||||
}
|
||||
|
||||
/* remove existing relations -- we're going to update them with fresh data */
|
||||
|
||||
if (this->id != 0) {
|
||||
|
@ -28,6 +28,8 @@ import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import io.casey.musikcube.remote.Application;
|
||||
import io.casey.musikcube.remote.util.NetworkUtil;
|
||||
@ -35,8 +37,21 @@ import okhttp3.Cache;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
public class ExoPlayerWrapper extends PlayerWrapper {
|
||||
private static OkHttpClient audioStreamClient = null;
|
||||
private static boolean certValidationDisabled = false;
|
||||
private static OkHttpClient audioStreamHttpClient = null;
|
||||
|
||||
private static final long BYTES_PER_MEGABYTE = 1048576L;
|
||||
private static final long BYTES_PER_GIGABYTE = 1073741824L;
|
||||
private static final Map<Integer, Long> CACHE_SETTING_TO_BYTES;
|
||||
|
||||
static {
|
||||
CACHE_SETTING_TO_BYTES = new HashMap<>();
|
||||
CACHE_SETTING_TO_BYTES.put(0, BYTES_PER_MEGABYTE * 32);
|
||||
CACHE_SETTING_TO_BYTES.put(1, BYTES_PER_GIGABYTE / 2);
|
||||
CACHE_SETTING_TO_BYTES.put(2, BYTES_PER_GIGABYTE);
|
||||
CACHE_SETTING_TO_BYTES.put(3, BYTES_PER_GIGABYTE * 2);
|
||||
CACHE_SETTING_TO_BYTES.put(4, BYTES_PER_GIGABYTE * 3);
|
||||
CACHE_SETTING_TO_BYTES.put(5, BYTES_PER_GIGABYTE * 4);
|
||||
}
|
||||
|
||||
private DefaultBandwidthMeter bandwidth;
|
||||
private DataSource.Factory datasources;
|
||||
@ -47,6 +62,12 @@ public class ExoPlayerWrapper extends PlayerWrapper {
|
||||
private Context context;
|
||||
private SharedPreferences prefs;
|
||||
|
||||
public static void invalidateSettings() {
|
||||
synchronized (ExoPlayerWrapper.class) {
|
||||
audioStreamHttpClient = null;
|
||||
}
|
||||
}
|
||||
|
||||
public ExoPlayerWrapper() {
|
||||
this.context = Application.getInstance();
|
||||
this.prefs = context.getSharedPreferences("prefs", Context.MODE_PRIVATE);
|
||||
@ -56,37 +77,34 @@ public class ExoPlayerWrapper extends PlayerWrapper {
|
||||
this.player = ExoPlayerFactory.newSimpleInstance(this.context, trackSelector);
|
||||
this.extractors = new DefaultExtractorsFactory();
|
||||
this.player.addListener(eventListener);
|
||||
|
||||
synchronized (ExoPlayerWrapper.class) {
|
||||
final boolean disabled = this.prefs.getBoolean("cert_validation_disabled", false);
|
||||
if (disabled != certValidationDisabled) {
|
||||
audioStreamClient = null;
|
||||
certValidationDisabled = disabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initDataSourceFactory(final String uri) {
|
||||
private void initHttpClient(final String uri) {
|
||||
final Context context = Application.getInstance();
|
||||
|
||||
synchronized (ExoPlayerWrapper.class) {
|
||||
if (audioStreamClient == null) {
|
||||
if (audioStreamHttpClient == null) {
|
||||
final File path = new File(context.getExternalCacheDir(), "audio");
|
||||
|
||||
OkHttpClient.Builder builder = new OkHttpClient.Builder()
|
||||
.cache(new Cache(path, 1048576 * 256)); /* 256 meg cache */
|
||||
int diskCacheIndex = this.prefs.getInt("disk_cache_size_index", 0);
|
||||
if (diskCacheIndex < 0 || diskCacheIndex > CACHE_SETTING_TO_BYTES.size()) {
|
||||
diskCacheIndex = 0;
|
||||
}
|
||||
|
||||
if (certValidationDisabled) {
|
||||
OkHttpClient.Builder builder = new OkHttpClient.Builder()
|
||||
.cache(new Cache(path, CACHE_SETTING_TO_BYTES.get(diskCacheIndex)));
|
||||
|
||||
if (this.prefs.getBoolean("cert_validation_disabled", false)) {
|
||||
NetworkUtil.disableCertificateValidation(builder);
|
||||
}
|
||||
|
||||
audioStreamClient = builder.build();
|
||||
audioStreamHttpClient = builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
if (uri.startsWith("http")) {
|
||||
this.datasources = new OkHttpDataSourceFactory(
|
||||
audioStreamClient,
|
||||
audioStreamHttpClient,
|
||||
Util.getUserAgent(context, "musikdroid"),
|
||||
bandwidth);
|
||||
}
|
||||
@ -98,7 +116,7 @@ public class ExoPlayerWrapper extends PlayerWrapper {
|
||||
|
||||
@Override
|
||||
public void play(String uri) {
|
||||
initDataSourceFactory(uri);
|
||||
initHttpClient(uri);
|
||||
this.source = new ExtractorMediaSource(Uri.parse(uri), datasources, extractors, null, null);
|
||||
this.player.setPlayWhenReady(true);
|
||||
this.player.prepare(this.source);
|
||||
@ -108,7 +126,7 @@ public class ExoPlayerWrapper extends PlayerWrapper {
|
||||
|
||||
@Override
|
||||
public void prefetch(String uri) {
|
||||
initDataSourceFactory(uri);
|
||||
initHttpClient(uri);
|
||||
this.prefetch = true;
|
||||
this.source = new ExtractorMediaSource(Uri.parse(uri), datasources, extractors, null, null);
|
||||
this.player.setPlayWhenReady(false);
|
||||
|
@ -13,6 +13,7 @@ import android.util.Log;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
@ -535,8 +536,8 @@ public class StreamingPlaybackService implements PlaybackService {
|
||||
|
||||
private String getUri(final JSONObject track) {
|
||||
if (track != null) {
|
||||
final long trackId = track.optLong("id", -1);
|
||||
if (trackId != -1) {
|
||||
final String externalId = track.optString("external_id", "");
|
||||
if (Strings.notEmpty(externalId)) {
|
||||
final String protocol = prefs.getBoolean("ssl_enabled", false) ? "https" : "http";
|
||||
|
||||
/* transcoding bitrate, if selected by the user */
|
||||
@ -553,11 +554,11 @@ public class StreamingPlaybackService implements PlaybackService {
|
||||
|
||||
return String.format(
|
||||
Locale.ENGLISH,
|
||||
"%s://%s:%d/audio/id/%d%s",
|
||||
"%s://%s:%d/audio/external_id/%s%s",
|
||||
protocol,
|
||||
prefs.getString("address", "192.168.1.100"),
|
||||
prefs.getInt("http_port", 7906),
|
||||
trackId,
|
||||
URLEncoder.encode(externalId),
|
||||
bitrateQueryParam);
|
||||
}
|
||||
}
|
||||
|
@ -10,8 +10,10 @@ import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
@ -20,6 +22,7 @@ import android.widget.Spinner;
|
||||
import java.util.Locale;
|
||||
|
||||
import io.casey.musikcube.remote.R;
|
||||
import io.casey.musikcube.remote.playback.ExoPlayerWrapper;
|
||||
import io.casey.musikcube.remote.playback.MediaPlayerWrapper;
|
||||
import io.casey.musikcube.remote.playback.PlaybackServiceFactory;
|
||||
import io.casey.musikcube.remote.ui.util.Views;
|
||||
@ -29,8 +32,9 @@ public class SettingsActivity extends AppCompatActivity {
|
||||
private EditText addressText, portText, httpPortText, passwordText;
|
||||
private CheckBox albumArtCheckbox, messageCompressionCheckbox, softwareVolume;
|
||||
private CheckBox sslCheckbox, certCheckbox;
|
||||
private Spinner playbackModeSpinner, bitrateSpinner;
|
||||
private Spinner playbackModeSpinner, bitrateSpinner, cacheSpinner;
|
||||
private SharedPreferences prefs;
|
||||
private boolean wasStreaming;
|
||||
|
||||
public static Intent getStartIntent(final Context context) {
|
||||
return new Intent(context, SettingsActivity.class);
|
||||
@ -42,16 +46,27 @@ public class SettingsActivity extends AppCompatActivity {
|
||||
prefs = this.getSharedPreferences("prefs", MODE_PRIVATE);
|
||||
setContentView(R.layout.activity_settings);
|
||||
setTitle(R.string.settings_title);
|
||||
wasStreaming = isStreamingEnabled();
|
||||
bindEventListeners();
|
||||
rebindUi();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.settings_menu, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
else if (item.getItemId() == R.id.action_save) {
|
||||
save();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
@ -76,6 +91,13 @@ public class SettingsActivity extends AppCompatActivity {
|
||||
bitrateSpinner.setAdapter(bitrates);
|
||||
bitrateSpinner.setSelection(prefs.getInt("transcoder_bitrate_index", 0));
|
||||
|
||||
final ArrayAdapter<CharSequence> cacheSizes = ArrayAdapter.createFromResource(
|
||||
this, R.array.disk_cache_array, android.R.layout.simple_spinner_item);
|
||||
|
||||
cacheSizes.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
cacheSpinner.setAdapter(cacheSizes);
|
||||
cacheSpinner.setSelection(prefs.getInt("disk_cache_size_index", 0));
|
||||
|
||||
this.albumArtCheckbox.setChecked(this.prefs.getBoolean("album_art_enabled", true));
|
||||
this.messageCompressionCheckbox.setChecked(this.prefs.getBoolean("message_compression_enabled", true));
|
||||
this.softwareVolume.setChecked(this.prefs.getBoolean("software_volume", false));
|
||||
@ -137,43 +159,58 @@ public class SettingsActivity extends AppCompatActivity {
|
||||
this.softwareVolume = (CheckBox) findViewById(R.id.software_volume);
|
||||
this.playbackModeSpinner = (Spinner) findViewById(R.id.playback_mode_spinner);
|
||||
this.bitrateSpinner = (Spinner) findViewById(R.id.transcoder_bitrate_spinner);
|
||||
this.cacheSpinner = (Spinner) findViewById(R.id.streaming_disk_cache_spinner);
|
||||
this.sslCheckbox = (CheckBox) findViewById(R.id.ssl_checkbox);
|
||||
this.certCheckbox = (CheckBox) findViewById(R.id.cert_validation);
|
||||
|
||||
final boolean wasStreaming = isStreamingEnabled();
|
||||
|
||||
this.findViewById(R.id.button_connect).setOnClickListener((View v) -> {
|
||||
final String addr = addressText.getText().toString();
|
||||
final String port = portText.getText().toString();
|
||||
final String httpPort = httpPortText.getText().toString();
|
||||
final String password = passwordText.getText().toString();
|
||||
|
||||
prefs.edit()
|
||||
.putString("address", addr)
|
||||
.putInt("port", (port.length() > 0) ? Integer.valueOf(port) : 0)
|
||||
.putInt("http_port", (httpPort.length() > 0) ? Integer.valueOf(httpPort) : 0)
|
||||
.putString("password", password)
|
||||
.putBoolean("album_art_enabled", albumArtCheckbox.isChecked())
|
||||
.putBoolean("message_compression_enabled", messageCompressionCheckbox.isChecked())
|
||||
.putBoolean("streaming_playback", isStreamingSelected())
|
||||
.putBoolean("software_volume", softwareVolume.isChecked())
|
||||
.putBoolean("ssl_enabled", sslCheckbox.isChecked())
|
||||
.putBoolean("cert_validation_disabled", certCheckbox.isChecked())
|
||||
.putInt("transcoder_bitrate_index", bitrateSpinner.getSelectedItemPosition())
|
||||
.apply();
|
||||
|
||||
if (!softwareVolume.isChecked()) {
|
||||
MediaPlayerWrapper.setGlobalVolume(1.0f);
|
||||
this.playbackModeSpinner.setOnItemSelectedListener(new Spinner.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> adapterView, View view, int selectedIndex, long l) {
|
||||
final boolean streaming = (selectedIndex == 1);
|
||||
bitrateSpinner.setEnabled(streaming);
|
||||
cacheSpinner.setEnabled(streaming);
|
||||
}
|
||||
|
||||
if (wasStreaming && !isStreamingEnabled()) {
|
||||
PlaybackServiceFactory.streaming(this).stop();
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> adapterView) {
|
||||
|
||||
}
|
||||
|
||||
WebSocketService.getInstance(this).disconnect();
|
||||
|
||||
finish();
|
||||
});
|
||||
}
|
||||
|
||||
private void save() {
|
||||
final String addr = addressText.getText().toString();
|
||||
final String port = portText.getText().toString();
|
||||
final String httpPort = httpPortText.getText().toString();
|
||||
final String password = passwordText.getText().toString();
|
||||
|
||||
prefs.edit()
|
||||
.putString("address", addr)
|
||||
.putInt("port", (port.length() > 0) ? Integer.valueOf(port) : 0)
|
||||
.putInt("http_port", (httpPort.length() > 0) ? Integer.valueOf(httpPort) : 0)
|
||||
.putString("password", password)
|
||||
.putBoolean("album_art_enabled", albumArtCheckbox.isChecked())
|
||||
.putBoolean("message_compression_enabled", messageCompressionCheckbox.isChecked())
|
||||
.putBoolean("streaming_playback", isStreamingSelected())
|
||||
.putBoolean("software_volume", softwareVolume.isChecked())
|
||||
.putBoolean("ssl_enabled", sslCheckbox.isChecked())
|
||||
.putBoolean("cert_validation_disabled", certCheckbox.isChecked())
|
||||
.putInt("transcoder_bitrate_index", bitrateSpinner.getSelectedItemPosition())
|
||||
.putInt("disk_cache_size_index", cacheSpinner.getSelectedItemPosition())
|
||||
.apply();
|
||||
|
||||
if (!softwareVolume.isChecked()) {
|
||||
MediaPlayerWrapper.setGlobalVolume(1.0f);
|
||||
}
|
||||
|
||||
if (wasStreaming && !isStreamingEnabled()) {
|
||||
PlaybackServiceFactory.streaming(this).stop();
|
||||
}
|
||||
|
||||
ExoPlayerWrapper.invalidateSettings();
|
||||
WebSocketService.getInstance(this).disconnect();
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
public static class SslAlertDialog extends DialogFragment {
|
||||
|
@ -1,192 +1,193 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
android:layout_height="wrap_content"
|
||||
android:fillViewport="true">
|
||||
|
||||
<ScrollView
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textColor="@color/theme_foreground"
|
||||
android:text="@string/edit_connection_info"/>
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp">
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginLeft="24dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
<EditText
|
||||
android:id="@+id/address"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textColor="@color/theme_foreground"
|
||||
android:text="@string/edit_connection_info"/>
|
||||
android:maxLines="1"
|
||||
android:hint="@string/edit_connection_hostname"
|
||||
android:inputType="textNoSuggestions" />
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:layout_marginLeft="24dp">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/port"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginLeft="24dp">
|
||||
android:maxLines="1"
|
||||
android:hint="@string/edit_connection_port"
|
||||
android:inputType="number" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/address"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="1"
|
||||
android:hint="@string/edit_connection_hostname"
|
||||
android:inputType="textNoSuggestions" />
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:layout_marginLeft="24dp">
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
<EditText
|
||||
android:id="@+id/http_port"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:layout_marginLeft="24dp">
|
||||
android:maxLines="1"
|
||||
android:hint="@string/edit_connection_http_port"
|
||||
android:inputType="number" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/port"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="1"
|
||||
android:hint="@string/edit_connection_port"
|
||||
android:inputType="number" />
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:layout_marginLeft="24dp">
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
<EditText
|
||||
android:id="@+id/password"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:layout_marginLeft="24dp">
|
||||
android:maxLines="1"
|
||||
android:hint="@string/edit_connection_password"
|
||||
android:inputType="textPassword" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/http_port"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="1"
|
||||
android:hint="@string/edit_connection_http_port"
|
||||
android:inputType="number" />
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
<android.support.v4.widget.Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="8dp"/>
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:layout_marginLeft="24dp">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginRight="8dp"
|
||||
android:text="@string/settings_playback_mode"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/password"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="1"
|
||||
android:hint="@string/edit_connection_password"
|
||||
android:inputType="textPassword" />
|
||||
<Spinner
|
||||
android:id="@+id/playback_mode_spinner"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="24dp"/>
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
<android.support.v4.widget.Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="16dp"/>
|
||||
|
||||
<android.support.v4.widget.Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="8dp"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginRight="8dp"
|
||||
android:text="@string/settings_transcoder_bitrate"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginRight="8dp"
|
||||
android:text="@string/settings_playback_mode"/>
|
||||
<Spinner
|
||||
android:id="@+id/transcoder_bitrate_spinner"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="24dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/playback_mode_spinner"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="24dp"/>
|
||||
<android.support.v4.widget.Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="16dp"/>
|
||||
|
||||
<android.support.v4.widget.Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="16dp"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginRight="8dp"
|
||||
android:text="@string/settings_cache_size"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginRight="8dp"
|
||||
android:text="@string/settings_transcoder_bitrate"/>
|
||||
<Spinner
|
||||
android:id="@+id/streaming_disk_cache_spinner"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="24dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/transcoder_bitrate_spinner"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="24dp"/>
|
||||
<android.support.v4.widget.Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="16dp"/>
|
||||
|
||||
<android.support.v4.widget.Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="16dp"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/settings_advanced"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/settings_general"/>
|
||||
<CheckBox
|
||||
android:id="@+id/album_art_checkbox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/theme_foreground"
|
||||
android:layout_marginLeft="24dp"
|
||||
android:text="@string/settings_enable_album_art"/>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/album_art_checkbox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/theme_foreground"
|
||||
android:layout_marginLeft="24dp"
|
||||
android:text="@string/settings_enable_album_art"/>
|
||||
<CheckBox
|
||||
android:id="@+id/ssl_checkbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/theme_foreground"
|
||||
android:layout_marginLeft="24dp"
|
||||
android:text="@string/settings_use_ssl"/>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/ssl_checkbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/theme_foreground"
|
||||
android:layout_marginLeft="24dp"
|
||||
android:text="@string/settings_use_ssl"/>
|
||||
<CheckBox
|
||||
android:id="@+id/cert_validation"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/theme_foreground"
|
||||
android:layout_marginLeft="24dp"
|
||||
android:visibility="visible"
|
||||
android:text="@string/settings_disable_cert_validation"/>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cert_validation"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/theme_foreground"
|
||||
android:layout_marginLeft="24dp"
|
||||
android:visibility="visible"
|
||||
android:text="@string/settings_disable_cert_validation"/>
|
||||
<CheckBox
|
||||
android:id="@+id/message_compression"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/theme_foreground"
|
||||
android:layout_marginLeft="24dp"
|
||||
android:visibility="gone"
|
||||
android:text="@string/settings_enable_message_compression"/>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/message_compression"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/theme_foreground"
|
||||
android:layout_marginLeft="24dp"
|
||||
android:visibility="gone"
|
||||
android:text="@string/settings_enable_message_compression"/>
|
||||
<CheckBox
|
||||
android:id="@+id/software_volume"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/theme_foreground"
|
||||
android:layout_marginLeft="24dp"
|
||||
android:text="@string/settings_enable_software_volume"/>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/software_volume"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/theme_foreground"
|
||||
android:layout_marginLeft="24dp"
|
||||
android:text="@string/settings_enable_software_volume"/>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<TextView
|
||||
style="@style/BrowseButton"
|
||||
android:id="@+id/button_connect"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="12dp"
|
||||
android:layout_gravity="right"
|
||||
android:text="@string/button_save"/>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
11
src/musikdroid/app/src/main/res/menu/settings_menu.xml
Normal file
11
src/musikdroid/app/src/main/res/menu/settings_menu.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_save"
|
||||
app:showAsAction="always"
|
||||
android:title="@string/button_save"/>
|
||||
|
||||
</menu>
|
11
src/musikdroid/app/src/main/res/values/disk_cache.xml
Normal file
11
src/musikdroid/app/src/main/res/values/disk_cache.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="disk_cache_array">
|
||||
<item>disabled</item>
|
||||
<item>0.5 gb</item>
|
||||
<item>1 gb</item>
|
||||
<item>2 gb</item>
|
||||
<item>3 gb</item>
|
||||
<item>4 gb</item>
|
||||
</string-array>
|
||||
</resources>
|
@ -53,8 +53,9 @@
|
||||
<string name="menu_playlists">playlists</string>
|
||||
<string name="unknown_value"><unknown></string>
|
||||
<string name="settings_playback_mode">playback mode:</string>
|
||||
<string name="settings_transcoder_bitrate">transcoder bitrate:</string>
|
||||
<string name="settings_general">general:</string>
|
||||
<string name="settings_transcoder_bitrate">streaming downsampler bitrate:</string>
|
||||
<string name="settings_cache_size">streaming disk cache size:</string>
|
||||
<string name="settings_advanced">advanced:</string>
|
||||
<string name="settings_use_ssl">use ssl for server connections</string>
|
||||
<string name="settings_enable_album_art">enable album art (uses last.fm)</string>
|
||||
<string name="settings_enable_message_compression">enable message compression</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user