This commit is contained in:
badaix 2016-01-16 12:27:06 +01:00
parent 0453ff4169
commit 8acc2ccd0f
8 changed files with 56 additions and 670 deletions

View file

@ -1,10 +0,0 @@
package de.badaix.snapcast;
import android.support.v4.app.Fragment;
/**
* Created by johannes on 10.01.16.
*/
public abstract class NamedFragment extends Fragment {
public abstract String getName();
}

View file

@ -1,198 +0,0 @@
package de.badaix.snapcast;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
import android.net.Uri;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;
import android.widget.Toast;
/**
* A simple {@link Fragment} subclass.
* Activities that contain this fragment must implement the
* {@link PlayerFragment.OnFragmentInteractionListener} interface
* to handle interaction events.
* Use the {@link PlayerFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class PlayerFragment extends NamedFragment implements View.OnClickListener {
private static final String TAG = "Player";
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";
// TODO: Rename and change types of parameters
private String mParam1;
private String mParam2;
private Button buttonStart;
private Button buttonStop;
private TextView tvHost;
private TextView tvInfo;
private CheckBox cbScreenWakelock;
private String host = "";
private int port = 1704;
private OnFragmentInteractionListener mListener;
public PlayerFragment() {
// Required empty public constructor
}
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment PlayerFragment.
*/
// TODO: Rename and change types and number of parameters
public static PlayerFragment newInstance(String param1, String param2) {
PlayerFragment fragment = new PlayerFragment();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_player, container, false);
buttonStart = (Button) view.findViewById(R.id.buttonStart);
buttonStop = (Button) view.findViewById(R.id.buttonStop);
tvHost = (TextView) view.findViewById(R.id.tvHost);
tvInfo = (TextView) view.findViewById(R.id.tvInfo);
cbScreenWakelock = (CheckBox) view.findViewById(R.id.cbScreenWakelock);
cbScreenWakelock.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
if (b)
getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
else
getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
});
buttonStart.setOnClickListener(this);
buttonStop.setOnClickListener(this);
AudioManager audioManager = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
String rate = audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
String size = audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
tvInfo.setText("Preferred buffer size: " + size + "\nPreferred sample rate: " + rate);
}
return view;
}
// TODO: Rename method, update argument and hook method into UI event
public void onButtonPressed(Uri uri) {
if (mListener != null) {
mListener.onFragmentInteraction(uri);
}
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnFragmentInteractionListener) {
mListener = (OnFragmentInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnFragmentInteractionListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
/**
* This interface must be implemented by activities that contain this
* fragment to allow an interaction in this fragment to be communicated
* to the activity and potentially other fragments contained in that
* activity.
* <p/>
* See the Android Training lesson <a href=
* "http://developer.android.com/training/basics/fragments/communicating.html"
* >Communicating with Other Fragments</a> for more information.
*/
public interface OnFragmentInteractionListener {
// TODO: Update argument type and name
void onFragmentInteraction(Uri uri);
}
@Override
public String getName() {
return "Player";
}
private void start() {
Intent i = new Intent(this.getContext(), SnapclientService.class);
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
i.putExtra(SnapclientService.EXTRA_HOST, host);
i.putExtra(SnapclientService.EXTRA_PORT, port);
getContext().startService(i);
}
private void stop() {
getContext().stopService(new Intent(getContext(), SnapclientService.class));
getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
@Override
public void onClick(View view) {
if (view == buttonStart) {
start();
} else if (view == buttonStop) {
stop();
}
}
public void setServer(final String host, final int port) {
this.host = host;
this.port = port;
}
public void setState(String state) {
tvHost.post(new Runnable() {
@Override
public void run() {
tvHost.setText("Host: " + host + ":" + port);
}
});
}
}

View file

@ -1,286 +0,0 @@
package de.badaix.snapcast;
import android.content.Context;
import android.content.res.AssetManager;
import android.net.Uri;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.support.design.widget.TabLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Vector;
public class SnapcastActivity extends AppCompatActivity implements PlayerFragment.OnFragmentInteractionListener, ZoneFragment.OnFragmentInteractionListener {
private static final String TAG = "Main";
private NsdManager.DiscoveryListener mDiscoveryListener;
private NsdManager mNsdManager = null;
private String host = "";
private int port = 1704;
private PlayerFragment playerFragment;
private ZoneFragment zoneFragment;
/**
* The {@link android.support.v4.view.PagerAdapter} that will provide
* fragments for each of the sections. We use a
* {@link FragmentPagerAdapter} derivative, which will keep every
* loaded fragment in memory. If this becomes too memory intensive, it
* may be best to switch to a
* {@link android.support.v4.app.FragmentStatePagerAdapter}.
*/
private SectionsPagerAdapter mSectionsPagerAdapter;
/**
* The {@link ViewPager} that will host the section contents.
*/
private ViewPager mViewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_snapcast);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// Create the adapter that will return a fragment for each of the three
// primary sections of the activity.
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
playerFragment = new PlayerFragment();
mSectionsPagerAdapter.addFragment(playerFragment);
zoneFragment = new ZoneFragment();
mSectionsPagerAdapter.addFragment(zoneFragment);
// Set up the ViewPager with the sections adapter.
mViewPager = (ViewPager) findViewById(R.id.container);
mViewPager.setAdapter(mSectionsPagerAdapter);
TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
tabLayout.setupWithViewPager(mViewPager);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
copyAssets();
initializeDiscoveryListener();
}
private void copyAssets() {
AssetManager assetManager = getAssets();
String[] files = null;
try {
files = assetManager.list("");
} catch (IOException e) {
Log.e(TAG, "Failed to get asset file list.", e);
}
if (files != null) for (String filename : files) {
InputStream in = null;
OutputStream out = null;
try {
File outFile = new File(getFilesDir(), filename);
// if (outFile.exists() && (outFile.length() == assetManager.openFd(filename).getLength())) {
// Log.d("Main", "Exists: " + outFile.getAbsolutePath());
// continue;
// }
Log.d("Main", "Asset: " + outFile.getAbsolutePath());
in = assetManager.open(filename);
out = new FileOutputStream(outFile);
copyFile(in, out);
Runtime.getRuntime().exec("chmod 755 " + outFile.getAbsolutePath()).waitFor();
} catch (Exception e) {
Log.e(TAG, "Failed to copy asset file: " + filename, e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
// NOOP
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
// NOOP
}
}
}
}
}
private void copyFile(InputStream in, OutputStream out) throws IOException {
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_snapcast, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
Toast.makeText(this, "Not implemented", Toast.LENGTH_SHORT).show();
return true;
} else if (id == R.id.action_scan) {
initializeDiscoveryListener();
return true;
}
return super.onOptionsItemSelected(item);
}
public void initializeDiscoveryListener() {
// Instantiate a new DiscoveryListener
mDiscoveryListener = new NsdManager.DiscoveryListener() {
private void setStatus(final String text) {
Log.e(TAG, text);
runOnUiThread(new Runnable() {
@Override
public void run() {
// SnapcastActivity.this.playerFragment.setState(text);
}
});
}
// Called as soon as service discovery begins.
@Override
public void onDiscoveryStarted(String regType) {
Log.d(TAG, "Service discovery started");
setStatus("Searching for a Snapserver");
}
@Override
public void onServiceFound(NsdServiceInfo service) {
// A service was found! Do something with it.
Log.d(TAG, "Service discovery success" + service);
mNsdManager.resolveService(service, new NsdManager.ResolveListener() {
@Override
public void onResolveFailed(NsdServiceInfo nsdServiceInfo, int i) {
}
@Override
public void onServiceResolved(NsdServiceInfo nsdServiceInfo) {
Log.d(TAG, "resolved: " + nsdServiceInfo);
host = nsdServiceInfo.getHost().getCanonicalHostName();
port = nsdServiceInfo.getPort();
SnapcastActivity.this.playerFragment.setServer(host, port);
setStatus(host + ":" + port);
// startTcpClient();
mNsdManager.stopServiceDiscovery(mDiscoveryListener);
}
});
}
@Override
public void onServiceLost(NsdServiceInfo service) {
// When the network service is no longer available.
// Internal bookkeeping code goes here.
Log.e(TAG, "service lost" + service);
}
@Override
public void onDiscoveryStopped(String serviceType) {
Log.i(TAG, "Discovery stopped: " + serviceType);
}
@Override
public void onStartDiscoveryFailed(String serviceType, int errorCode) {
setStatus("Discovery failed: Error code:" + errorCode);
mNsdManager.stopServiceDiscovery(this);
}
@Override
public void onStopDiscoveryFailed(String serviceType, int errorCode) {
setStatus("Discovery failed: Error code:" + errorCode);
mNsdManager.stopServiceDiscovery(this);
}
};
mNsdManager = (NsdManager) getSystemService(Context.NSD_SERVICE);
mNsdManager.discoverServices("_snapcast._tcp.", NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);
}
@Override
public void onFragmentInteraction(Uri uri) {
}
/**
* A {@link FragmentPagerAdapter} that returns a fragment corresponding to
* one of the sections/tabs/pages.
*/
public class SectionsPagerAdapter extends FragmentPagerAdapter {
private Vector<NamedFragment> fragments = new Vector<>();
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
public void addFragment(NamedFragment fragment) {
fragments.add(fragment);
}
@Override
public Fragment getItem(int position) {
return fragments.get(position);
}
@Override
public int getCount() {
return fragments.size();
}
@Override
public CharSequence getPageTitle(int position) {
return fragments.get(position).getName();
}
}
}

View file

@ -15,7 +15,6 @@ import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.widget.Toast;
import java.io.BufferedReader;
@ -31,15 +30,9 @@ import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
public class SnapclientService extends Service {
public interface SnapclientListener {
void onPlayerStart();
void onPlayerStop();
void onLog(String log);
}
public static final String EXTRA_HOST = "EXTRA_HOST";
public static final String EXTRA_PORT = "EXTRA_PORT";
private final IBinder mBinder = new LocalBinder();
private java.lang.Process process = null;
private PowerManager.WakeLock wakeLock = null;
private WifiManager.WifiLock wifiWakeLock = null;
@ -47,19 +40,6 @@ public class SnapclientService extends Service {
private boolean running = false;
private SnapclientListener listener = null;
private final IBinder mBinder = new LocalBinder();
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
SnapclientService getService() {
// Return this instance of LocalService so clients can call public methods
return SnapclientService.this;
}
}
public boolean isRunning() {
return running;
}
@ -206,6 +186,25 @@ public class SnapclientService extends Service {
listener.onPlayerStop();
}
public interface SnapclientListener {
void onPlayerStart();
void onPlayerStop();
void onLog(String log);
}
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
SnapclientService getService() {
// Return this instance of LocalService so clients can call public methods
return SnapclientService.this;
}
}
// Handler that receives messages from the thread
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {

View file

@ -1,113 +0,0 @@
package de.badaix.snapcast;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* A simple {@link Fragment} subclass.
* Activities that contain this fragment must implement the
* {@link ZoneFragment.OnFragmentInteractionListener} interface
* to handle interaction events.
* Use the {@link ZoneFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class ZoneFragment extends NamedFragment {
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";
// TODO: Rename and change types of parameters
private String mParam1;
private String mParam2;
private OnFragmentInteractionListener mListener;
public ZoneFragment() {
// Required empty public constructor
}
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment ZoneFragment.
*/
// TODO: Rename and change types and number of parameters
public static ZoneFragment newInstance(String param1, String param2) {
ZoneFragment fragment = new ZoneFragment();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_zone, container, false);
}
// TODO: Rename method, update argument and hook method into UI event
public void onButtonPressed(Uri uri) {
if (mListener != null) {
mListener.onFragmentInteraction(uri);
}
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnFragmentInteractionListener) {
mListener = (OnFragmentInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnFragmentInteractionListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
/**
* This interface must be implemented by activities that contain this
* fragment to allow an interaction in this fragment to be communicated
* to the activity and potentially other fragments contained in that
* activity.
* <p/>
* See the Android Training lesson <a href=
* "http://developer.android.com/training/basics/fragments/communicating.html"
* >Communicating with Other Fragments</a> for more information.
*/
public interface OnFragmentInteractionListener {
// TODO: Update argument type and name
void onFragmentInteraction(Uri uri);
}
@Override
public String getName() {
return "Clients";
}
}

View file

@ -1,7 +1,6 @@
package de.badaix.snapcast.control;
import android.os.Parcel;
import android.os.Parcelable;
import org.json.JSONException;
import org.json.JSONObject;
@ -10,6 +9,17 @@ import org.json.JSONObject;
* Created by johannes on 06.01.16.
*/
public class ClientInfo implements JsonSerialisable {
public static final Creator<ClientInfo> CREATOR = new Creator<ClientInfo>() {
@Override
public ClientInfo createFromParcel(Parcel in) {
return new ClientInfo(in);
}
@Override
public ClientInfo[] newArray(int size) {
return new ClientInfo[size];
}
};
private String mac;
private String ip;
private String host;
@ -36,18 +46,6 @@ public class ClientInfo implements JsonSerialisable {
latency = in.readInt();
}
public static final Creator<ClientInfo> CREATOR = new Creator<ClientInfo>() {
@Override
public ClientInfo createFromParcel(Parcel in) {
return new ClientInfo(in);
}
@Override
public ClientInfo[] newArray(int size) {
return new ClientInfo[size];
}
};
@Override
public void fromJson(JSONObject json) {
try {
@ -88,6 +86,10 @@ public class ClientInfo implements JsonSerialisable {
return volume;
}
public void setVolume(Volume volume) {
this.volume = volume;
}
public Time_t getLastSeen() {
return lastSeen;
}
@ -112,30 +114,26 @@ public class ClientInfo implements JsonSerialisable {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getLatency() {
return latency;
}
public void setLatency(int latency) {
this.latency = latency;
}
public boolean isConnected() {
return connected;
}
public void setName(String name) {
this.name = name;
}
public void setVolume(Volume volume) {
this.volume = volume;
}
public void setConnected(boolean connected) {
this.connected = connected;
}
public void setLatency(int latency) {
this.latency = latency;
}
@Override
public String toString() {
return "ClientInfo{" +

View file

@ -6,9 +6,6 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import de.badaix.snapcast.ClientInfoItem;
import de.badaix.snapcast.TcpClient;
/**
* Created by johannes on 13.01.16.
*/
@ -30,6 +27,7 @@ public class RemoteControl implements TcpClient.TcpClientListener {
public interface RemoteControlListener {
void onConnected(RemoteControl remoteControl);
void onDisconnected(RemoteControl remoteControl);
void onClientEvent(RemoteControl remoteControl, ClientInfo clientInfo, ClientEvent event);

View file

@ -1,4 +1,4 @@
package de.badaix.snapcast;
package de.badaix.snapcast.control;
import android.util.Log;
@ -16,18 +16,6 @@ import java.net.Socket;
public class TcpClient {
private static final String TAG = "TCP";
// Declare the interface. The method messageReceived(String message) will
// must be implemented in the MyActivity
// class at on asynckTask doInBackground
public interface TcpClientListener {
void onMessageReceived(TcpClient tcpClient, String message);
void onConnected(TcpClient tcpClient);
void onDisconnected(TcpClient tcpClient);
}
private String mServerMessage;
// sends message received notifications
private TcpClientListener mMessageListener = null;
@ -39,7 +27,6 @@ public class TcpClient {
private BufferedReader mBufferIn;
private Thread worker = null;
private Socket socket = null;
private String uid;
/**
@ -154,5 +141,16 @@ public class TcpClient {
worker.start();
}
// Declare the interface. The method messageReceived(String message) will
// must be implemented in the MyActivity
// class at on asynckTask doInBackground
public interface TcpClientListener {
void onMessageReceived(TcpClient tcpClient, String message);
void onConnected(TcpClient tcpClient);
void onDisconnected(TcpClient tcpClient);
}
}