package net.digitalprimates.androiddashsender; import java.io.IOException; import org.json.JSONException; import org.json.JSONObject; import android.content.Context; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.support.v7.app.MediaRouteButton; import android.support.v7.media.MediaRouteSelector; import android.support.v7.media.MediaRouter; import android.support.v7.media.MediaRouter.RouteInfo; import android.util.Log; import android.view.View; import android.widget.ArrayAdapter; import android.widget.CompoundButton; import android.widget.ImageButton; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.Spinner; import android.widget.ToggleButton; import com.google.cast.ApplicationChannel; import com.google.cast.ApplicationMetadata; import com.google.cast.ApplicationSession; import com.google.cast.CastContext; import com.google.cast.CastDevice; import com.google.cast.MediaRouteAdapter; import com.google.cast.MediaRouteHelper; import com.google.cast.MediaRouteStateChangeListener; import com.google.cast.MessageStream; import com.google.cast.SessionError; public class MainActivity extends FragmentActivity implements MediaRouteAdapter { private String TAG = "DashTest"; // Streams Spinner spinner; ToggleButton playPauseToggle; ToggleButton soundToggle; SeekBar volumeBar; SeekBar scrubBar; Boolean playbackStarted = false; Boolean playing = false; private class Stream { public String name; public String url; protected Stream(String n, String u) { name = n; url = u; } public String toString() { return name; } } private void populateStreams() { spinner = (Spinner) this.findViewById(R.id.streamOptions); Stream[] items = new Stream[] { /*new Stream( "4K", "http://dash.edgesuite.net/akamai/test/tears1/tearsofsteel_4096x1714_14Mbps.mpd" ),*/ new Stream( "Fraunhofer - HEACC 2.0 - Dream", "http://dash.edgesuite.net/digitalprimates/fraunhofer/480p_video/heaac_2_0_with_video/ElephantsDream/elephants_dream_480p_heaac2_0.mpd" ), new Stream( "Fraunhofer - HEACC 2.0 - Sintel", "http://dash.edgesuite.net/digitalprimates/fraunhofer/480p_video/heaac_2_0_with_video/Sintel/sintel_480p_heaac2_0.mpd" ), new Stream( "Fraunhofer - HEACC 5.1 - 6 CH ID", "http://dash.edgesuite.net/digitalprimates/fraunhofer/480p_video/heaac_5_1_with_video/6chId/6chId_480p_heaac5_1.mpd" ), new Stream( "Fraunhofer - HEACC 5.1 - Dream", "http://dash.edgesuite.net/digitalprimates/fraunhofer/480p_video/heaac_5_1_with_video/ElephantsDream/elephants_dream_480p_heaac5_1.mpd" ), new Stream( "Fraunhofer - HEACC 5.1 - Sintel", "http://dash.edgesuite.net/digitalprimates/fraunhofer/480p_video/heaac_5_1_with_video/Sintel/sintel_480p_heaac5_1.mpd" ), new Stream( "Fraunhofer - Audio Only - Dream", "http://dash.edgesuite.net/digitalprimates/fraunhofer/audio_only/heaac_2_0_without_video/ElephantsDream/elephants_dream_audio_only_heaac2_0.mpd" ), new Stream( "Fraunhofer - Audio Only - Sintel", "http://dash.edgesuite.net/digitalprimates/fraunhofer/audio_only/heaac_2_0_without_video/Sintel/sintel_audio_only_heaac2_0.mpd" ), new Stream( "Envivio", "http://dash.edgesuite.net/envivio/dashpr/clear/Manifest.mpd" ), new Stream( "Segment List", "http://www.digitalprimates.net/dash/streams/gpac/mp4-main-multi-mpd-AV-NBS.mpd" ), new Stream( "Segment Template", "http://www.digitalprimates.net/dash/streams/mp4-live-template/mp4-live-mpd-AV-BS.mpd" ), new Stream( "Segment Template", "http://www.digitalprimates.net/dash/streams/mp4-live-template/mp4-live-mpd-AV-BS.mpd" ), new Stream( "Unified Streaming - Timeline", "http://demo.unified-streaming.com/video/ateam/ateam.ism/ateam.mpd" ), new Stream( "Microsoft #1", "http://origintest.cloudapp.net/media/SintelTrailer_MP4_from_WAME/sintel_trailer-1080p.ism/manifest(format=mpd-time-csf)" ), new Stream( "Microsoft #2", "http://origintest.cloudapp.net/media/SintelTrailer_Smooth_from_WAME/sintel_trailer-1080p.ism/manifest(format=mpd-time-csf)" ), new Stream( "Microsoft #3", "http://origintest.cloudapp.net/media/SintelTrailer_Smooth_from_WAME_720p_Main_Profile/sintel_trailer-720p.ism/manifest(format=mpd-time-csf)" ), new Stream( "Microsoft #4", "http://origintest.cloudapp.net/media/MPTExpressionData01/ElephantsDream_1080p24_IYUV_2ch.ism/manifest(format=mpd-time-csf)" ), new Stream( "Microsoft #5", "http://origintest.cloudapp.net/media/MPTExpressionData02/BigBuckBunny_1080p24_IYUV_2ch.ism/manifest(format=mpd-time-csf)" ), new Stream( "D-Dash #1", "http://www-itec.uni-klu.ac.at/dash/ddash/mpdGenerator.php?segmentlength=2&type=full" ), new Stream( "D-Dash #2", "http://www-itec.uni-klu.ac.at/dash/ddash/mpdGenerator.php?segmentlength=4&type=full" ), new Stream( "D-Dash #3", "http://www-itec.uni-klu.ac.at/dash/ddash/mpdGenerator.php?segmentlength=6&type=full" ), new Stream( "D-Dash #4", "http://www-itec.uni-klu.ac.at/dash/ddash/mpdGenerator.php?segmentlength=8&type=full" ), new Stream( "D-Dash #5", "http://www-itec.uni-klu.ac.at/dash/ddash/mpdGenerator.php?segmentlength=10&type=full" ), new Stream( "D-Dash #6", "http://www-itec.uni-klu.ac.at/dash/ddash/mpdGenerator.php?segmentlength=15&type=full" ), new Stream( "DASH-AVC/264 Ð test vector 1a - Netflix", "http://dash.edgesuite.net/dash264/TestCases/1a/netflix/exMPD_BIP_TC1.mpd" ), new Stream( "DASH-AVC/264 Ð test vector 1a - Sony", "http://dash.edgesuite.net/dash264/TestCases/1a/sony/SNE_DASH_SD_CASE1A_REVISED.mpd" ), new Stream( "DASH-AVC/264 Ð test vector 1b - Envivio", "http://dash.edgesuite.net/dash264/TestCases/1b/envivio/manifest.mpd" ), new Stream( "DASH-AVC/264 Ð test vector 1b - Thomson", "http://dash.edgesuite.net/dash264/TestCases/1b/thomson-networks/2/manifest.mpd" ), new Stream( "DASH-AVC/264 Ð test vector 1c - Envivio", "http://dash.edgesuite.net/dash264/TestCases/1c/envivio/manifest.mpd" ), new Stream( "DASH-AVC/264 Ð test vector 2a - Envivio", "http://dash.edgesuite.net/dash264/TestCases/2a/envivio/manifest.mpd" ), new Stream( "DASH-AVC/264 Ð test vector 2a - Sony", "http://dash.edgesuite.net/dash264/TestCases/2a/sony/SNE_DASH_CASE_2A_SD_REVISED.mpd" ), new Stream( "DASH-AVC/264 Ð test vector 2a - Thomson", "http://dash.edgesuite.net/dash264/TestCases/2a/thomson-networks/2/manifest.mpd" ), new Stream( "DASH-AVC/264 Ð test vector 3a - Fraunhofer", "http://dash.edgesuite.net/dash264/TestCases/3a/fraunhofer/ed.mpd" ), new Stream( "DASH-AVC/264 Ð test vector 3b - Fraunhofer", "http://dash.edgesuite.net/dash264/TestCases/3b/fraunhofer/elephants_dream_heaac2_0.mpd" ), new Stream( "DASH-AVC/264 Ð test vector 3b - Sony", "http://dash.edgesuite.net/dash264/TestCases/3b/sony/SNE_DASH_CASE3B_SD_REVISED.mpd" ), new Stream( "DASH-AVC/264 Ð test vector 4b - Sony", "http://dash.edgesuite.net/dash264/TestCases/4b/sony/SNE_DASH_CASE4B_SD_REVISED.mpd" ), new Stream( "DASH-AVC/264 Ð test vector 5a - Thomson/Envivio", "http://dash.edgesuite.net/dash264/TestCases/5a/1/manifest.mpd" ), new Stream( "DASH-AVC/264 Ð test vector 5b - Thomson/Envivio", "http://dash.edgesuite.net/dash264/TestCases/5b/1/manifest.mpd" ), new Stream( "DASH-AVC/264 Ð test vector 6c - Envivio Manifest 1", "http://dash.edgesuite.net/dash264/TestCases/6c/envivio/manifest.mpd" ), new Stream( "DASH-AVC/264 Ð test vector 6c - Envivio Manifest 2", "http://dash.edgesuite.net/dash264/TestCases/6c/envivio/manifest2.mpd" ) }; ArrayAdapter streams = new ArrayAdapter( this, android.R.layout.simple_spinner_item, items ); spinner.setAdapter(streams); } // Variables private String APP_ID = "75215b49-c8b8-45ae-b0fb-afb39599204e"; private CastContext context; private MediaRouter router; private MediaRouteSelector routeSelector; private MediaRouter.Callback routerCallback; private MediaRouteButton castButton; private CastDevice selectedDevice; private MediaRouteStateChangeListener stateListener; private ApplicationSession session; private DashMessageStream messageStream; // Chromecast private void initCast() { castButton = (MediaRouteButton) findViewById(R.id.media_route_button); Context appContext = getApplicationContext(); context = new CastContext(appContext); MediaRouteHelper.registerMinimalMediaRouteProvider(context, this); router = MediaRouter.getInstance(appContext); routeSelector = MediaRouteHelper.buildMediaRouteSelector(MediaRouteHelper.CATEGORY_CAST, APP_ID, null); castButton.setRouteSelector(routeSelector); routerCallback = new DashMediaRouterCallback(); router.addCallback(routeSelector, routerCallback, MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY); } private class DashMediaRouterCallback extends MediaRouter.Callback { @Override public void onRouteSelected(MediaRouter router, RouteInfo route) { MediaRouteHelper.requestCastDeviceForRoute(route); } @Override public void onRouteUnselected(MediaRouter router, RouteInfo route) { selectedDevice = null; stateListener = null; } } private class DashMessageStream extends MessageStream { private static final String NAMESPACE = "org.dashif.dashjs"; protected DashMessageStream() { super(NAMESPACE); } @Override public void onMessageReceived(JSONObject obj) { Log.e(TAG, "got message " + obj); try { String event = obj.getString("event"); String value = obj.getString("value"); double d = Double.parseDouble(value); int num = (int) Math.floor(d); if (event.equals("timeupdate")) { scrubBar.setProgress(num); } else if (event.equals("durationchange")) { scrubBar.setMax(num); } else if (event.equals("ended")) { // TODO } } catch (JSONException e) { } } public void send(JSONObject payload) { try { sendMessage(payload); } catch (IOException error) { Log.e(TAG, "send message failed: " + error); } } } private void connectSession() { session = new ApplicationSession(context, selectedDevice); ApplicationSession.Listener listener = new ApplicationSession.Listener() { @Override public void onSessionStarted(ApplicationMetadata appMetadata) { ApplicationChannel channel = session.getChannel(); if (channel == null) { Log.e(TAG, "channel = null"); return; } messageStream = new DashMessageStream(); channel.attachMessageStream(messageStream); setAllButtonsEnabled(true); } @Override public void onSessionStartFailed(SessionError error) { Log.e(TAG, "onStartFailed " + error); } @Override public void onSessionEnded(SessionError error) { Log.i(TAG, "onEnded " + error); } }; session.setListener(listener); try { session.startSession(APP_ID); } catch (IOException e) { Log.e(TAG, "Failed to open session", e); } } private void doPlay() { JSONObject payload = new JSONObject(); if (!playbackStarted) { Stream item = (Stream) spinner.getSelectedItem(); payload = new JSONObject(); try { payload.put("command", "load"); payload.put("manifest", item.url); payload.put("isLive", false); } catch (JSONException error) { } messageStream.send(payload); playbackStarted = true; playing = true; } else if (!playing) { payload = new JSONObject(); try { payload.put("command", "play"); } catch (JSONException error) { } messageStream.send(payload); playing = true; } } private void doPause() { if (playing) { JSONObject payload = new JSONObject(); try { payload.put("command", "pause"); } catch (JSONException error) { } messageStream.send(payload); playing = true; } } private void doMute() { JSONObject payload = new JSONObject(); try { payload.put("command", "setMuted"); payload.put("muted", true); } catch (JSONException error) { } messageStream.send(payload); } private void doUnmute() { JSONObject payload = new JSONObject(); try { payload.put("command", "setMuted"); payload.put("muted", false); } catch (JSONException error) { } messageStream.send(payload); } private void setAllButtonsEnabled(Boolean enabled) { spinner.setEnabled(enabled); playPauseToggle.setEnabled(enabled); soundToggle.setEnabled(enabled); volumeBar.setEnabled(enabled); scrubBar.setEnabled(enabled); } private void initButtons() { playPauseToggle = (ToggleButton) this.findViewById(R.id.play_pause_toggle); playPauseToggle.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { doPlay(); } else { doPause(); } } } ); soundToggle = (ToggleButton) this.findViewById(R.id.sound_toggle); soundToggle.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { doMute(); } else { doUnmute(); } } } ); volumeBar = (SeekBar) this.findViewById(R.id.volume_bar); volumeBar.setMax(100); volumeBar.setProgress(100); volumeBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { @Override public void onStopTrackingTouch(SeekBar seekBar) { int percent = seekBar.getProgress(); double value = (double)percent / 100; JSONObject payload = new JSONObject(); try { payload.put("command", "setVolume"); payload.put("volume", value); } catch (JSONException error) { } messageStream.send(payload); } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { } }); scrubBar = (SeekBar) this.findViewById(R.id.scrub_bar); scrubBar.setProgress(0); scrubBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { @Override public void onStopTrackingTouch(SeekBar seekBar) { int value = seekBar.getProgress(); JSONObject payload = new JSONObject(); try { payload.put("command", "seek"); payload.put("time", value); } catch (JSONException error) { } messageStream.send(payload); } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { } }); setAllButtonsEnabled(false); } // MediaRouteAdapter Methods public void onDeviceAvailable(CastDevice device, String app, MediaRouteStateChangeListener listener) { // TODO : What is the string? selectedDevice = device; stateListener = listener; connectSession(); } public void onSetVolume(double volume) { // Handle volume change. } public void onUpdateVolume(double delta) { // Handle volume change. } // Lifecycle @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); populateStreams(); initButtons(); initCast(); } @Override protected void onDestroy() { MediaRouteHelper.unregisterMediaRouteProvider(context); context.dispose(); super.onDestroy(); } }