Skip to content

Commit 19c784c

Browse files
author
Chris Bellew
committed
Disconnect from subscribed client automatically after a certain number of heartbeats fail (in case the client went away).
1 parent e04d0bd commit 19c784c

14 files changed

+439
-191
lines changed

Voice Control For Plex/src/main/java/com/atomjack/vcfp/PlexSubscription.java

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ public class PlexSubscription {
6464
Thread serverThread = null;
6565
Handler updateConversationHandler;
6666

67+
private int failedHeartbeats = 0;
68+
private final int failedHeartbeatMax = 5;
69+
6770
private Handler mHandler;
6871

6972
public PlexSubscription() {
@@ -260,6 +263,7 @@ public void subscribe(PlexClient client, final boolean isHeartbeat) {
260263
PlexHttpClient.get(String.format("http://%s:%s/player/timeline/subscribe?%s", mClient.address, mClient.port, qs), headers, new PlexHttpResponseHandler() {
261264
@Override
262265
public void onSuccess(PlexResponse response) {
266+
failedHeartbeats = 0;
263267
Logger.d("PlexSubscription: Subscribed: %s", response.status);
264268
commandId++;
265269
subscribed = true;
@@ -275,14 +279,39 @@ public void onSuccess(PlexResponse response) {
275279
@Override
276280
public void onFailure(final Throwable error) {
277281
error.printStackTrace();
278-
mHandler.post(new Runnable() {
279-
@Override
280-
public void run() {
281-
if(listener != null) {
282-
listener.onSubscribeError(error.getMessage());
283-
}
282+
283+
284+
if(isHeartbeat) {
285+
failedHeartbeats++;
286+
Logger.d("%d failed heartbeats", failedHeartbeats);
287+
if(failedHeartbeats >= failedHeartbeatMax) {
288+
Logger.d("Unsubscribing due to failed heartbeats");
289+
// Since several heartbeats in a row failed, set ourselves as unsubscribed and notify any listeners that we're no longer subscribed. Don't
290+
// bother trying to actually unsubscribe since we probably can't rely on the client to respond at this point
291+
//
292+
subscribed = false;
293+
onUnsubscribed();
294+
mHandler.removeCallbacks(subscriptionHeartbeat);
295+
if(listener != null)
296+
mHandler.post(new Runnable() {
297+
@Override
298+
public void run() {
299+
if(listener != null) {
300+
listener.onSubscribeError(String.format(listener.getString(R.string.client_lost_connection), mClient.name));
301+
}
302+
}
303+
});
284304
}
285-
});
305+
} else {
306+
mHandler.post(new Runnable() {
307+
@Override
308+
public void run() {
309+
if(listener != null) {
310+
listener.onSubscribeError(error.getMessage());
311+
}
312+
}
313+
});
314+
}
286315
}
287316
});
288317
}
@@ -294,14 +323,10 @@ public void run() {
294323
Logger.d("PlexSubscription onSubscribed, client: %s, listener: %s", mClient, listener);
295324
if (listener != null && mClient != null) {
296325
listener.onSubscribed(mClient);
297-
Logger.d("Sending broadcast");
298-
// Intent subscribedBroadcast = new Intent(listener, listener.getClass());
299-
// subscribedBroadcast.setAction(ACTION_SUBSCRIBED);
326+
// Logger.d("Sending broadcast");
327+
// Intent subscribedBroadcast = new Intent(ACTION_SUBSCRIBED);
300328
// subscribedBroadcast.putExtra(EXTRA_CLIENT, mClient);
301-
// listener.startActivity(subscribedBroadcast);
302-
Intent subscribedBroadcast = new Intent(ACTION_SUBSCRIBED);
303-
subscribedBroadcast.putExtra(EXTRA_CLIENT, mClient);
304-
LocalBroadcastManager.getInstance(listener).sendBroadcast(subscribedBroadcast);
329+
// LocalBroadcastManager.getInstance(listener).sendBroadcast(subscribedBroadcast);
305330
}
306331
}
307332
});
@@ -355,6 +380,8 @@ public void onSuccess(PlexResponse response) {
355380
public void onFailure(Throwable error) {
356381
// TODO: Handle failure here?
357382
Logger.d("failure unsubscribing");
383+
subscribed = false;
384+
mHandler.removeCallbacks(subscriptionHeartbeat);
358385
onUnsubscribed();
359386
}
360387
});

Voice Control For Plex/src/main/java/com/atomjack/vcfp/RemoteScan.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ public void onSuccess(int statusCode, org.apache.http.Header[] headers, byte[] r
8282
Logger.d("Device %s is a player", device.name);
8383
}
8484
}
85-
// Preferences.put(Preferences.SAVED_CLIENTS, gson.toJson(VoiceControlForPlexApplication.clients));
8685

8786
final int[] serversScanned = new int[1];
8887
serversScanned[0] = 0;

Voice Control For Plex/src/main/java/com/atomjack/vcfp/VoiceControlForPlexApplication.java

Lines changed: 121 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
package com.atomjack.vcfp;
22

3+
import java.io.IOException;
34
import java.io.InputStream;
45
import java.lang.reflect.Type;
56
import java.math.BigInteger;
6-
import java.net.InetAddress;
7-
import java.net.InterfaceAddress;
8-
import java.net.NetworkInterface;
97
import java.security.MessageDigest;
108
import java.security.SecureRandom;
119
import java.util.ArrayList;
1210
import java.util.Arrays;
13-
import java.util.Enumeration;
1411
import java.util.HashMap;
1512
import java.util.Locale;
1613
import java.util.Map;
@@ -81,6 +78,16 @@ public class VoiceControlForPlexApplication extends Application
8178
.registerTypeAdapter(Uri.class, new UriSerializer())
8279
.create();
8380

81+
private NOTIFICATION_STATUS notificationStatus = NOTIFICATION_STATUS.off;
82+
public static enum NOTIFICATION_STATUS {
83+
off,
84+
on,
85+
initializing
86+
}
87+
88+
private NotificationManager mNotifyMgr;
89+
private Bitmap notificationBitmap = null;
90+
8491
public final static class Intent {
8592
public final static String GDMRECEIVE = "com.atomjack.vcfp.intent.gdmreceive";
8693

@@ -148,6 +155,8 @@ public void onCreate() {
148155
if(!mHasChromecast)
149156
setupInAppPurchasing();
150157

158+
mNotifyMgr = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
159+
151160
// Load saved clients and servers
152161
Type clientType = new TypeToken<HashMap<String, PlexClient>>(){}.getType();
153162
VoiceControlForPlexApplication.castClients = gsonRead.fromJson(Preferences.get(Preferences.SAVED_CAST_CLIENTS, "{}"), clientType);
@@ -336,81 +345,118 @@ public static String generateRandomString() {
336345
}
337346

338347
public void setNotification(final PlexClient client, final PlayerState currentState, final PlexMedia media) {
348+
setNotification(client, currentState, media, false);
349+
}
350+
351+
public void setNotification(final PlexClient client, final PlayerState currentState, final PlexMedia media, boolean skipThumb) {
339352
Logger.d("Setting notification, client: %s, media: %s", client, media);
340-
new AsyncTask() {
341-
@Override
342-
protected Object doInBackground(Object[] objects) {
343-
if(client != null && media != null) {
344-
InputStream inputStream = null;
345-
try {
346-
SimpleDiskCache.InputStreamEntry inputStreamEntry = mSimpleDiskCache.getInputStream(media.getImageKey(PlexMedia.IMAGE_KEY.NOTIFICATION_THUMB));
347-
if(inputStreamEntry != null) {
348-
inputStream = inputStreamEntry.getInputStream();
349-
// inputStream.reset();
350-
}
351-
} catch (Exception ex) {
352-
ex.printStackTrace();
353-
}
354-
if(inputStream == null) {
355-
inputStream = media.getThumb(64, 64);
356-
}
357-
Bitmap thumb = BitmapFactory.decodeStream(inputStream);
358-
NotificationManager mNotifyMgr = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
359-
android.content.Intent rewindIntent = new android.content.Intent(VoiceControlForPlexApplication.this, PlexControlService.class);
360-
rewindIntent.setAction(PlexControlService.ACTION_REWIND);
361-
rewindIntent.putExtra(PlexControlService.CLIENT, client);
362-
rewindIntent.putExtra(PlexControlService.MEDIA, media);
363-
PendingIntent piRewind = PendingIntent.getService(VoiceControlForPlexApplication.this, 0, rewindIntent, PendingIntent.FLAG_UPDATE_CURRENT);
364-
365-
android.content.Intent playIntent = new android.content.Intent(VoiceControlForPlexApplication.this, PlexControlService.class);
366-
playIntent.setAction(PlexControlService.ACTION_PLAY);
367-
playIntent.putExtra(PlexControlService.CLIENT, client);
368-
playIntent.putExtra(PlexControlService.MEDIA, media);
369-
PendingIntent playPendingIntent = PendingIntent.getService(VoiceControlForPlexApplication.this, 0, playIntent, PendingIntent.FLAG_UPDATE_CURRENT);
370-
371-
android.content.Intent pauseIntent = new android.content.Intent(VoiceControlForPlexApplication.this, PlexControlService.class);
372-
pauseIntent.setAction(PlexControlService.ACTION_PAUSE);
373-
pauseIntent.putExtra(PlexControlService.CLIENT, client);
374-
pauseIntent.putExtra(PlexControlService.MEDIA, media);
375-
PendingIntent pausePendingIntent = PendingIntent.getService(VoiceControlForPlexApplication.this, 0, pauseIntent, PendingIntent.FLAG_UPDATE_CURRENT);
376-
377-
android.content.Intent nowPlayingIntent;
378-
if(client.isCastClient) {
379-
nowPlayingIntent = new android.content.Intent(VoiceControlForPlexApplication.this, CastActivity.class);
380-
// nowPlayingIntent.setAction(Intent.CAST_MEDIA);
381-
} else
382-
nowPlayingIntent = new android.content.Intent(VoiceControlForPlexApplication.this, NowPlayingActivity.class);
383-
nowPlayingIntent.setFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK |
384-
android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK);
385-
nowPlayingIntent.putExtra(Intent.EXTRA_MEDIA, media);
386-
nowPlayingIntent.putExtra(Intent.EXTRA_CLIENT, client);
387-
PendingIntent piNowPlaying = PendingIntent.getActivity(VoiceControlForPlexApplication.this, 0, nowPlayingIntent, PendingIntent.FLAG_UPDATE_CURRENT);
388-
389-
try {
390-
NotificationCompat.Builder mBuilder =
391-
new NotificationCompat.Builder(VoiceControlForPlexApplication.this)
392-
.setSmallIcon(R.drawable.ic_launcher)
393-
.setAutoCancel(false)
394-
.setOngoing(true)
395-
.setOnlyAlertOnce(true)
396-
.setContentIntent(piNowPlaying)
397-
.setContent(getNotificationView(R.layout.now_playing_notification, thumb, media, client, playPendingIntent, pausePendingIntent, piRewind, currentState == PlayerState.PLAYING))
398-
.setDefaults(Notification.DEFAULT_ALL);
399-
Notification n = mBuilder.build();
400-
if (Build.VERSION.SDK_INT >= 16)
401-
n.bigContentView = getNotificationView(R.layout.now_playing_notification_big, thumb, media, client, playPendingIntent, pausePendingIntent, piRewind, currentState == PlayerState.PLAYING);
402-
403-
// Disable notification sound
404-
n.defaults = 0;
405-
mNotifyMgr.notify(nowPlayingNotificationId, n);
406-
} catch (Exception ex) {
407-
ex.printStackTrace();
353+
if(notificationStatus == NOTIFICATION_STATUS.off) {
354+
notificationStatus = NOTIFICATION_STATUS.initializing;
355+
notificationBitmap = null;
356+
}
357+
358+
try {
359+
Logger.d("Trying to get cached thumb: %s", media.getImageKey(PlexMedia.IMAGE_KEY.NOTIFICATION_THUMB));
360+
SimpleDiskCache.BitmapEntry bitmapEntry = mSimpleDiskCache.getBitmap(media.getImageKey(PlexMedia.IMAGE_KEY.NOTIFICATION_THUMB));
361+
Logger.d("bitmapEntry: %s", bitmapEntry);
362+
if(bitmapEntry != null) {
363+
notificationBitmap = bitmapEntry.getBitmap();
364+
}
365+
} catch (Exception ex) {
366+
ex.printStackTrace();
367+
}
368+
369+
if(notificationBitmap == null && notificationStatus == NOTIFICATION_STATUS.initializing && !skipThumb) {
370+
Logger.d("Thumb not found in cache. Downloading.");
371+
new AsyncTask() {
372+
@Override
373+
protected Object doInBackground(Object[] objects) {
374+
if (client != null && media != null) {
375+
InputStream inputStream = media.getNotificationThumb();
376+
Logger.d("Got input stream: %s", inputStream);
377+
notificationBitmap = BitmapFactory.decodeStream(inputStream);
378+
try {
379+
inputStream.reset();
380+
} catch (IOException e) {}
381+
Logger.d("notificationBitmap: %s", notificationBitmap);
382+
try {
383+
Logger.d("image key: %s", media.getImageKey(PlexMedia.IMAGE_KEY.NOTIFICATION_THUMB));
384+
mSimpleDiskCache.put(media.getImageKey(PlexMedia.IMAGE_KEY.NOTIFICATION_THUMB), inputStream);
385+
inputStream.close();
386+
Logger.d("Downloaded thumb. Redoing notification.");
387+
setNotification(client, currentState, media, true);
388+
} catch (Exception e) {}
408389
}
390+
return null;
409391
}
410-
return null;
411-
}
412-
}.execute();
392+
}.execute();
393+
}
394+
395+
Logger.d("Setting up notification");
396+
// notificationBitmap
397+
android.content.Intent rewindIntent = new android.content.Intent(VoiceControlForPlexApplication.this, PlexControlService.class);
398+
rewindIntent.setAction(PlexControlService.ACTION_REWIND);
399+
rewindIntent.putExtra(PlexControlService.CLIENT, client);
400+
rewindIntent.putExtra(PlexControlService.MEDIA, media);
401+
PendingIntent piRewind = PendingIntent.getService(VoiceControlForPlexApplication.this, 0, rewindIntent, PendingIntent.FLAG_UPDATE_CURRENT);
402+
403+
android.content.Intent playIntent = new android.content.Intent(VoiceControlForPlexApplication.this, PlexControlService.class);
404+
playIntent.setAction(PlexControlService.ACTION_PLAY);
405+
playIntent.putExtra(PlexControlService.CLIENT, client);
406+
playIntent.putExtra(PlexControlService.MEDIA, media);
407+
PendingIntent playPendingIntent = PendingIntent.getService(VoiceControlForPlexApplication.this, 0, playIntent, PendingIntent.FLAG_UPDATE_CURRENT);
408+
409+
android.content.Intent pauseIntent = new android.content.Intent(VoiceControlForPlexApplication.this, PlexControlService.class);
410+
pauseIntent.setAction(PlexControlService.ACTION_PAUSE);
411+
pauseIntent.putExtra(PlexControlService.CLIENT, client);
412+
pauseIntent.putExtra(PlexControlService.MEDIA, media);
413+
PendingIntent pausePendingIntent = PendingIntent.getService(VoiceControlForPlexApplication.this, 0, pauseIntent, PendingIntent.FLAG_UPDATE_CURRENT);
414+
415+
android.content.Intent nowPlayingIntent;
416+
if(client.isCastClient) {
417+
nowPlayingIntent = new android.content.Intent(VoiceControlForPlexApplication.this, CastActivity.class);
418+
// nowPlayingIntent.setAction(Intent.CAST_MEDIA);
419+
} else
420+
nowPlayingIntent = new android.content.Intent(VoiceControlForPlexApplication.this, NowPlayingActivity.class);
421+
nowPlayingIntent.setFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK |
422+
android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK);
423+
nowPlayingIntent.putExtra(Intent.EXTRA_MEDIA, media);
424+
nowPlayingIntent.putExtra(Intent.EXTRA_CLIENT, client);
425+
PendingIntent piNowPlaying = PendingIntent.getActivity(VoiceControlForPlexApplication.this, 0, nowPlayingIntent, PendingIntent.FLAG_UPDATE_CURRENT);
426+
427+
try {
428+
NotificationCompat.Builder mBuilder =
429+
new NotificationCompat.Builder(VoiceControlForPlexApplication.this)
430+
.setSmallIcon(R.drawable.ic_launcher)
431+
.setAutoCancel(false)
432+
.setOngoing(true)
433+
.setOnlyAlertOnce(true)
434+
.setContentIntent(piNowPlaying)
435+
.setContent(getNotificationView(R.layout.now_playing_notification, notificationBitmap, media, client, playPendingIntent, pausePendingIntent, piRewind, currentState == PlayerState.PLAYING))
436+
.setDefaults(Notification.DEFAULT_ALL);
437+
Notification n = mBuilder.build();
438+
if (Build.VERSION.SDK_INT >= 16)
439+
n.bigContentView = getNotificationView(media.isMusic() ? R.layout.now_playing_notification_big_music : R.layout.now_playing_notification_big, notificationBitmap, media, client, playPendingIntent, pausePendingIntent, piRewind, currentState == PlayerState.PLAYING);
440+
441+
// Disable notification sound
442+
n.defaults = 0;
443+
mNotifyMgr.notify(nowPlayingNotificationId, n);
444+
notificationStatus = NOTIFICATION_STATUS.on;
445+
Logger.d("Notification set");
446+
} catch (Exception ex) {
447+
ex.printStackTrace();
448+
}
449+
450+
451+
}
452+
453+
public void cancelNotification() {
454+
mNotifyMgr.cancel(nowPlayingNotificationId);
455+
notificationStatus = NOTIFICATION_STATUS.off;
456+
}
413457

458+
public NOTIFICATION_STATUS getNotificationStatus() {
459+
return notificationStatus;
414460
}
415461

416462
private RemoteViews getNotificationView(int layoutId, Bitmap thumb, PlexMedia media, PlexClient client,

0 commit comments

Comments
 (0)