package com.tt.droidplay; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javax.jmdns.ServiceInfo; /** * Class used to communicate to the AirPlay-enabled device. * * @author Tuomas Tikka */ public class AirPlayClientService { // executor service for asynchronous tasks to the service private ExecutorService es; // callback back to UI private AirPlayClientCallback callback; /** * Initialize the service. * * @param callback The callback back to the UI. */ public AirPlayClientService(AirPlayClientCallback callback) { this.callback = callback; es = Executors.newSingleThreadExecutor(); } /** * Shut down the service. */ public void shutdown() { if (es != null) { es.shutdown(); } } /** * Send an image file to the service (display the image). * * @param file The image file * @param serviceInfo The service to use * @param transition The image transition to use * @throws Exception If there are any problems with the parameters */ public void putImage(File file, ServiceInfo serviceInfo, String transition) throws Exception { if (serviceInfo == null) { throw new Exception("Not connected to AirPlay service"); } es.submit(new PutImageTask(file, serviceInfo, transition)); } /** * Send a video link to the service (start playing). * * @param location The link to the video * @param serviceInfo The service to use * @throws Exception If there are any problems with the parameters */ public void playVideo(URL location, ServiceInfo serviceInfo) throws Exception { if (serviceInfo == null) { throw new Exception("Not connected to AirPlay service"); } es.submit(new PlayVideoTask(location, serviceInfo)); } /** * Send a request to stop playing the current video. * * @param serviceInfo The service to use * @throws Exception If there are any problems with the parameters */ public void stopVideo(ServiceInfo serviceInfo) throws Exception { if (serviceInfo == null) { throw new Exception("Not connected to AirPlay service"); } es.submit(new StopVideoTask(serviceInfo)); } // // Private // private class PutImageTask implements Runnable { private File file; private ServiceInfo serviceInfo; private String transition; public PutImageTask(File file, ServiceInfo serviceInfo, String transition) { this.file = file; this.serviceInfo = serviceInfo; this.transition = transition; } @Override public void run() { try { URL url = new URL(serviceInfo.getURL() + "/photo"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setDoInput(true); conn.setDoOutput(true); conn.setConnectTimeout(15 * 1000); conn.setReadTimeout(15 * 1000); conn.setRequestMethod("PUT"); conn.setRequestProperty("Content-Length", "" + file.length()); conn.setRequestProperty("X-Apple-AssetKey", UUID.randomUUID().toString()); conn.setRequestProperty("X-Apple-Session-ID", UUID.randomUUID().toString()); conn.setRequestProperty("X-Apple-Transition", transition); conn.setRequestProperty("User-Agent", "MediaControl/1.0"); BufferedOutputStream out = new BufferedOutputStream(conn.getOutputStream()); BufferedInputStream in = new BufferedInputStream(new FileInputStream(file)); byte[] buffer = new byte[32 * 1024]; int i; while ((i = in.read(buffer)) != -1) { out.write(buffer, 0, i); } in.close(); out.close(); int status = conn.getResponseCode(); if (status == 200) { callback.onPutImageSuccess(file); } else { callback.onPutImageError(file, "AirPlay service responded HTTP " + status); } } catch (Exception e) { callback.onPutImageError(file, e.getMessage()); } } } private class PlayVideoTask implements Runnable { private URL location; private ServiceInfo serviceInfo; public PlayVideoTask(URL location, ServiceInfo serviceInfo) { this.location = location; this.serviceInfo = serviceInfo; } @Override public void run() { try { StringBuilder content = new StringBuilder(); content.append("Content-Location: "); content.append(location.toString()); content.append("\n"); content.append("Start-Position: 0\n"); URL url = new URL(serviceInfo.getURL() + "/play"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setDoInput(true); conn.setDoOutput(true); conn.setConnectTimeout(15 * 1000); conn.setReadTimeout(15 * 1000); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Length", "" + content.length()); conn.setRequestProperty("Content-Type", "text/parameters"); conn.setRequestProperty("X-Apple-AssetKey", UUID.randomUUID().toString()); conn.setRequestProperty("X-Apple-Session-ID", UUID.randomUUID().toString()); conn.setRequestProperty("User-Agent", "MediaControl/1.0"); BufferedOutputStream out = new BufferedOutputStream(conn.getOutputStream()); out.write(content.toString().getBytes()); out.close(); int status = conn.getResponseCode(); if (status == 200) { callback.onPlayVideoSuccess(location); } else { callback.onPlayVideoError(location, "AirPlay service responded HTTP " + status); } } catch (Exception e) { callback.onPlayVideoError(location, e.getMessage()); } } } private class StopVideoTask implements Runnable { private ServiceInfo serviceInfo; public StopVideoTask(ServiceInfo serviceInfo) { this.serviceInfo = serviceInfo; } @Override public void run() { try { URL url = new URL(serviceInfo.getURL() + "/stop"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(15 * 1000); conn.setReadTimeout(15 * 1000); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Length", "0"); conn.setRequestProperty("User-Agent", "MediaControl/1.0"); int status = conn.getResponseCode(); if (status == 200) { callback.onStopVideoSuccess(); } else { callback.onStopVideoError("AirPlay service responded HTTP " + status); } } catch (Exception e) { callback.onStopVideoError(e.getMessage()); } } } }