Skip to content

Commit 36c36d8

Browse files
author
Chris Bellew
committed
Initial work on audio/subtitle stream support (Chromecast not working yet).
1 parent 8d14057 commit 36c36d8

27 files changed

+349
-40
lines changed

Voice Control For Plex/mobile.iml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,8 @@
9999
<orderEntry type="library" exported="" name="library-1.0.30" level="project" />
100100
<orderEntry type="library" exported="" name="gson-2.3.1" level="project" />
101101
<orderEntry type="library" exported="" name="mediarouter-v7-23.0.0" level="project" />
102-
<orderEntry type="library" exported="" name="stax-1.2.0" level="project" />
103102
<orderEntry type="library" exported="" name="okhttp-2.5.0" level="project" />
103+
<orderEntry type="library" exported="" name="stax-1.2.0" level="project" />
104104
<orderEntry type="library" exported="" name="okio-1.6.0" level="project" />
105105
<orderEntry type="library" exported="" name="play-services-wearable-7.8.0" level="project" />
106106
<orderEntry type="library" exported="" name="simple-xml-2.7.1" level="project" />

mobile/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ apply plugin: 'com.android.application'
33
android {
44
useLibrary 'org.apache.http.legacy'
55
compileSdkVersion 23
6-
buildToolsVersion '23.0.0'
6+
buildToolsVersion '23.0.2'
77
defaultConfig {
88
applicationId "com.atomjack.vcfp"
99
minSdkVersion 15
@@ -107,7 +107,7 @@ dependencies {
107107
wearApp project(':wear')
108108
compile 'org.apache.httpcomponents:httpclient-android:4.3.5.1'
109109
compile 'com.squareup.retrofit:retrofit:2.0.0-beta1'
110-
compile ('com.squareup.retrofit:converter-simplexml:2.0.0-beta1') {
110+
compile('com.squareup.retrofit:converter-simplexml:2.0.0-beta1') {
111111
exclude group: 'xpp3', module: 'xpp3'
112112
exclude group: 'stax', module: 'stax-api'
113113
exclude group: 'stax', module: 'stax'

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ public void doPrevious(View v) {
347347
castPlayerManager.doPrevious();
348348
}
349349

350-
@Override
350+
@Override
351351
public void onStopTrackingTouch(SeekBar _seekBar) {
352352
Logger.d("stopped changing progress: %d", _seekBar.getProgress());
353353
try {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ public void onFailure(Throwable error) {
191191
});
192192
}
193193

194-
@Override
194+
@Override
195195
protected void onPause() {
196196
super.onPause();
197197
Logger.d("NowPlaying onPause");

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

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
package com.atomjack.vcfp.activities;
22

3+
import android.app.AlertDialog;
4+
import android.app.Dialog;
35
import android.app.PendingIntent;
46
import android.content.Intent;
57
import android.net.Uri;
68
import android.os.Bundle;
79
import android.speech.RecognizerIntent;
810
import android.support.v4.view.GestureDetectorCompat;
911
import android.view.GestureDetector;
12+
import android.view.LayoutInflater;
1013
import android.view.MotionEvent;
1114
import android.view.View;
15+
import android.widget.AdapterView;
1216
import android.widget.ImageButton;
1317
import android.widget.SeekBar;
18+
import android.widget.Spinner;
1419
import android.widget.TextView;
1520

1621
import com.atomjack.shared.Logger;
@@ -19,15 +24,18 @@
1924
import com.atomjack.shared.WearConstants;
2025
import com.atomjack.vcfp.R;
2126
import com.atomjack.vcfp.VoiceControlForPlexApplication;
27+
import com.atomjack.vcfp.adapters.StreamAdapter;
2228
import com.atomjack.vcfp.model.PlexMedia;
2329
import com.atomjack.vcfp.model.PlexServer;
2430
import com.atomjack.vcfp.model.PlexTrack;
2531
import com.atomjack.vcfp.model.PlexVideo;
32+
import com.atomjack.vcfp.model.Stream;
2633
import com.atomjack.vcfp.services.PlexSearchService;
2734
import com.google.android.gms.wearable.DataMap;
2835

2936
import java.math.BigInteger;
3037
import java.security.SecureRandom;
38+
import java.util.List;
3139

3240
public abstract class PlayerActivity extends VCFPActivity implements SeekBar.OnSeekBarChangeListener {
3341
protected boolean resumePlayback;
@@ -39,6 +47,8 @@ public abstract class PlayerActivity extends VCFPActivity implements SeekBar.OnS
3947

4048
protected GestureDetectorCompat mDetector;
4149

50+
protected Dialog mediaOptionsDialog;
51+
4252
@Override
4353
protected void onCreate(Bundle savedInstanceState) {
4454
super.onCreate(savedInstanceState);
@@ -73,6 +83,64 @@ public void doMic(View v) {
7383
}
7484
}
7585

86+
// Open an alert to allow selection of currently playing media's audio and/or subtitle options
87+
public void doMediaOptions(View v) {
88+
if(nowPlayingMedia == null) {
89+
return;
90+
}
91+
92+
final List<Stream> audioStreams = nowPlayingMedia.getStreams(Stream.AUDIO);
93+
final List<Stream> subtitleStreams = nowPlayingMedia.getStreams(Stream.SUBTITLE);
94+
95+
AlertDialog.Builder builder = new AlertDialog.Builder(this);
96+
LayoutInflater inflater = getLayoutInflater();
97+
View layout = inflater.inflate(R.layout.media_options_dialog, null);
98+
99+
Spinner subtitlesSpinner = (Spinner)layout.findViewById(R.id.subtitlesSpinner);
100+
StreamAdapter subtitlesStreamAdapter = new StreamAdapter(this, android.R.layout.simple_spinner_dropdown_item, subtitleStreams);
101+
subtitlesSpinner.setAdapter(subtitlesStreamAdapter);
102+
subtitlesSpinner.setSelection(subtitleStreams.indexOf(nowPlayingMedia.getActiveStream(Stream.SUBTITLE)), false);
103+
104+
subtitlesSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
105+
@Override
106+
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
107+
Stream stream = subtitleStreams.get(position);
108+
if(!stream.isActive()) {
109+
mClient.setStream(stream);
110+
nowPlayingMedia.setActiveStream(stream);
111+
}
112+
}
113+
114+
@Override
115+
public void onNothingSelected(AdapterView<?> parent) {
116+
}
117+
});
118+
Spinner audioSpinner = (Spinner)layout.findViewById(R.id.audioSpinner);
119+
StreamAdapter audioStreamAdapter = new StreamAdapter(this, android.R.layout.simple_spinner_dropdown_item, audioStreams);
120+
audioSpinner.setAdapter(audioStreamAdapter);
121+
audioSpinner.setSelection(audioStreams.indexOf(nowPlayingMedia.getActiveStream(Stream.AUDIO)), false);
122+
123+
audioSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
124+
@Override
125+
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
126+
Stream stream = audioStreams.get(position);
127+
if(!stream.isActive()) {
128+
mClient.setStream(stream);
129+
nowPlayingMedia.setActiveStream(stream);
130+
}
131+
}
132+
133+
@Override
134+
public void onNothingSelected(AdapterView<?> parent) {
135+
}
136+
});
137+
138+
builder.setView(layout);
139+
builder.setTitle(getResources().getString(R.string.stream_selection));
140+
mediaOptionsDialog = builder.create();
141+
mediaOptionsDialog.show();
142+
}
143+
76144
private void attachUIElements() {
77145
playPauseButton = (ImageButton)findViewById(R.id.playPauseButton);
78146
currentTimeDisplay = (TextView)findViewById(R.id.currentTimeView);

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@
7777

7878
import cz.fhucho.android.util.SimpleDiskCache;
7979

80-
public abstract class VCFPActivity extends AppCompatActivity implements PlexSubscription.PlexListener, CastPlayerManager.CastListener, VoiceControlForPlexApplication.NetworkChangeListener {
80+
public abstract class VCFPActivity extends AppCompatActivity implements PlexSubscription.PlexListener,
81+
CastPlayerManager.CastListener, VoiceControlForPlexApplication.NetworkChangeListener {
8182
protected PlexMedia nowPlayingMedia;
8283
protected boolean subscribing = false;
8384
protected PlexClient mClient;
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.atomjack.vcfp.adapters;
2+
3+
import android.content.Context;
4+
import android.view.LayoutInflater;
5+
import android.view.View;
6+
import android.view.ViewGroup;
7+
import android.widget.ArrayAdapter;
8+
import android.widget.TextView;
9+
10+
import com.atomjack.vcfp.model.Stream;
11+
12+
import java.util.List;
13+
14+
public class StreamAdapter extends ArrayAdapter<Stream> {
15+
Context context;
16+
private List<Stream> streams;
17+
18+
public StreamAdapter(Context c, int resource, List<Stream> s) {
19+
super(c, resource, s);
20+
streams = s;
21+
context = c;
22+
}
23+
24+
@Override
25+
public int getCount() {
26+
return streams.size();
27+
}
28+
29+
@Override
30+
public Stream getItem(int position) {
31+
return streams.get(position);
32+
}
33+
34+
@Override
35+
public long getItemId(int position) {
36+
return position;
37+
}
38+
39+
@Override
40+
public View getDropDownView(int position, View convertView, ViewGroup parent) {
41+
if (convertView == null) {
42+
LayoutInflater vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
43+
convertView = vi.inflate(com.atomjack.vcfp.R.layout.stream_dropdown_item, parent, false);
44+
}
45+
((TextView) convertView).setText(streams.get(position).getTitle());
46+
return convertView;
47+
48+
}
49+
50+
@Override
51+
public View getView(int position, View convertView, ViewGroup parent) {
52+
TextView textView = (TextView) View.inflate(context, android.R.layout.simple_spinner_item, null);
53+
textView.setText(streams.get(position).getTitle());
54+
return textView;
55+
}
56+
}

mobile/src/main/java/com/atomjack/vcfp/model/PlexClient.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@
1414

1515
import java.util.ArrayList;
1616
import java.util.Arrays;
17+
import java.util.HashMap;
1718

1819
import retrofit.Call;
20+
import retrofit.Callback;
21+
import retrofit.Response;
1922

2023
@Root(name="Server", strict=false)
2124
public class PlexClient extends PlexDevice {
@@ -162,4 +165,31 @@ public String toString() {
162165

163166
return output;
164167
}
168+
169+
public void setStream(Stream stream) {
170+
if(isCastClient) {
171+
// TODO: Implement
172+
} else {
173+
PlexHttpClient.PlexHttpService service = PlexHttpClient.getService(String.format("http://%s:%s", address, port));
174+
HashMap<String, String> qs = new HashMap<>();
175+
if (stream.streamType == Stream.AUDIO) {
176+
qs.put("audioStreamID", stream.id);
177+
} else if (stream.streamType == Stream.SUBTITLE) {
178+
qs.put("subtitleStreamID", stream.id);
179+
}
180+
Call<PlexResponse> call = service.setStreams(qs);
181+
call.enqueue(new Callback<PlexResponse>() {
182+
@Override
183+
public void onResponse(Response<PlexResponse> response) {
184+
Logger.d("setStream response: %s", response.body().status);
185+
}
186+
187+
@Override
188+
public void onFailure(Throwable t) {
189+
190+
}
191+
});
192+
193+
}
194+
}
165195
}

mobile/src/main/java/com/atomjack/vcfp/model/PlexMedia.java

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -167,24 +167,63 @@ public int describeContents() {
167167
return 0;
168168
}
169169

170+
private List<Stream> streams;
171+
170172
public List<Stream> getStreams() {
171-
Media m = media.get(0);
172-
Part p = m.parts.get(0);
173-
return p.streams;
173+
// The list of streams needs to have a "none" subtitle stream added to it. Subsequent calls
174+
// to get the list of streams should get this list, since any manipulation of which (audio/video)
175+
// stream is active will need to be saved to it - the list of streams in media/parts will not reflect
176+
// the updating of active streams.
177+
if (streams == null) {
178+
streams = new ArrayList<>();
179+
Media m = media.get(0);
180+
Part p = m.parts.get(0);
181+
182+
List<Stream> ss = new ArrayList<>();
183+
Stream none = Stream.getNoneSubtitleStream();
184+
for (int i = 0; i < p.streams.size(); i++) {
185+
if (p.streams.get(i).streamType == Stream.SUBTITLE && !ss.contains(none)) {
186+
ss.add(none);
187+
}
188+
ss.add(p.streams.get(i));
189+
}
190+
boolean subsActive = false;
191+
for (Stream s : ss) {
192+
if(s.streamType == Stream.SUBTITLE && s.isActive())
193+
subsActive = true;
194+
}
195+
if(!subsActive)
196+
none.setActive(true);
197+
streams = ss;
198+
}
199+
return streams;
174200
}
175201

176202
public List<Stream> getStreams(int streamType) {
177203
List<Stream> s = new ArrayList<Stream>();
178-
try {
179-
Media m = media.get(0);
180-
Part p = m.parts.get(0);
204+
for (Stream stream : getStreams()) {
205+
if (stream.streamType == streamType)
206+
s.add(stream);
207+
}
208+
return s;
209+
}
210+
211+
public Stream getActiveStream(int streamType) {
212+
List<Stream> streams = getStreams(streamType);
213+
for(Stream stream : streams) {
214+
if(stream.isActive())
215+
return stream;
216+
}
181217

182-
for (Stream stream : p.streams) {
183-
if (stream.streamType == streamType)
184-
s.add(stream);
218+
return streams.get(0);
219+
}
220+
221+
public void setActiveStream(Stream s) {
222+
for(Stream ss : streams) {
223+
if(ss.streamType == s.streamType) {
224+
ss.setActive(s.id.equals(ss.id));
185225
}
186-
} catch (Exception ex) {}
187-
return s;
226+
}
188227
}
189228

190229
public PlexMedia() {
@@ -279,7 +318,7 @@ class Part implements Parcelable {
279318
@Attribute(required=false)
280319
public String id;
281320
@ElementList(required=false, inline=true, entry="Stream")
282-
public List<Stream> streams = new ArrayList<Stream>();
321+
public List<Stream> streams = new ArrayList<>();
283322
@Attribute(required=false)
284323
public String key;
285324
@Attribute(required=false)
@@ -291,7 +330,7 @@ public Part() {
291330

292331
public Part(Parcel in) {
293332
id = in.readString();
294-
streams = new ArrayList<Stream>();
333+
streams = new ArrayList<>();
295334
in.readTypedList(streams, Stream.CREATOR);
296335
key = in.readString();
297336
duration = in.readInt();

mobile/src/main/java/com/atomjack/vcfp/model/PlexStream.java

Lines changed: 0 additions & 8 deletions
This file was deleted.

0 commit comments

Comments
 (0)