Skip to content

Commit c507ae1

Browse files
author
Chris Bellew
committed
Initial support for Plex Media Player. If we are subscribed to a Plex Client and network connectivity is lost, when it is re-acquired, if more than 30 seconds has passed since the client was last contacted, force a refresh of the subscription, in case the client booted us off for being unreachable for too long.
1 parent ee67575 commit c507ae1

21 files changed

+300
-124
lines changed

mobile/src/main/java/com/atomjack/vcfp/CastPlayerManager.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,8 @@ public void onApplicationStatusChanged(String appStatus) {
421421
@Override
422422
public void onApplicationConnectionFailed(int errorCode) {
423423
Logger.d("[CastPlayerManager] onApplicationConnectionFailed: %d", errorCode);
424-
listener.onCastConnectionFailed();
424+
if(listener != null)
425+
listener.onCastConnectionFailed();
425426
}
426427

427428
@Override

mobile/src/main/java/com/atomjack/vcfp/PlexSubscription.java

Lines changed: 54 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -20,38 +20,22 @@
2020
import java.io.IOException;
2121
import java.io.InputStreamReader;
2222
import java.io.PrintStream;
23+
import java.net.InetSocketAddress;
2324
import java.net.ServerSocket;
2425
import java.net.Socket;
2526
import java.net.SocketException;
27+
import java.util.Date;
2628
import java.util.HashMap;
2729
import java.util.Map;
2830
import java.util.regex.Matcher;
2931
import java.util.regex.Pattern;
3032

3133
public class PlexSubscription {
32-
public static final String ACTION_SUBSCRIBE = "com.atomjack.vcfp.action_subscribe";
33-
public static final String ACTION_SUBSCRIBED = "com.atomjack.vcfp.action_subscribed";
34-
public static final String ACTION_UNSUBSCRIBE = "com.atomjack.vcfp.action_unsubscribe";
35-
36-
// Boolean indicating whether or not to notify an activity of an action done
37-
public static final String EXTRA_NOTIFY = "com.atomjack.vcfp.extra_unsubscribe_notify";
38-
39-
public static final String ACTION_UNSUBSCRIBED = "com.atomjack.vcfp.action_unsubscribed";
40-
public static final String ACTION_BROADCAST = "com.atomjack.vcfp.action_broadcast";
41-
public static final String ACTION_MESSAGE = "com.atomjack.vcfp.action_message";
42-
43-
public static final String EXTRA_CLASS = "com.atomjack.vcfp.extra_class";
44-
public static final String EXTRA_CLIENT = "com.atomjack.vcfp.extra_client";
45-
public static final String EXTRA_TIMELINES = "com.atomjack.vcfp.extra_timelines";
46-
4734
private static final int SUBSCRIBE_INTERVAL = 30000; // Send subscribe message every 30 seconds to keep us alive
4835

4936
private static Serializer serial = new Persister();
5037

51-
public PlexClient mClient; // the mClient we are subscribing to
52-
53-
private String uuid;
54-
private String appName;
38+
public PlexClient mClient; // the client we are subscribing to
5539

5640
private VCFPActivity listener;
5741
private VCFPActivity notificationListener; // This will be the listener but will not be reset, and will be used for changing the notification
@@ -71,17 +55,17 @@ public class PlexSubscription {
7155
private PlayerState currentState = PlayerState.STOPPED;
7256
private Timeline currentTimeline;
7357

58+
public Date timeLastHeardFromClient;
59+
7460
public PlexSubscription() {
7561
mHandler = new Handler();
76-
uuid = VoiceControlForPlexApplication.getInstance().prefs.getUUID();
7762
}
7863

7964
public void setListener(VCFPActivity _listener) {
8065
if(_listener != null)
8166
Logger.d("Setting listener to %s", _listener.getClass().getSimpleName());
8267
listener = _listener;
8368
if(_listener != null) {
84-
appName = _listener.getString(R.string.app_name);
8569
notificationListener = _listener;
8670
}
8771
}
@@ -125,12 +109,19 @@ public void run() {
125109
Logger.d("starting serverthread");
126110
Socket socket = null;
127111
try {
128-
serverSocket = new ServerSocket(subscriptionPort);
112+
serverSocket = new ServerSocket();
113+
serverSocket.setReuseAddress(true);
114+
serverSocket.bind(new InetSocketAddress(subscriptionPort));
115+
// subscriptionPort = serverSocket.getLocalPort();
129116
} catch (IOException e) {
117+
130118
e.printStackTrace();
131119
}
132120

133121
Logger.d("running");
122+
123+
124+
134125
onSocketReady.run();
135126
if(onReady != null) {
136127
// onReady.run();
@@ -258,27 +249,45 @@ public void subscribe(PlexClient client, final boolean isHeartbeat) {
258249
if(client == null)
259250
return;
260251
mClient = client;
261-
PlexHttpClient.subscribe(client, subscriptionPort, commandId, uuid, appName, new PlexHttpResponseHandler() {
252+
253+
PlexHttpClient.subscribe(client, subscriptionPort, commandId, VoiceControlForPlexApplication.getInstance().getUUID(), VoiceControlForPlexApplication.getInstance().getString(R.string.app_name), new PlexHttpResponseHandler() {
262254
@Override
263255
public void onSuccess(PlexResponse response) {
264256
failedHeartbeats = 0;
265-
Logger.d("PlexSubscription: Subscribed: %s", response.status);
266-
commandId++;
267-
subscribed = true;
257+
if(!isHeartbeat)
258+
Logger.d("PlexSubscription: Subscribed: %s", response != null ? response.status : "");
259+
else
260+
Logger.d("PlexSubscription: Heartbeat: %s", response != null ? response.status : "");
261+
262+
263+
264+
if(response.code != 200) {
265+
this.onFailure(new Throwable(response.status));
266+
// Close the server socket so it's no longer listening on the subscriptionPort
267+
try {
268+
serverSocket.close();
269+
serverSocket = null;
270+
} catch (Exception e) {}
268271

269-
if (!isHeartbeat) {
270-
// Start the heartbeat subscription (so the server knows we're still here)
271-
mHandler.removeCallbacks(subscriptionHeartbeat);
272-
mHandler.postDelayed(subscriptionHeartbeat, SUBSCRIBE_INTERVAL);
273-
onSubscribed();
272+
273+
} else {
274+
timeLastHeardFromClient = new Date();
275+
276+
commandId++;
277+
subscribed = true;
278+
279+
if (!isHeartbeat) {
280+
// Start the heartbeat subscription (so the plex client knows we're still here)
281+
mHandler.removeCallbacks(subscriptionHeartbeat);
282+
mHandler.postDelayed(subscriptionHeartbeat, SUBSCRIBE_INTERVAL);
283+
onSubscribed();
284+
}
274285
}
275286
}
276287

277288
@Override
278289
public void onFailure(final Throwable error) {
279290
error.printStackTrace();
280-
281-
282291
if(isHeartbeat) {
283292
failedHeartbeats++;
284293
Logger.d("%d failed heartbeats", failedHeartbeats);
@@ -352,7 +361,7 @@ public void unsubscribe(final boolean notify, final Runnable onFinish) {
352361
if(listener == null)
353362
return;
354363

355-
PlexHttpClient.unsubscribe(mClient, commandId, uuid, listener.getString(R.string.app_name), new PlexHttpResponseHandler() {
364+
PlexHttpClient.unsubscribe(mClient, commandId, VoiceControlForPlexApplication.getInstance().prefs.getUUID(), listener.getString(R.string.app_name), new PlexHttpResponseHandler() {
356365
@Override
357366
public void onSuccess(PlexResponse response) {
358367
Logger.d("Unsubscribed");
@@ -364,7 +373,8 @@ public void onSuccess(PlexResponse response) {
364373
serverSocket.close();
365374
serverSocket = null;
366375
} catch (Exception ex) {
367-
// ex.printStackTrace();
376+
Logger.d("Exception attempting to close socket.");
377+
ex.printStackTrace();
368378
}
369379
if(notify)
370380
onUnsubscribed();
@@ -378,6 +388,15 @@ public void onFailure(Throwable error) {
378388
Logger.d("failure unsubscribing");
379389
subscribed = false;
380390
mHandler.removeCallbacks(subscriptionHeartbeat);
391+
392+
try {
393+
serverSocket.close();
394+
serverSocket = null;
395+
} catch (Exception ex) {
396+
Logger.d("Exception attempting to close socket due to failed unsubscribe.");
397+
ex.printStackTrace();
398+
}
399+
381400
onUnsubscribed();
382401
}
383402
});

mobile/src/main/java/com/atomjack/vcfp/activities/CastActivity.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ public class CastActivity extends PlayerActivity {
4040

4141
private void start(final boolean setView) {
4242
mClient = getIntent().getParcelableExtra(Intent.EXTRA_CLIENT);
43-
Logger.d("[CastActivity] set mClient: %s", mClient);
43+
Logger.d("[CastActivity] set client: %s", mClient);
44+
Logger.d("[CastActivity] action: %s", getIntent().getAction());
4445

4546
if(getIntent().getBooleanExtra(WearConstants.FROM_WEAR, false)) {
4647
new SendToDataLayerThread(WearConstants.FINISH, this).start();
@@ -290,6 +291,11 @@ protected void onDestroy() {
290291
super.onDestroy();
291292
}
292293

294+
@Override
295+
public void doPlayPause() {
296+
doPlayPause(null);
297+
}
298+
293299
@Override
294300
public void doPlayPause(View v) {
295301
try {
@@ -302,6 +308,7 @@ public void doPlayPause(View v) {
302308
} catch (Exception ex) {}
303309
}
304310

311+
@Override
305312
public void doRewind(View v) {
306313
if(position > -1) {
307314
nowPlayingMedia.viewOffset = Integer.toString(position - 15000);
@@ -313,6 +320,7 @@ public void doRewind(View v) {
313320
}
314321
}
315322

323+
@Override
316324
public void doForward(View v) {
317325
if(position > -1) {
318326
nowPlayingMedia.viewOffset = Integer.toString(position + 30000);

mobile/src/main/java/com/atomjack/vcfp/activities/MainActivity.java

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1161,19 +1161,21 @@ public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo route)
11611161
{
11621162
Logger.d("onRouteAdded: %s", route);
11631163
if(!VoiceControlForPlexApplication.castClients.containsKey(route.getName())) {
1164-
PlexClient client = new PlexClient();
1165-
client.isCastClient = true;
1166-
client.name = route.getName();
1167-
client.product = route.getDescription();
1168-
client.castDevice = CastDevice.getFromBundle(route.getExtras());
1169-
VoiceControlForPlexApplication.castClients.put(client.name, client);
1170-
Logger.d("Added cast client %s", client.name);
1171-
VoiceControlForPlexApplication.getInstance().prefs.put(Preferences.SAVED_CAST_CLIENTS, gsonWrite.toJson(VoiceControlForPlexApplication.castClients));
1172-
// If the "select a plex client" dialog is showing, refresh the list of clients
1173-
if(deviceSelectDialog != null && deviceSelectDialog.isShowing()) {
1174-
deviceSelectDialogRefresh();
1175-
}
1164+
VoiceControlForPlexApplication.castClients.remove(route.getName());
11761165
}
1166+
PlexClient client = new PlexClient();
1167+
client.isCastClient = true;
1168+
client.name = route.getName();
1169+
client.product = route.getDescription();
1170+
client.castDevice = CastDevice.getFromBundle(route.getExtras());
1171+
client.machineIdentifier = client.castDevice.getDeviceId();
1172+
VoiceControlForPlexApplication.castClients.put(client.name, client);
1173+
Logger.d("Added cast client %s (%s)", client.name, client.machineIdentifier);
1174+
VoiceControlForPlexApplication.getInstance().prefs.put(Preferences.SAVED_CAST_CLIENTS, gsonWrite.toJson(VoiceControlForPlexApplication.castClients));
1175+
// If the "select a plex client" dialog is showing, refresh the list of clients
1176+
if(deviceSelectDialog != null && deviceSelectDialog.isShowing()) {
1177+
deviceSelectDialogRefresh();
1178+
}
11771179
}
11781180

11791181
@Override

mobile/src/main/java/com/atomjack/vcfp/activities/NowPlayingActivity.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@
44
import android.content.res.Configuration;
55
import android.os.Bundle;
66
import android.os.Handler;
7+
import android.support.v4.view.GestureDetectorCompat;
8+
import android.view.GestureDetector;
79
import android.view.Menu;
10+
import android.view.MotionEvent;
811
import android.view.View;
12+
import android.widget.ScrollView;
913
import android.widget.SeekBar;
1014

1115
import com.atomjack.shared.PlayerState;
@@ -97,7 +101,6 @@ public void run() {
97101
}
98102
}
99103

100-
101104
private void setState(PlayerState newState) {
102105
state = newState;
103106
if(state == PlayerState.PAUSED) {
@@ -143,12 +146,14 @@ public void doPlayPause(View v) {
143146
doPlayPause();
144147
}
145148

149+
@Override
146150
public void doRewind(View v) {
147151
if(position > -1) {
148152
mClient.seekTo(position - 15000, null);
149153
}
150154
}
151155

156+
@Override
152157
public void doForward(View v) {
153158
if(position > -1) {
154159
mClient.seekTo(position + 30000, null);

mobile/src/main/java/com/atomjack/vcfp/activities/PlayerActivity.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
import android.net.Uri;
66
import android.os.Bundle;
77
import android.speech.RecognizerIntent;
8+
import android.support.v4.view.GestureDetectorCompat;
9+
import android.view.GestureDetector;
10+
import android.view.MotionEvent;
811
import android.view.View;
912
import android.widget.ImageButton;
1013
import android.widget.SeekBar;
@@ -34,6 +37,8 @@ public abstract class PlayerActivity extends VCFPActivity implements SeekBar.OnS
3437
protected TextView currentTimeDisplay;
3538
protected TextView durationDisplay;
3639

40+
protected GestureDetectorCompat mDetector;
41+
3742
@Override
3843
protected void onCreate(Bundle savedInstanceState) {
3944
super.onCreate(savedInstanceState);
@@ -72,8 +77,52 @@ private void attachUIElements() {
7277
playPauseButton = (ImageButton)findViewById(R.id.playPauseButton);
7378
currentTimeDisplay = (TextView)findViewById(R.id.currentTimeView);
7479
durationDisplay = (TextView)findViewById(R.id.durationView);
80+
81+
mDetector = new GestureDetectorCompat(this, new TouchGestureListener());
82+
View nowPlayingScrollView = findViewById(R.id.nowPlayingScrollView);
83+
nowPlayingScrollView.setOnTouchListener(new View.OnTouchListener() {
84+
@Override
85+
public boolean onTouch(View v, MotionEvent event) {
86+
return mDetector.onTouchEvent(event);
87+
}
88+
});
7589
}
7690

91+
class TouchGestureListener extends GestureDetector.SimpleOnGestureListener {
92+
@Override
93+
public boolean onSingleTapUp(MotionEvent e) {
94+
Logger.d("Single tap.");
95+
doPlayPause();
96+
return true;
97+
}
98+
99+
@Override
100+
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
101+
float SWIPE_SPEED_THRESHOLD = 2000;
102+
103+
try {
104+
float diffY = e2.getY() - e1.getY();
105+
float diffX = e2.getX() - e1.getX();
106+
if (Math.abs(diffX) > Math.abs(diffY) && Math.abs(velocityX) >= SWIPE_SPEED_THRESHOLD) {
107+
108+
if (diffX > 0) {
109+
Logger.d("Doing forward via fling right");
110+
doForward(null);
111+
} else {
112+
Logger.d("Doing back via fling left");
113+
doRewind(null);
114+
}
115+
}
116+
} catch (Exception e) {
117+
e.printStackTrace();
118+
}
119+
return true;
120+
}
121+
}
122+
123+
public void doRewind(View v) {}
124+
public void doForward(View v) {}
125+
77126
protected void setCurrentTimeDisplay(long seconds) {
78127
currentTimeDisplay.setText(VoiceControlForPlexApplication.secondsToTimecode(seconds));
79128
}

0 commit comments

Comments
 (0)