Browse Source

reduce latency, set volume, get volume and battery status, respond to pings

isundil 6 years ago
parent
commit
5dedcc499d

+ 1 - 0
app/build.gradle

@@ -27,6 +27,7 @@ dependencies {
     implementation 'com.android.support:appcompat-v7:28.0.0'
     implementation 'com.android.support:support-v4:28.0.0'
     implementation 'com.android.support:support-vector-drawable:28.0.0'
+    implementation 'com.google.android.exoplayer:exoplayer-core:2.9.4'
     testImplementation 'junit:junit:4.12'
     androidTestImplementation 'com.android.support.test:runner:1.0.2'
     androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

+ 35 - 5
app/src/main/java/homespeakers/knacki/info/homespeakersandroid/Service.java

@@ -3,6 +3,7 @@ package homespeakers.knacki.info.homespeakersandroid;
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.Intent;
+import android.os.Build;
 import android.os.IBinder;
 import android.preference.PreferenceManager;
 import android.support.v4.app.NotificationCompat;
@@ -15,6 +16,8 @@ import java.io.StringReader;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.Socket;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayDeque;
 import java.util.Collection;
 import java.util.logging.Level;
@@ -23,14 +26,18 @@ import java.util.logging.Logger;
 import homespeakers.knacki.info.homespeakersandroid.radio.Input;
 import homespeakers.knacki.info.homespeakersandroid.radio.InputPlayer;
 import homespeakers.knacki.info.homespeakersandroid.radio.InputReader;
+import homespeakers.knacki.info.homespeakersandroid.system.BatteryController;
+import homespeakers.knacki.info.homespeakersandroid.system.VolumeController;
 
 public class Service extends android.app.Service {
     public final static String NOTIFICATION_CHANNEL_ID = "info.knacki.homespeakers.service";
     public final static int NOTIFICATION_ID = NOTIFICATION_CHANNEL_ID.hashCode();
-    public final static long RECONNECT_DELAY = 60000; // 1 minute
+    public final static long RECONNECT_DELAY = 6000; // 1 minute
     public final static String ADDRESS = "192.168.0.5";
     public final static short PORT = 9001;
 
+    protected static final Charset charset = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT ? StandardCharsets.UTF_8 : Charset.forName("utf-8");
+
     private final static Logger log = Logger.getLogger(Service.class.getName());
     private Collection<InputPlayer> playingInputs = new ArrayDeque<>();
 
@@ -94,7 +101,7 @@ public class Service extends android.app.Service {
                         continue newLoop;
                 // New source, add it
                 InputPlayer player = new InputPlayer(i);
-                player.start();
+                player.start(this);
                 playingInputs.add(player);
             }
         }
@@ -105,8 +112,18 @@ public class Service extends android.app.Service {
             String config = stream.readLine();
             if (config == null)
                 return;
-            Collection<Input> inputs = InputReader.Read(new JsonReader(new StringReader(config)));
-            setInputs(inputs);
+            if (config.startsWith("SETVOL")) {
+                short vol = Short.parseShort(config.substring("SETVOL".length()));
+                VolumeController.SetVolume(this, vol);
+                sendStatus();
+            } else if (config.startsWith("SET")) {
+                Collection<Input> inputs = InputReader.Read(new JsonReader(new StringReader(config.substring("SET".length()))));
+                setInputs(inputs);
+            } else if (config.startsWith("PING")) {
+                log.severe("Send pong");
+                fSock.getOutputStream().write("PONG\n".getBytes(charset));
+                sendStatus();
+            }
         }
     }
 
@@ -115,11 +132,24 @@ public class Service extends android.app.Service {
     private static void sendName(String name) {
         if (fSock == null) return;
         try {
-            fSock.getOutputStream().write(("HELO " +name + "\n").getBytes("utf-8"));
+            fSock.getOutputStream().write(("HELO " +name + "\n").getBytes(charset));
         }
         catch (IOException e) {}
     }
 
+    public void sendStatus() {
+        final short volume = VolumeController.GetVolume(this);
+        final boolean plugged = BatteryController.IsPlugged(this);
+        final short battery = BatteryController.GetLevel(this);
+
+        try {
+            fSock.getOutputStream().write(("STATUS{\"battery\":" +battery +",\"plugged\":" +(plugged ? "true" : "false") +",\"volume\":" +volume +"}").getBytes(charset));
+        }
+        catch (IOException e) {
+            log.log(Level.SEVERE, "Cannot send status to server", e);
+        }
+    }
+
     public void createSocket() {
         fSock = null;
         while (true) {

+ 41 - 44
app/src/main/java/homespeakers/knacki/info/homespeakersandroid/SettingsActivity.java

@@ -38,55 +38,52 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
      * A preference value change listener that updates the preference's summary
      * to reflect its new value.
      */
-    private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() {
-        @Override
-        public boolean onPreferenceChange(Preference preference, Object value) {
-            String stringValue = value.toString();
-
-            if (preference instanceof ListPreference) {
-                // For list preferences, look up the correct display value in
-                // the preference's 'entries' list.
-                ListPreference listPreference = (ListPreference) preference;
-                int index = listPreference.findIndexOfValue(stringValue);
-
-                // Set the summary to reflect the new value.
-                preference.setSummary(
-                        index >= 0
-                                ? listPreference.getEntries()[index]
-                                : null);
-
-            } else if (preference instanceof RingtonePreference) {
-                // For ringtone preferences, look up the correct display value
-                // using RingtoneManager.
-                if (TextUtils.isEmpty(stringValue)) {
-                    // Empty values correspond to 'silent' (no ringtone).
-                    preference.setSummary(R.string.pref_ringtone_silent);
+    private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = (preference, value) -> {
+        String stringValue = value.toString();
+
+        if (preference instanceof ListPreference) {
+            // For list preferences, look up the correct display value in
+            // the preference's 'entries' list.
+            ListPreference listPreference = (ListPreference) preference;
+            int index = listPreference.findIndexOfValue(stringValue);
+
+            // Set the summary to reflect the new value.
+            preference.setSummary(
+                    index >= 0
+                            ? listPreference.getEntries()[index]
+                            : null);
+
+        } else if (preference instanceof RingtonePreference) {
+            // For ringtone preferences, look up the correct display value
+            // using RingtoneManager.
+            if (TextUtils.isEmpty(stringValue)) {
+                // Empty values correspond to 'silent' (no ringtone).
+                preference.setSummary(R.string.pref_ringtone_silent);
+
+            } else {
+                Ringtone ringtone = RingtoneManager.getRingtone(
+                        preference.getContext(), Uri.parse(stringValue));
 
+                if (ringtone == null) {
+                    // Clear the summary if there was a lookup error.
+                    preference.setSummary(null);
                 } else {
-                    Ringtone ringtone = RingtoneManager.getRingtone(
-                            preference.getContext(), Uri.parse(stringValue));
-
-                    if (ringtone == null) {
-                        // Clear the summary if there was a lookup error.
-                        preference.setSummary(null);
-                    } else {
-                        // Set the summary to reflect the new ringtone display
-                        // name.
-                        String name = ringtone.getTitle(preference.getContext());
-                        preference.setSummary(name);
-                    }
+                    // Set the summary to reflect the new ringtone display
+                    // name.
+                    String name = ringtone.getTitle(preference.getContext());
+                    preference.setSummary(name);
                 }
-
-            } else {
-                // For all other preferences, set the summary to the value's
-                // simple string representation.
-                preference.setSummary(stringValue);
-            }
-            if (preference.hasKey() && preference.getKey().equals("name")) {
-                Service.setName(stringValue);
             }
-            return true;
+
+        } else {
+            // For all other preferences, set the summary to the value's
+            // simple string representation.
+            preference.setSummary(stringValue);
+        }
+        if (preference.hasKey() && preference.getKey().equals("name")) {
+            Service.setName(stringValue);
         }
+        return true;
     };
 
     /**

+ 23 - 50
app/src/main/java/homespeakers/knacki/info/homespeakersandroid/radio/InputPlayer.java

@@ -1,75 +1,48 @@
 package homespeakers.knacki.info.homespeakersandroid.radio;
 
+import android.content.Context;
 import android.media.AudioManager;
-import android.media.MediaPlayer;
+import android.net.Uri;
+
+import com.google.android.exoplayer2.ExoPlayer;
+import com.google.android.exoplayer2.ExoPlayerFactory;
+import com.google.android.exoplayer2.source.BaseMediaSource;
+import com.google.android.exoplayer2.source.ExtractorMediaSource;
+import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
+import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
 
 import java.io.IOException;
+import java.net.URI;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-public class InputPlayer implements MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener, MediaPlayer.OnBufferingUpdateListener, MediaPlayer.OnCompletionListener {
+import homespeakers.knacki.info.homespeakersandroid.R;
+
+public class InputPlayer {
     private final static Logger log = Logger.getLogger(Input.class.getName());
 
     public final String fName;
-    private final String fUrl;
-    private MediaPlayer fPlayer;
+    private final Uri fUri;
+    private ExoPlayer fPlayer;
 
     public InputPlayer(Input in) {
         fName = in.fName;
-        fUrl = in.fAddress;
+        fUri = Uri.parse(in.fAddress);
     }
 
-    public void start() {
-        log.warning("Start source " + this.fName +" on resource " +this.fUrl);
-        fPlayer = null;
-        try {
-            fPlayer = new MediaPlayer();
-            fPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
-            fPlayer.setDataSource(fUrl);
-            fPlayer.setOnPreparedListener(this);
-            fPlayer.setOnErrorListener(this);
-            fPlayer.setOnBufferingUpdateListener(this);
-            fPlayer.setOnCompletionListener(this);
-            fPlayer.prepareAsync();
-        } catch (IOException e) {
-            log.log(Level.SEVERE, "Cannot read source", e);
-            try {
-                Thread.sleep(30 * 1000);
-            } catch (InterruptedException e1) {
-                if (fPlayer != null)
-                    fPlayer.release();
-            }
-        }
-        fPlayer.start();
+    public void start(Context ctx) {
+        stop();
+        log.warning("Start source " + this.fName +" on resource " +this.fUri.toString());
+        fPlayer = ExoPlayerFactory.newSimpleInstance(ctx);
+        fPlayer.setPlayWhenReady(true);
+        fPlayer.prepare(new ExtractorMediaSource.Factory(new DefaultHttpDataSourceFactory(ctx.getResources().getString(R.string.app_name))).createMediaSource(fUri));
     }
 
     public void stop() {
         if (fPlayer != null) {
-            fPlayer.stop();
+            fPlayer.stop(true);
             fPlayer.release();
             fPlayer = null;
         }
     }
-
-    @Override
-    public void onPrepared(MediaPlayer mp) {
-        if (fPlayer != null)
-            fPlayer.start();
-    }
-
-    @Override
-    public boolean onError(MediaPlayer mp, int what, int extra) {
-        log.severe("Error " +what +" " +extra);
-        return true;
-    }
-
-    @Override
-    public void onBufferingUpdate(MediaPlayer mp, int percent) {
-        log.warning("on buffering update: " +percent);
-    }
-
-    @Override
-    public void onCompletion(MediaPlayer mp) {
-        log.warning("on Completion called");
-    }
 }

+ 30 - 0
app/src/main/java/homespeakers/knacki/info/homespeakersandroid/system/BatteryController.java

@@ -0,0 +1,30 @@
+package homespeakers.knacki.info.homespeakersandroid.system;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+
+public class BatteryController {
+
+    private static Intent GetBatteryStatus(Context ctx) {
+        return ctx.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+    }
+
+    public static short GetLevel(Context ctx) {
+        final Intent batteryStatus = GetBatteryStatus(ctx);
+        if (batteryStatus == null)
+            return -1;
+        final int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
+        final int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
+        return (short) ((level * 100) / scale);
+    }
+
+    public static boolean IsPlugged(Context ctx) {
+        final Intent batteryStatus = GetBatteryStatus(ctx);
+        final int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
+
+        return status == BatteryManager.BATTERY_STATUS_CHARGING ||
+                status == BatteryManager.BATTERY_STATUS_FULL;
+    }
+}

+ 21 - 0
app/src/main/java/homespeakers/knacki/info/homespeakersandroid/system/VolumeController.java

@@ -0,0 +1,21 @@
+package homespeakers.knacki.info.homespeakersandroid.system;
+
+import android.content.Context;
+import android.media.AudioManager;
+
+public class VolumeController {
+    private static AudioManager GetAudioManager(Context ctx) {
+        return (AudioManager) ctx.getSystemService(Context.AUDIO_SERVICE);
+    }
+
+    public static short GetVolume(Context ctx) {
+        final AudioManager manager = GetAudioManager(ctx);
+        return (short) ((manager.getStreamVolume(AudioManager.STREAM_MUSIC) * 100) / manager.getStreamMaxVolume(AudioManager.STREAM_MUSIC));
+    }
+
+    public static void SetVolume(Context ctx, short volume) {
+        final AudioManager manager = GetAudioManager(ctx);
+        int value = ((volume * manager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)) / 100);
+        manager.setStreamVolume(AudioManager.STREAM_MUSIC, value, AudioManager.FLAG_SHOW_UI);
+    }
+}