" : Arrays.toString(this.namespaces.toArray());
61 |
62 | return String.format("Application{id: %s, name: %s, sessionId: %s, statusText: %s, transportId: %s,"
63 | + " isIdleScreen: %b, launchedFromCloud: %b, namespaces: %s}",
64 | this.id, this.name, this.sessionId, this.statusText, this.transportId,
65 | this.isIdleScreen, this.launchedFromCloud, namespacesString);
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/su/litvak/chromecast/api/v2/ChromeCast.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Vitaly Litvak (vitavaque@gmail.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package su.litvak.chromecast.api.v2;
17 |
18 | import javax.jmdns.JmDNS;
19 | import javax.jmdns.ServiceInfo;
20 |
21 | import java.io.IOException;
22 | import java.security.GeneralSecurityException;
23 | import java.util.HashMap;
24 | import java.util.Map;
25 |
26 | import static su.litvak.chromecast.api.v2.Util.getContentType;
27 | import static su.litvak.chromecast.api.v2.Util.getMediaTitle;
28 |
29 | /**
30 | * ChromeCast device - main object used for interaction with ChromeCast dongle.
31 | */
32 | public class ChromeCast {
33 | public static final String SERVICE_TYPE = "_googlecast._tcp.local.";
34 |
35 | private final EventListenerHolder eventListenerHolder = new EventListenerHolder();
36 |
37 | private String name;
38 | private final String address;
39 | private final int port;
40 | private String appsURL;
41 | private String application;
42 | private Channel channel;
43 | private boolean autoReconnect = true;
44 |
45 | private String title;
46 | private String appTitle;
47 | private String model;
48 |
49 | ChromeCast(JmDNS mDNS, String name) {
50 | this.name = name;
51 | ServiceInfo serviceInfo = mDNS.getServiceInfo(SERVICE_TYPE, name);
52 | this.address = serviceInfo.getInet4Addresses()[0].getHostAddress();
53 | this.port = serviceInfo.getPort();
54 | this.appsURL = serviceInfo.getURLs().length == 0 ? null : serviceInfo.getURLs()[0];
55 | this.application = serviceInfo.getApplication();
56 |
57 | this.title = serviceInfo.getPropertyString("fn");
58 | this.appTitle = serviceInfo.getPropertyString("rs");
59 | this.model = serviceInfo.getPropertyString("md");
60 | }
61 |
62 | public ChromeCast(String address) {
63 | this(address, 8009);
64 | }
65 |
66 | public ChromeCast(String address, int port) {
67 | this.address = address;
68 | this.port = port;
69 | }
70 |
71 | /**
72 | * @return The technical name of the device. Usually something like Chromecast-e28835678bc02247abcdef112341278f.
73 | */
74 | public final String getName() {
75 | return name;
76 | }
77 |
78 | public final void setName(String name) {
79 | this.name = name;
80 | }
81 |
82 | /**
83 | * @return The IP address of the device.
84 | */
85 | public final String getAddress() {
86 | return address;
87 | }
88 |
89 | /**
90 | * @return The TCP port number that the device is listening to.
91 | */
92 | public final int getPort() {
93 | return port;
94 | }
95 |
96 | public final String getAppsURL() {
97 | return appsURL;
98 | }
99 |
100 | public final void setAppsURL(String appsURL) {
101 | this.appsURL = appsURL;
102 | }
103 |
104 | /**
105 | * @return The mDNS service name. Usually "googlecast".
106 | *
107 | * @see #getRunningApp()
108 | */
109 | public final String getApplication() {
110 | return application;
111 | }
112 |
113 | public final void setApplication(String application) {
114 | this.application = application;
115 | }
116 |
117 | /**
118 | * @return The name of the device as entered by the person who installed it.
119 | * Usually something like "Living Room Chromecast".
120 | */
121 | public final String getTitle() {
122 | return title;
123 | }
124 |
125 | /**
126 | * @return The title of the app that is currently running, or empty string in case of the backdrop.
127 | * Usually something like "YouTube" or "Spotify", but could also be, say, the URL of a web page being mirrored.
128 | */
129 | public final String getAppTitle() {
130 | return appTitle;
131 | }
132 |
133 | /**
134 | * @return The model of the device. Usually "Chromecast" or, if Chromecast is built into your TV,
135 | * the model of your TV.
136 | */
137 | public final String getModel() {
138 | return model;
139 | }
140 |
141 | /**
142 | * Returns the {@link #channel}. May open it if autoReconnect
is set to "true" (default value)
143 | * and it's not yet or no longer open.
144 | * @return an open channel.
145 | */
146 | private synchronized Channel channel() throws IOException {
147 | if (autoReconnect) {
148 | try {
149 | connect();
150 | } catch (GeneralSecurityException e) {
151 | throw new IOException(e);
152 | }
153 | }
154 |
155 | return channel;
156 | }
157 |
158 | private String getTransportId(Application runningApp) {
159 | return runningApp.transportId == null ? runningApp.sessionId : runningApp.transportId;
160 | }
161 |
162 | public final synchronized void connect() throws IOException, GeneralSecurityException {
163 | if (channel == null || channel.isClosed()) {
164 | channel = new Channel(this.address, this.port, this.eventListenerHolder);
165 | channel.open();
166 | }
167 | }
168 |
169 | public final synchronized void disconnect() throws IOException {
170 | if (channel == null) {
171 | return;
172 | }
173 |
174 | channel.close();
175 | channel = null;
176 | }
177 |
178 | public final boolean isConnected() {
179 | return channel != null && !channel.isClosed();
180 | }
181 |
182 | /**
183 | * Changes behaviour for opening/closing of connection with ChromeCast device. If set to "true" (default value)
184 | * then connection will be re-established on every request in case it is not present yet, or has been lost.
185 | * "false" value means manual control over connection with ChromeCast device, i.e. calling connect()
186 | * or disconnect()
methods when needed.
187 | *
188 | * @param autoReconnect true means controlling connection with ChromeCast device automatically, false - manually
189 | * @see #connect()
190 | * @see #disconnect()
191 | */
192 | public void setAutoReconnect(boolean autoReconnect) {
193 | this.autoReconnect = autoReconnect;
194 | }
195 |
196 | /**
197 | * @return current value of autoReconnect
setting, which controls opening/closing of connection
198 | * with ChromeCast device
199 | *
200 | * @see #setAutoReconnect(boolean)
201 | */
202 | public boolean isAutoReconnect() {
203 | return autoReconnect;
204 | }
205 |
206 | /**
207 | * Set up how much time to wait until request is processed (in milliseconds).
208 | * @param requestTimeout value in milliseconds until request times out waiting for response
209 | */
210 | public void setRequestTimeout(long requestTimeout) {
211 | channel.setRequestTimeout(requestTimeout);
212 | }
213 |
214 | /**
215 | * @return current chromecast status - volume, running applications, etc.
216 | * @throws IOException
217 | */
218 | public final Status getStatus() throws IOException {
219 | return channel().getStatus();
220 | }
221 |
222 | /**
223 | * @return descriptor of currently running application
224 | * @throws IOException
225 | */
226 | public final Application getRunningApp() throws IOException {
227 | Status status = getStatus();
228 | return status.getRunningApp();
229 | }
230 |
231 | /**
232 | * @param appId application identifier
233 | * @return true if application is available to this chromecast device, false otherwise
234 | * @throws IOException
235 | */
236 | public final boolean isAppAvailable(String appId) throws IOException {
237 | return channel().isAppAvailable(appId);
238 | }
239 |
240 | /**
241 | * @param appId application identifier
242 | * @return true if application with specified identifier is running now
243 | * @throws IOException
244 | */
245 | public final boolean isAppRunning(String appId) throws IOException {
246 | Status status = getStatus();
247 | return status.getRunningApp() != null && appId.equals(status.getRunningApp().id);
248 | }
249 |
250 | /**
251 | * @param appId application identifier
252 | * @return application descriptor if app successfully launched, null otherwise
253 | * @throws IOException
254 | */
255 | public final Application launchApp(String appId) throws IOException {
256 | Status status = channel().launch(appId);
257 | return status == null ? null : status.getRunningApp();
258 | }
259 |
260 | /**
261 | * Stops currently running application
262 | *
263 | * If no application is running at the moment then exception is thrown.
264 | *
265 | * @throws IOException
266 | */
267 | public final void stopApp() throws IOException {
268 | Application runningApp = getRunningApp();
269 | if (runningApp == null) {
270 | throw new ChromeCastException("No application is running in ChromeCast");
271 | }
272 | channel().stop(runningApp.sessionId);
273 | }
274 |
275 | /**
276 | * Stops the session with the given identifier.
277 | *
278 | * @param sessionId session identifier
279 | * @throws IOException
280 | */
281 | public final void stopSession(String sessionId) throws IOException {
282 | channel().stop(sessionId);
283 | }
284 |
285 | /**
286 | * @param level volume level from 0 to 1 to set
287 | */
288 | public final void setVolume(float level) throws IOException {
289 | channel().setVolume(new Volume(level, false, Volume.DEFAULT_INCREMENT,
290 | Volume.DEFAULT_INCREMENT.doubleValue(), Volume.DEFAULT_CONTROL_TYPE));
291 | }
292 |
293 | /**
294 | * ChromeCast does not allow you to jump levels too quickly to avoid blowing speakers.
295 | * Setting by increment allows us to easily get the level we want
296 | *
297 | * @param level volume level from 0 to 1 to set
298 | * @throws IOException
299 | * @see sender
300 | */
301 | public final void setVolumeByIncrement(float level) throws IOException {
302 | Volume volume = this.getStatus().volume;
303 | float total = volume.level;
304 |
305 | if (volume.increment <= 0f) {
306 | throw new ChromeCastException("Volume.increment is <= 0");
307 | }
308 |
309 | // With floating points we always have minor decimal variations, using the Math.min/max
310 | // works around this issue
311 | // Increase volume
312 | if (level > total) {
313 | while (total < level) {
314 | total = Math.min(total + volume.increment, level);
315 | setVolume(total);
316 | }
317 | // Decrease Volume
318 | } else if (level < total) {
319 | while (total > level) {
320 | total = Math.max(total - volume.increment, level);
321 | setVolume(total);
322 | }
323 | }
324 | }
325 |
326 | /**
327 | * @param muted is to mute or not
328 | */
329 | public final void setMuted(boolean muted) throws IOException {
330 | channel().setVolume(new Volume(null, muted, Volume.DEFAULT_INCREMENT,
331 | Volume.DEFAULT_INCREMENT.doubleValue(), Volume.DEFAULT_CONTROL_TYPE));
332 | }
333 |
334 | /**
335 | * If no application is running at the moment then exception is thrown.
336 | *
337 | * @return current media status, state, time, playback rate, etc.
338 | * @throws IOException
339 | */
340 | public final MediaStatus getMediaStatus() throws IOException {
341 | Application runningApp = getRunningApp();
342 | if (runningApp == null) {
343 | throw new ChromeCastException("No application is running in ChromeCast");
344 | }
345 | return channel().getMediaStatus(getTransportId(runningApp));
346 | }
347 |
348 | /**
349 | * Resume paused media playback
350 | *
351 | * If no application is running at the moment then exception is thrown.
352 | *
353 | * @throws IOException
354 | */
355 | public final void play() throws IOException {
356 | Status status = getStatus();
357 | Application runningApp = status.getRunningApp();
358 | if (runningApp == null) {
359 | throw new ChromeCastException("No application is running in ChromeCast");
360 | }
361 | MediaStatus mediaStatus = channel().getMediaStatus(getTransportId(runningApp));
362 | if (mediaStatus == null) {
363 | throw new ChromeCastException("ChromeCast has invalid state to resume media playback");
364 | }
365 | channel().play(getTransportId(runningApp), runningApp.sessionId, mediaStatus.mediaSessionId);
366 | }
367 |
368 | /**
369 | * Pause current playback
370 | *
371 | * If no application is running at the moment then exception is thrown.
372 | *
373 | * @throws IOException
374 | */
375 | public final void pause() throws IOException {
376 | Status status = getStatus();
377 | Application runningApp = status.getRunningApp();
378 | if (runningApp == null) {
379 | throw new ChromeCastException("No application is running in ChromeCast");
380 | }
381 | MediaStatus mediaStatus = channel().getMediaStatus(getTransportId(runningApp));
382 | if (mediaStatus == null) {
383 | throw new ChromeCastException("ChromeCast has invalid state to pause media playback");
384 | }
385 | channel().pause(getTransportId(runningApp), runningApp.sessionId, mediaStatus.mediaSessionId);
386 | }
387 |
388 | /**
389 | * Moves current playback time point to specified value
390 | *
391 | * If no application is running at the moment then exception is thrown.
392 | *
393 | * @param time time point between zero and media duration
394 | * @throws IOException
395 | */
396 | public final void seek(double time) throws IOException {
397 | Status status = getStatus();
398 | Application runningApp = status.getRunningApp();
399 | if (runningApp == null) {
400 | throw new ChromeCastException("No application is running in ChromeCast");
401 | }
402 | MediaStatus mediaStatus = channel().getMediaStatus(getTransportId(runningApp));
403 | if (mediaStatus == null) {
404 | throw new ChromeCastException("ChromeCast has invalid state to seek media playback");
405 | }
406 | channel().seek(getTransportId(runningApp), runningApp.sessionId, mediaStatus.mediaSessionId, time);
407 | }
408 |
409 | /**
410 | * Loads and starts playing media in specified URL
411 | *
412 | * If no application is running at the moment then exception is thrown.
413 | *
414 | * @param url media url
415 | * @return The new media status that resulted from loading the media.
416 | * @throws IOException
417 | */
418 | public final MediaStatus load(String url) throws IOException {
419 | return load(getMediaTitle(url), null, url, getContentType(url));
420 | }
421 |
422 | /**
423 | * Loads and starts playing specified media
424 | *
425 | * If no application is running at the moment then exception is thrown.
426 | *
427 | * @param mediaTitle name to be displayed
428 | * @param thumb url of video thumbnail to be displayed, relative to media url
429 | * @param url media url
430 | * @param contentType MIME content type
431 | * @return The new media status that resulted from loading the media.
432 | * @throws IOException
433 | */
434 | public final MediaStatus load(String mediaTitle, String thumb, String url, String contentType) throws IOException {
435 | Status status = getStatus();
436 | Application runningApp = status.getRunningApp();
437 | if (runningApp == null) {
438 | throw new ChromeCastException("No application is running in ChromeCast");
439 | }
440 | Map metadata = new HashMap(2);
441 | metadata.put("title", mediaTitle);
442 | metadata.put("thumb", thumb);
443 | return channel().load(getTransportId(runningApp), runningApp.sessionId, new Media(url,
444 | contentType == null ? getContentType(url) : contentType, null, null, null,
445 | metadata, null, null), true, 0d, null);
446 | }
447 |
448 | /**
449 | * Loads and starts playing specified media
450 | *
451 | * If no application is running at the moment then exception is thrown.
452 | *
453 | * @param media The media to load and play.
454 | * See
455 | * https://developers.google.com/cast/docs/reference/messages#Load for more details.
456 | * @return The new media status that resulted from loading the media.
457 | * @throws IOException
458 | */
459 | public final MediaStatus load(final Media media) throws IOException {
460 | Status status = getStatus();
461 | Application runningApp = status.getRunningApp();
462 | if (runningApp == null) {
463 | throw new ChromeCastException("No application is running in ChromeCast");
464 | }
465 | Media mediaToPlay;
466 | if (media.contentType == null) {
467 | mediaToPlay = new Media(media.url, getContentType(media.url), media.duration, media.streamType,
468 | media.customData, media.metadata, media.textTrackStyle, media.tracks);
469 | } else {
470 | mediaToPlay = media;
471 | }
472 | return channel().load(getTransportId(runningApp), runningApp.sessionId, mediaToPlay, true, 0d, null);
473 | }
474 |
475 | /**
476 | * Sends some generic request to the currently running application.
477 | *
478 | * If no application is running at the moment then exception is thrown.
479 | *
480 | * @param namespace request namespace
481 | * @param request request object
482 | * @param responseClass class of the response for proper deserialization
483 | * @param type of response
484 | * @return deserialized response
485 | * @throws IOException
486 | */
487 | public final T send(String namespace, Request request, Class responseClass)
488 | throws IOException {
489 | Status status = getStatus();
490 | Application runningApp = status.getRunningApp();
491 | if (runningApp == null) {
492 | throw new ChromeCastException("No application is running in ChromeCast");
493 | }
494 | return channel().sendGenericRequest(getTransportId(runningApp), namespace, request, responseClass);
495 | }
496 |
497 | /**
498 | * Sends some generic request to the currently running application.
499 | * No response is expected as a result of this call.
500 | *
501 | * If no application is running at the moment then exception is thrown.
502 | *
503 | * @param namespace request namespace
504 | * @param request request object
505 | * @throws IOException
506 | */
507 | public final void send(String namespace, Request request) throws IOException {
508 | send(namespace, request, null);
509 | }
510 |
511 | public final void registerListener(ChromeCastSpontaneousEventListener listener) {
512 | this.eventListenerHolder.registerListener(listener);
513 | }
514 |
515 | public final void unregisterListener(ChromeCastSpontaneousEventListener listener) {
516 | this.eventListenerHolder.unregisterListener(listener);
517 | }
518 |
519 | public final void registerConnectionListener(ChromeCastConnectionEventListener listener) {
520 | this.eventListenerHolder.registerConnectionListener(listener);
521 | }
522 |
523 | public final void unregisterConnectionListener(ChromeCastConnectionEventListener listener) {
524 | this.eventListenerHolder.unregisterConnectionListener(listener);
525 | }
526 |
527 | @Override
528 | public final String toString() {
529 | return String.format("ChromeCast{name: %s, title: %s, model: %s, address: %s, port: %d}",
530 | this.name, this.title, this.model, this.address, this.port);
531 | }
532 | }
533 |
--------------------------------------------------------------------------------
/src/main/java/su/litvak/chromecast/api/v2/ChromeCastConnectionEvent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Vitaly Litvak (vitavaque@gmail.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package su.litvak.chromecast.api.v2;
17 |
18 | /**
19 | * Event fired when connection to ChromeCast device is either established or closed.
20 | */
21 | public class ChromeCastConnectionEvent {
22 | /**
23 | * Identifies type of event.
24 | *
25 | * true
value means connection was established.
26 | * false
value means connection was closed.
27 | */
28 | private final boolean connected;
29 |
30 | ChromeCastConnectionEvent(final boolean connected) {
31 | this.connected = connected;
32 | }
33 |
34 | public final boolean isConnected() {
35 | return connected;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/su/litvak/chromecast/api/v2/ChromeCastConnectionEventListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Vitaly Litvak (vitavaque@gmail.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package su.litvak.chromecast.api.v2;
17 |
18 | /**
19 | * The listener interface for receiving connection open/close events. The class that is interested in processing
20 | * connection events implements this interface, and object create with that class is registered
21 | * with ChromeCast
instance using the registerConnectionListener
method.
22 | * When connection event occurs, that object's connectionEventReceived
is invoked.
23 | *
24 | * @see ChromeCastConnectionEvent
25 | */
26 | public interface ChromeCastConnectionEventListener {
27 |
28 | void connectionEventReceived(ChromeCastConnectionEvent event);
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/su/litvak/chromecast/api/v2/ChromeCastException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Vitaly Litvak (vitavaque@gmail.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package su.litvak.chromecast.api.v2;
17 |
18 | import java.io.IOException;
19 |
20 | /**
21 | * Generic error, which may happen during interaction with ChromeCast device. Contains some descriptive message.
22 | */
23 | public class ChromeCastException extends IOException {
24 | public ChromeCastException(String message) {
25 | super(message);
26 | }
27 |
28 | public ChromeCastException(String message, Throwable cause) {
29 | super(message, cause);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/su/litvak/chromecast/api/v2/ChromeCastSpontaneousEvent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Vitaly Litvak (vitavaque@gmail.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package su.litvak.chromecast.api.v2;
17 |
18 | import com.fasterxml.jackson.databind.JsonNode;
19 |
20 | /**
21 | * Identifies that a broadcast message was received from "receiver application". This message was not triggered
22 | * by a sender request.
23 | *
24 | * @see
25 | * https://developers.google.com/cast/docs/reference/messages#MediaMess
26 | */
27 | public class ChromeCastSpontaneousEvent {
28 |
29 | /**
30 | * Type of a spontaneous events. Some events are expected and can contain some useful known data. For the rest
31 | * there is UNKNOWN
type of spontaneous event with generic data.
32 | */
33 | public enum SpontaneousEventType {
34 |
35 | /**
36 | * Data type will be {@link MediaStatus}.
37 | */
38 | MEDIA_STATUS(MediaStatus.class),
39 |
40 | /**
41 | * Data type will be {@link Status}.
42 | */
43 | STATUS(Status.class),
44 |
45 | /**
46 | * Data type will be {@link AppEvent}.
47 | */
48 | APPEVENT(AppEvent.class),
49 |
50 | /**
51 | * Special event usually received when session is stopped.
52 | */
53 | CLOSE(Object.class),
54 |
55 | /**
56 | * Data type will be {@link com.fasterxml.jackson.databind.JsonNode}.
57 | */
58 | UNKNOWN(JsonNode.class);
59 |
60 | private final Class> dataClass;
61 |
62 | SpontaneousEventType(Class> dataClass) {
63 | this.dataClass = dataClass;
64 | }
65 |
66 | public Class> getDataClass() {
67 | return this.dataClass;
68 | }
69 | }
70 |
71 | private final SpontaneousEventType type;
72 | private final Object data;
73 |
74 | public ChromeCastSpontaneousEvent(final SpontaneousEventType type, final Object data) {
75 | if (!type.getDataClass().isAssignableFrom(data.getClass())) {
76 | throw new IllegalArgumentException("Data type " + data.getClass() + " does not match type for event "
77 | + type.getDataClass());
78 | }
79 | this.type = type;
80 | this.data = data;
81 | }
82 |
83 | public final SpontaneousEventType getType() {
84 | return this.type;
85 | }
86 |
87 | public final Object getData() {
88 | return this.data;
89 | }
90 |
91 | public final T getData(Class cls) {
92 | if (!cls.isAssignableFrom(this.type.getDataClass())) {
93 | throw new IllegalArgumentException("Requested type " + cls + " does not match type for event "
94 | + this.type.getDataClass());
95 | }
96 | return cls.cast(this.data);
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/main/java/su/litvak/chromecast/api/v2/ChromeCastSpontaneousEventListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Vitaly Litvak (vitavaque@gmail.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package su.litvak.chromecast.api.v2;
17 |
18 | /**
19 | * The listener interface for receiving spontaneous events. The class that is interested in processing spontaneous
20 | * events implements this interface, and object create with that class is registered with ChromeCast
21 | * instance using the registerListener
method. When spontaneous event occurs, that object's
22 | * spontaneousEventReceived
is invoked.
23 | *
24 | * @see ChromeCastSpontaneousEvent
25 | * @see
26 | * https://developers.google.com/cast/docs/reference/messages#MediaMess
27 | */
28 | public interface ChromeCastSpontaneousEventListener {
29 |
30 | void spontaneousEventReceived(ChromeCastSpontaneousEvent event);
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/su/litvak/chromecast/api/v2/ChromeCasts.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Vitaly Litvak (vitavaque@gmail.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package su.litvak.chromecast.api.v2;
17 |
18 | import javax.jmdns.JmDNS;
19 | import javax.jmdns.ServiceEvent;
20 | import javax.jmdns.ServiceListener;
21 |
22 | import java.io.IOException;
23 | import java.net.InetAddress;
24 | import java.util.ArrayList;
25 | import java.util.Collections;
26 | import java.util.List;
27 |
28 | /**
29 | * Utility class that discovers ChromeCast devices and holds references to all of them.
30 | */
31 | public final class ChromeCasts {
32 | private static final ChromeCasts INSTANCE = new ChromeCasts();
33 | private final MyServiceListener listener = new MyServiceListener();
34 |
35 | private JmDNS mDNS;
36 |
37 | private final List listeners = new ArrayList();
38 | private final List chromeCasts = Collections.synchronizedList(new ArrayList());
39 |
40 | private ChromeCasts() {
41 | }
42 |
43 | /** Returns a copy of the currently seen chrome casts.
44 | * @return a copy of the currently seen chromecast devices.
45 | */
46 | public static List get() {
47 | return new ArrayList(INSTANCE.chromeCasts);
48 | }
49 |
50 | /** Hidden service listener to receive callbacks.
51 | * Is hidden to avoid messing with it.
52 | */
53 | private class MyServiceListener implements ServiceListener {
54 | @Override
55 | public void serviceAdded(ServiceEvent se) {
56 | if (se.getInfo() != null) {
57 | ChromeCast device = new ChromeCast(mDNS, se.getInfo().getName());
58 | chromeCasts.add(device);
59 | for (ChromeCastsListener nextListener : listeners) {
60 | nextListener.newChromeCastDiscovered(device);
61 | }
62 | }
63 | }
64 |
65 | @Override
66 | public void serviceRemoved(ServiceEvent se) {
67 | if (ChromeCast.SERVICE_TYPE.equals(se.getType())) {
68 | // We have a ChromeCast device unregistering
69 | List copy = get();
70 | ChromeCast deviceRemoved = null;
71 | // Probably better keep a map to better lookup devices
72 | for (ChromeCast device : copy) {
73 | if (device.getName().equals(se.getInfo().getName())) {
74 | deviceRemoved = device;
75 | chromeCasts.remove(device);
76 | break;
77 | }
78 | }
79 | if (deviceRemoved != null) {
80 | for (ChromeCastsListener nextListener : listeners) {
81 | nextListener.chromeCastRemoved(deviceRemoved);
82 | }
83 | }
84 | }
85 | }
86 |
87 | @Override
88 | public void serviceResolved(ServiceEvent se) {
89 | // intentionally blank
90 | }
91 | }
92 |
93 | private void doStartDiscovery(InetAddress addr) throws IOException {
94 | if (mDNS == null) {
95 | chromeCasts.clear();
96 |
97 | if (addr != null) {
98 | mDNS = JmDNS.create(addr);
99 | } else {
100 | mDNS = JmDNS.create();
101 | }
102 | mDNS.addServiceListener(ChromeCast.SERVICE_TYPE, listener);
103 | }
104 | }
105 |
106 | private void doStopDiscovery() throws IOException {
107 | if (mDNS != null) {
108 | mDNS.close();
109 | mDNS = null;
110 | }
111 | }
112 |
113 | /**
114 | * Starts ChromeCast device discovery.
115 | */
116 | public static void startDiscovery() throws IOException {
117 | INSTANCE.doStartDiscovery(null);
118 | }
119 |
120 | /**
121 | * Starts ChromeCast device discovery.
122 | *
123 | * @param addr the address of the interface that should be used for discovery
124 | */
125 | public static void startDiscovery(InetAddress addr) throws IOException {
126 | INSTANCE.doStartDiscovery(addr);
127 | }
128 |
129 | /**
130 | * Stops ChromeCast device discovery.
131 | */
132 | public static void stopDiscovery() throws IOException {
133 | INSTANCE.doStopDiscovery();
134 | }
135 |
136 | /**
137 | * Restarts discovery by sequentially calling 'stop' and 'start' methods.
138 | */
139 | public static void restartDiscovery() throws IOException {
140 | stopDiscovery();
141 | startDiscovery();
142 | }
143 |
144 | /**
145 | * Restarts discovery by sequentially calling 'stop' and 'start' methods.
146 | *
147 | * @param addr the address of the interface that should be used for discovery
148 | */
149 | public static void restartDiscovery(InetAddress addr) throws IOException {
150 | stopDiscovery();
151 | startDiscovery(addr);
152 | }
153 |
154 | public static void registerListener(ChromeCastsListener listener) {
155 | if (listener != null) {
156 | INSTANCE.listeners.add(listener);
157 | }
158 | }
159 |
160 | public static void unregisterListener(ChromeCastsListener listener) {
161 | if (listener != null) {
162 | INSTANCE.listeners.remove(listener);
163 | }
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/src/main/java/su/litvak/chromecast/api/v2/ChromeCastsListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Vitaly Litvak (vitavaque@gmail.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package su.litvak.chromecast.api.v2;
17 |
18 | /**
19 | * The listener interface for discovering ChromeCast devices.
20 | */
21 | public interface ChromeCastsListener {
22 |
23 | void newChromeCastDiscovered(ChromeCast chromeCast);
24 |
25 | void chromeCastRemoved(ChromeCast chromeCast);
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/su/litvak/chromecast/api/v2/Device.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Vitaly Litvak (vitavaque@gmail.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package su.litvak.chromecast.api.v2;
17 |
18 | import com.fasterxml.jackson.annotation.JsonProperty;
19 |
20 | /**
21 | * Device descriptor.
22 | */
23 | public class Device {
24 | public final String name;
25 | public final int capabilities;
26 | public final String deviceId;
27 | public final Volume volume;
28 |
29 | public Device(@JsonProperty("name") String name,
30 | @JsonProperty("capabilities") int capabilities,
31 | @JsonProperty("deviceId") String deviceId,
32 | @JsonProperty("volume") Volume volume) {
33 | this.name = name;
34 | this.capabilities = capabilities;
35 | this.deviceId = deviceId;
36 | this.volume = volume;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/su/litvak/chromecast/api/v2/EventListenerHolder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Vitaly Litvak (vitavaque@gmail.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package su.litvak.chromecast.api.v2;
17 |
18 | import com.fasterxml.jackson.databind.JsonMappingException;
19 | import com.fasterxml.jackson.databind.JsonNode;
20 | import com.fasterxml.jackson.databind.ObjectMapper;
21 | import su.litvak.chromecast.api.v2.ChromeCastSpontaneousEvent.SpontaneousEventType;
22 |
23 | import java.io.IOException;
24 | import java.util.Set;
25 | import java.util.concurrent.CopyOnWriteArraySet;
26 |
27 | /**
28 | * Helper class for delivering spontaneous events to their listeners.
29 | */
30 | class EventListenerHolder implements ChromeCastSpontaneousEventListener, ChromeCastConnectionEventListener {
31 |
32 | private final ObjectMapper jsonMapper = JacksonHelper.createJSONMapper();
33 | private final Set eventListeners =
34 | new CopyOnWriteArraySet();
35 | private final Set eventListenersConnection =
36 | new CopyOnWriteArraySet();
37 |
38 | EventListenerHolder() {}
39 |
40 | public void registerListener(ChromeCastSpontaneousEventListener listener) {
41 | if (listener != null) {
42 | this.eventListeners.add(listener);
43 | }
44 | }
45 |
46 | public void unregisterListener(ChromeCastSpontaneousEventListener listener) {
47 | if (listener != null) {
48 | this.eventListeners.remove(listener);
49 | }
50 | }
51 |
52 | public void deliverEvent(JsonNode json) throws IOException {
53 | if (json == null || this.eventListeners.isEmpty()) {
54 | return;
55 | }
56 |
57 | StandardResponse resp;
58 | if (json.has("responseType")) {
59 | try {
60 | resp = this.jsonMapper.treeToValue(json, StandardResponse.class);
61 | } catch (JsonMappingException jme) {
62 | resp = null;
63 | }
64 | } else {
65 | resp = null;
66 | }
67 |
68 | /*
69 | * The documentation only mentions MEDIA_STATUS as being a possible spontaneous event.
70 | * Though RECEIVER_STATUS has also been observed.
71 | * If others are observed, they should be added here.
72 | * see: https://developers.google.com/cast/docs/reference/messages#MediaMess
73 | */
74 | if (resp instanceof StandardResponse.MediaStatus) {
75 | StandardResponse.MediaStatus mediaStatusResponse = (StandardResponse.MediaStatus) resp;
76 | // it may be a single media status event
77 | if (mediaStatusResponse.statuses == null) {
78 | if (json.has("media")) {
79 | try {
80 | MediaStatus ms = jsonMapper.treeToValue(json, MediaStatus.class);
81 | spontaneousEventReceived(new ChromeCastSpontaneousEvent(SpontaneousEventType.MEDIA_STATUS, ms));
82 | } catch (JsonMappingException jme) {
83 | // ignored
84 | }
85 | }
86 | } else {
87 | for (final MediaStatus ms : mediaStatusResponse.statuses) {
88 | spontaneousEventReceived(new ChromeCastSpontaneousEvent(SpontaneousEventType.MEDIA_STATUS, ms));
89 | }
90 | }
91 | } else if (resp instanceof StandardResponse.Status) {
92 | spontaneousEventReceived(new ChromeCastSpontaneousEvent(SpontaneousEventType.STATUS,
93 | ((StandardResponse.Status) resp).status));
94 | } else if (resp instanceof StandardResponse.Close) {
95 | spontaneousEventReceived(new ChromeCastSpontaneousEvent(SpontaneousEventType.CLOSE, new Object()));
96 | } else {
97 | spontaneousEventReceived(new ChromeCastSpontaneousEvent(SpontaneousEventType.UNKNOWN, json));
98 | }
99 | }
100 |
101 | public void deliverAppEvent(AppEvent event) throws IOException {
102 | spontaneousEventReceived(new ChromeCastSpontaneousEvent(SpontaneousEventType.APPEVENT, event));
103 | }
104 |
105 | @Override
106 | public void spontaneousEventReceived(ChromeCastSpontaneousEvent event) {
107 | for (ChromeCastSpontaneousEventListener listener : this.eventListeners) {
108 | listener.spontaneousEventReceived(event);
109 | }
110 | }
111 |
112 | public void registerConnectionListener(ChromeCastConnectionEventListener listener) {
113 | if (listener != null) {
114 | this.eventListenersConnection.add(listener);
115 | }
116 | }
117 |
118 | public void unregisterConnectionListener(ChromeCastConnectionEventListener listener) {
119 | if (listener != null) {
120 | this.eventListenersConnection.remove(listener);
121 | }
122 | }
123 |
124 | public void deliverConnectionEvent(boolean connected) {
125 | connectionEventReceived(new ChromeCastConnectionEvent(connected));
126 | }
127 |
128 | @Override
129 | public void connectionEventReceived(ChromeCastConnectionEvent event) {
130 | for (ChromeCastConnectionEventListener listener : this.eventListenersConnection) {
131 | listener.connectionEventReceived(event);
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/main/java/su/litvak/chromecast/api/v2/Item.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Vitaly Litvak (vitavaque@gmail.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package su.litvak.chromecast.api.v2;
17 |
18 | import com.fasterxml.jackson.annotation.JsonProperty;
19 |
20 | import java.util.Arrays;
21 | import java.util.Collections;
22 | import java.util.Map;
23 |
24 | /**
25 | * Media item.
26 | */
27 | public class Item {
28 |
29 | public final boolean autoplay;
30 | public final Map customData;
31 | public final Media media;
32 | public final long id;
33 |
34 | public Item(@JsonProperty("autoplay") boolean autoplay,
35 | @JsonProperty("customData") Map customData,
36 | @JsonProperty("itemId") long id,
37 | @JsonProperty("media") Media media) {
38 | this.autoplay = autoplay;
39 | this.customData = customData != null ? Collections.unmodifiableMap(customData) : null;
40 | this.id = id;
41 | this.media = media;
42 | }
43 |
44 | @Override
45 | public final int hashCode() {
46 | return Arrays.hashCode(new Object[] {this.autoplay, this.customData, this.id, this.media});
47 | }
48 |
49 | @Override
50 | public final boolean equals(Object obj) {
51 | if (obj == null) {
52 | return false;
53 | }
54 | if (obj == this) {
55 | return true;
56 | }
57 | if (!(obj instanceof Item)) {
58 | return false;
59 | }
60 | final Item that = (Item) obj;
61 | return this.autoplay == that.autoplay
62 | && this.customData == null ? that.customData == null : this.customData.equals(that.customData)
63 | && this.id == that.id
64 | && this.media == null ? that.media == null : this.media.equals(that.media);
65 | }
66 |
67 | @Override
68 | public final String toString() {
69 | return String.format("Item{id: %s, media: %s}", this.id, this.media);
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/src/main/java/su/litvak/chromecast/api/v2/JacksonHelper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Vitaly Litvak (vitavaque@gmail.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package su.litvak.chromecast.api.v2;
17 |
18 | import com.fasterxml.jackson.databind.DeserializationFeature;
19 | import com.fasterxml.jackson.databind.ObjectMapper;
20 |
21 | /**
22 | * Utility class for creating pre-configured instances of JSON mapper.
23 | */
24 | final class JacksonHelper {
25 | private JacksonHelper() {}
26 |
27 | static ObjectMapper createJSONMapper() {
28 | ObjectMapper jsonMapper = new ObjectMapper();
29 | jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
30 | return jsonMapper;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/su/litvak/chromecast/api/v2/Media.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Vitaly Litvak (vitavaque@gmail.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package su.litvak.chromecast.api.v2;
17 |
18 | import com.fasterxml.jackson.annotation.JsonIgnore;
19 | import com.fasterxml.jackson.annotation.JsonInclude;
20 | import com.fasterxml.jackson.annotation.JsonProperty;
21 |
22 | import java.util.Arrays;
23 | import java.util.Collections;
24 | import java.util.List;
25 | import java.util.Map;
26 |
27 | import static su.litvak.chromecast.api.v2.Media.MetadataType.GENERIC;
28 |
29 | /**
30 | * Media streamed on ChromeCast device.
31 | *
32 | * @see
33 | * https://developers.google.com/cast/docs/reference/receiver/cast.receiver.media.MediaInformation
34 | */
35 | public class Media {
36 | public static final String METADATA_TYPE = "metadataType";
37 | public static final String METADATA_ALBUM_ARTIST = "albumArtist";
38 | public static final String METADATA_ALBUM_NAME = "albumName";
39 | public static final String METADATA_ARTIST = "artist";
40 | public static final String METADATA_BROADCAST_DATE = "broadcastDate";
41 | public static final String METADATA_COMPOSER = "composer";
42 | public static final String METADATA_CREATION_DATE = "creationDate";
43 | public static final String METADATA_DISC_NUMBER = "discNumber";
44 | public static final String METADATA_EPISODE_NUMBER = "episodeNumber";
45 | public static final String METADATA_HEIGHT = "height";
46 | public static final String METADATA_IMAGES = "images";
47 | public static final String METADATA_LOCATION_NAME = "locationName";
48 | public static final String METADATA_LOCATION_LATITUDE = "locationLatitude";
49 | public static final String METADATA_LOCATION_LONGITUDE = "locationLongitude";
50 | public static final String METADATA_RELEASE_DATE = "releaseDate";
51 | public static final String METADATA_SEASON_NUMBER = "seasonNumber";
52 | public static final String METADATA_SERIES_TITLE = "seriesTitle";
53 | public static final String METADATA_STUDIO = "studio";
54 | public static final String METADATA_SUBTITLE = "subtitle";
55 | public static final String METADATA_TITLE = "title";
56 | public static final String METADATA_TRACK_NUMBER = "trackNumber";
57 | public static final String METADATA_WIDTH = "width";
58 |
59 | /**
60 | * Type of the data found inside {@link #metadata}. You can access the type with the key {@link #METADATA_TYPE}.
61 | *
62 | * You can access known metadata types using the constants in {@link Media}, such as {@link #METADATA_ALBUM_NAME}.
63 | *
64 | * @see
65 | * https://developers.google.com/cast/docs/reference/ios/interface_g_c_k_media_metadata
66 | * @see
67 | * https://developers.google.com/android/reference/com/google/android/gms/cast/MediaMetadata
68 | */
69 | public enum MetadataType {
70 | GENERIC,
71 | MOVIE,
72 | TV_SHOW,
73 | MUSIC_TRACK,
74 | PHOTO
75 | }
76 |
77 | /**
78 | * Stream type.
79 | *
80 | * Some receivers use upper-case (like Pandora), some use lower-case (like Google Audio),
81 | * duplicate elements to support both.
82 | *
83 | * @see
84 | * https://developers.google.com/cast/docs/reference/receiver/cast.receiver.media#.StreamType
85 | */
86 | public enum StreamType {
87 | BUFFERED, buffered,
88 | LIVE, live,
89 | NONE, none
90 | }
91 |
92 | @JsonProperty
93 | @JsonInclude(JsonInclude.Include.NON_NULL)
94 | public final Map metadata;
95 |
96 | @JsonProperty("contentId")
97 | public final String url;
98 |
99 | @JsonProperty
100 | @JsonInclude(JsonInclude.Include.NON_NULL)
101 | public final Double duration;
102 |
103 | @JsonProperty
104 | @JsonInclude(JsonInclude.Include.NON_NULL)
105 | public final StreamType streamType;
106 |
107 | @JsonProperty
108 | public final String contentType;
109 |
110 | @JsonProperty
111 | @JsonInclude(JsonInclude.Include.NON_NULL)
112 | public final Map customData;
113 |
114 | @JsonIgnore
115 | public final Map textTrackStyle;
116 |
117 | @JsonIgnore
118 | public final List