/*
* Copyright (C) 2013 ENTERTAILION, LLC. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.entertailion.java.caster;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Properties;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.entertailion.java.caster.HttpServer.Response;
/**
* Command line ChromeCast client: java -jar caster.jar -h
* https://github.com/entertailion/Caster
*
* @author leon_nicholls
*
*/
public class Main {
private static final String LOG_TAG = "Main";
// TODO Add your own app id here
private static final String APP_ID = "YOUR_APP_ID";
public static final String VERSION = "0.4";
private static Platform platform = new Platform();
private static String appId = APP_ID;
/**
* @param args
*/
public static void main(String[] args) {
// http://commons.apache.org/proper/commons-cli/usage.html
Option help = new Option("h", "help", false, "Print this help message");
Option version = new Option("V", "version", false, "Print version information");
Option list = new Option("l", "list", false, "List ChromeCast devices");
Option verbose = new Option("v", "verbose", false, "Verbose debug logging");
Option transcode = new Option("t", "transcode", false, "Transcode media; -f also required");
Option rest = new Option("r", "rest", false, "REST API server");
Option url = OptionBuilder.withLongOpt("stream").hasArg().withValueSeparator().withDescription("HTTP URL for streaming content; -d also required")
.create("s");
Option server = OptionBuilder.withLongOpt("device").hasArg().withValueSeparator().withDescription("ChromeCast device IP address").create("d");
Option id = OptionBuilder.withLongOpt("app-id").hasArg().withValueSeparator().withDescription("App ID for whitelisted device").create("id");
Option mediaFile = OptionBuilder.withLongOpt("file").hasArg().withValueSeparator().withDescription("Local media file; -d also required").create("f");
Option transcodingParameters = OptionBuilder.withLongOpt("transcode-parameters").hasArg().withValueSeparator()
.withDescription("Transcode parameters; -t also required").create("tp");
Option restPort = OptionBuilder.withLongOpt("rest-port").hasArg().withValueSeparator().withDescription("REST API port; default 8080")
.create("rp");
Options options = new Options();
options.addOption(help);
options.addOption(version);
options.addOption(list);
options.addOption(verbose);
options.addOption(url);
options.addOption(server);
options.addOption(id);
options.addOption(mediaFile);
options.addOption(transcode);
options.addOption(transcodingParameters);
options.addOption(rest);
options.addOption(restPort);
// create the command line parser
CommandLineParser parser = new PosixParser();
//String[] arguments = new String[] { "-vr" };
try {
// parse the command line arguments
CommandLine line = parser.parse(options, args);
Option[] lineOptions = line.getOptions();
if (lineOptions.length == 0) {
System.out.println("caster: try 'java -jar caster.jar -h' for more information");
System.exit(0);
}
Log.setVerbose(line.hasOption("v"));
// Custom app-id
if (line.hasOption("id")) {
Log.d(LOG_TAG, line.getOptionValue("id"));
appId = line.getOptionValue("id");
}
// Print version
if (line.hasOption("V")) {
System.out.println("Caster version " + VERSION);
}
// List ChromeCast devices
if (line.hasOption("l")) {
final DeviceFinder deviceFinder = new DeviceFinder(new DeviceFinderListener() {
@Override
public void discoveringDevices(DeviceFinder deviceFinder) {
Log.d(LOG_TAG, "discoveringDevices");
}
@Override
public void discoveredDevices(DeviceFinder deviceFinder) {
Log.d(LOG_TAG, "discoveredDevices");
TrackedDialServers trackedDialServers = deviceFinder.getTrackedDialServers();
for (DialServer dialServer : trackedDialServers) {
System.out.println(dialServer.toString()); // keep system for output
}
}
});
deviceFinder.discoverDevices();
}
// Stream media from internet
if (line.hasOption("s") && line.hasOption("d")) {
Log.d(LOG_TAG, line.getOptionValue("d"));
Log.d(LOG_TAG, line.getOptionValue("s"));
try {
Playback playback = new Playback(platform, appId, new DialServer(InetAddress.getByName(line.getOptionValue("d"))), new PlaybackListener() {
private int time;
private int duration;
private int state;
@Override
public void updateTime(Playback playback, int time) {
Log.d(LOG_TAG, "updateTime: " + time);
this.time = time;
}
@Override
public void updateDuration(Playback playback, int duration) {
Log.d(LOG_TAG, "updateDuration: " + duration);
this.duration = duration;
}
@Override
public void updateState(Playback playback, int state) {
Log.d(LOG_TAG, "updateState: " + state);
// Stop the app if the video reaches the end
if (time > 0 && time == duration && state == 0) {
playback.doStop();
System.exit(0);
}
}
public int getTime() {
return time;
}
public int getDuration() {
return duration;
}
public int getState() {
return state;
}
});
playback.stream(line.getOptionValue("s"));
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
// Play local media file
if (line.hasOption("f") && line.hasOption("d")) {
Log.d(LOG_TAG, line.getOptionValue("d"));
Log.d(LOG_TAG, line.getOptionValue("f"));
final String file = line.getOptionValue("f");
String device = line.getOptionValue("d");
try {
Playback playback = new Playback(platform, appId, new DialServer(InetAddress.getByName(device)), new PlaybackListener() {
private int time;
private int duration;
private int state;
@Override
public void updateTime(Playback playback, int time) {
Log.d(LOG_TAG, "updateTime: " + time);
this.time = time;
}
@Override
public void updateDuration(Playback playback, int duration) {
Log.d(LOG_TAG, "updateDuration: " + duration);
this.duration = duration;
}
@Override
public void updateState(Playback playback, int state) {
Log.d(LOG_TAG, "updateState: " + state);
// Stop the app if the video reaches the end
if (time > 0 && time == duration && state == 0) {
playback.doStop();
System.exit(0);
}
}
public int getTime() {
return time;
}
public int getDuration() {
return duration;
}
public int getState() {
return state;
}
});
if (line.hasOption("t") && line.hasOption("tp")) {
playback.setTranscodingParameters(line.getOptionValue("tp"));
}
playback.play(file, line.hasOption("t"));
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
// REST API server
if (line.hasOption("r")) {
final DeviceFinder deviceFinder = new DeviceFinder(new DeviceFinderListener() {
@Override
public void discoveringDevices(DeviceFinder deviceFinder) {
Log.d(LOG_TAG, "discoveringDevices");
}
@Override
public void discoveredDevices(DeviceFinder deviceFinder) {
Log.d(LOG_TAG, "discoveredDevices");
TrackedDialServers trackedDialServers = deviceFinder.getTrackedDialServers();
for (DialServer dialServer : trackedDialServers) {
Log.d(LOG_TAG, dialServer.toString());
}
}
});
deviceFinder.discoverDevices();
int port = 0;
if (line.hasOption("rp")) {
try {
port = Integer.parseInt(line.getOptionValue("rp"));
} catch (NumberFormatException e) {
Log.e(LOG_TAG, "invalid rest port", e);
}
}
Playback.startWebserver(port, new WebListener() {
String[] prefixes = { "/playback", "/devices" };
HashMap<String, Playback> playbackMap = new HashMap<String, Playback>();
HashMap<String, RestPlaybackListener> playbackListenerMap = new HashMap<String, RestPlaybackListener>();
final class RestPlaybackListener implements PlaybackListener {
private String device;
private int time;
private int duration;
private int state;
public RestPlaybackListener(String device) {
this.device = device;
}
@Override
public void updateTime(Playback playback, int time) {
Log.d(LOG_TAG, "updateTime: " + time);
this.time = time;
}
@Override
public void updateDuration(Playback playback, int duration) {
Log.d(LOG_TAG, "updateDuration: " + duration);
this.duration = duration;
}
@Override
public void updateState(Playback playback, int state) {
Log.d(LOG_TAG, "updateState: " + state);
this.state = state;
// Stop the app if the video reaches the end
if (this.time > 0 && this.time == this.duration && state == 0) {
playback.doStop();
playbackMap.remove(device);
playbackListenerMap.remove(device);
}
}
public int getTime() {
return time;
}
public int getDuration() {
return duration;
}
public int getState() {
return state;
}
}
@Override
public Response handleRequest(String uri, String method, Properties header, Properties parms) {
Log.d(LOG_TAG, "handleRequest: " + uri);
if (method.equals("GET")) {
if (uri.startsWith(prefixes[0])) { // playback
String device = parms.getProperty("device");
if (device != null) {
RestPlaybackListener playbackListener = playbackListenerMap.get(device);
if (playbackListener != null) {
// https://code.google.com/p/json-simple/wiki/EncodingExamples
JSONObject obj = new JSONObject();
obj.put("time", playbackListener.getTime());
obj.put("duration", playbackListener.getDuration());
switch (playbackListener.getState()) {
case 0:
obj.put("state", "idle");
break;
case 1:
obj.put("state", "stopped");
break;
case 2:
obj.put("state", "playing");
break;
default:
obj.put("state", "idle");
break;
}
return new Response(HttpServer.HTTP_OK, "text/plain", obj.toJSONString());
} else {
// Nothing is playing
JSONObject obj = new JSONObject();
obj.put("time", 0);
obj.put("duration", 0);
obj.put("state", "stopped");
return new Response(HttpServer.HTTP_OK, "text/plain", obj.toJSONString());
}
}
} else if (uri.startsWith(prefixes[1])) { // devices
// https://code.google.com/p/json-simple/wiki/EncodingExamples
JSONArray list = new JSONArray();
TrackedDialServers trackedDialServers = deviceFinder.getTrackedDialServers();
for (DialServer dialServer : trackedDialServers) {
JSONObject obj = new JSONObject();
obj.put("name", dialServer.getFriendlyName());
obj.put("ip_address", dialServer.getIpAddress().getHostAddress());
list.add(obj);
}
return new Response(HttpServer.HTTP_OK, "text/plain", list.toJSONString());
}
} else if (method.equals("POST")) {
if (uri.startsWith(prefixes[0])) { // playback
String device = parms.getProperty("device");
if (device != null) {
String stream = parms.getProperty("stream");
String file = parms.getProperty("file");
String state = parms.getProperty("state");
String transcode = parms.getProperty("transcode");
String transcodeParameters = parms.getProperty("transcode-parameters");
Log.d(LOG_TAG, "transcodeParameters="+transcodeParameters);
if (stream != null) {
try {
if (playbackMap.get(device) == null) {
DialServer dialServer = deviceFinder.getTrackedDialServers().findDialServer(InetAddress.getByName(device));
if (dialServer != null) {
RestPlaybackListener playbackListener = new RestPlaybackListener(device);
playbackMap.put(device, new Playback(platform, appId, dialServer, playbackListener));
playbackListenerMap.put(device, playbackListener);
}
}
Playback playback = playbackMap.get(device);
if (playback != null) {
playback.stream(stream);
return new Response(HttpServer.HTTP_OK, "text/plain", "Ok");
}
} catch (Exception e1) {
Log.e(LOG_TAG, "playback", e1);
}
} else if (file != null) {
try {
if (playbackMap.get(device) == null) {
DialServer dialServer = deviceFinder.getTrackedDialServers().findDialServer(InetAddress.getByName(device));
if (dialServer != null) {
RestPlaybackListener playbackListener = new RestPlaybackListener(device);
playbackMap.put(device, new Playback(platform, appId, dialServer, playbackListener));
playbackListenerMap.put(device, playbackListener);
}
}
Playback playback = playbackMap.get(device);
if (transcodeParameters!=null) {
playback.setTranscodingParameters(transcodeParameters);
}
if (playback != null) {
playback.play(file, transcode!=null);
return new Response(HttpServer.HTTP_OK, "text/plain", "Ok");
}
} catch (Exception e1) {
Log.e(LOG_TAG, "playback", e1);
}
} else if (state != null) {
try {
if (playbackMap.get(device) == null) {
DialServer dialServer = deviceFinder.getTrackedDialServers().findDialServer(InetAddress.getByName(device));
if (dialServer != null) {
RestPlaybackListener playbackListener = new RestPlaybackListener(device);
playbackMap.put(device, new Playback(platform, appId, dialServer, playbackListener));
playbackListenerMap.put(device, playbackListener);
}
}
Playback playback = playbackMap.get(device);
if (playback != null) {
// Handle case where current app wasn't started with caster
playback.setDialServer(deviceFinder.getTrackedDialServers().findDialServer(InetAddress.getByName(device)));
// Change the playback state
if (state.equals("play")) {
playback.doPlay();
return new Response(HttpServer.HTTP_OK, "text/plain", "Ok");
} else if (state.equals("pause")) {
playback.doPause();
return new Response(HttpServer.HTTP_OK, "text/plain", "Ok");
} else if (state.equals("stop")) {
playback.doStop();
playbackMap.remove(device);
playbackListenerMap.remove(device);
return new Response(HttpServer.HTTP_OK, "text/plain", "Ok");
} else {
Log.e(LOG_TAG, "playback invalid state: "+state);
}
}
} catch (Exception e1) {
Log.e(LOG_TAG, "playback", e1);
}
}
}
}
}
return new Response(HttpServer.HTTP_BADREQUEST, "text/plain", "Bad Request");
}
@Override
public String[] uriPrefixes() {
return prefixes;
}
});
Log.d(LOG_TAG, "REST server ready");
// Run forever...
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
// Print help
if (line.hasOption("h")) {
printHelp(options);
}
} catch (ParseException exp) {
System.out.println("ERROR: " + exp.getMessage());
System.out.println();
printHelp(options);
}
}
private static void printHelp(Options options) {
StringWriter out = new StringWriter();
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp(new PrintWriter(out), 80, "java -jar caster.jar", "\n", options, 2, 2, "", true);
System.out.println(out.toString());
}
}