package com.castlabs.sdk.demos;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Parcelable;
import androidx.appcompat.app.AppCompatActivity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;

import com.castlabs.android.SdkConsts;
import com.castlabs.android.drm.Drm;
import com.castlabs.android.drm.DrmConfiguration;
import com.castlabs.android.drm.DrmTodayConfiguration;
import com.castlabs.android.network.NetworkConfiguration;
import com.castlabs.android.network.RetryConfiguration;
import com.castlabs.android.player.AbrConfiguration;
import com.castlabs.android.player.BufferConfiguration;
import com.castlabs.android.player.CustomUtcTimingElement;
import com.castlabs.android.player.LiveConfiguration;
import com.castlabs.android.player.LowLatencyProfile;
import com.castlabs.android.player.PlayerConfig;
import com.castlabs.android.player.TrickplayConfiguration;
import com.castlabs.android.player.models.SideloadedTrack;
import com.castlabs.android.player.models.ThumbnailDataTrack;
import com.castlabs.sdk.ima.ImaAdRequest;
import com.castlabs.sdk.ima.ImaStreamRequest;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class MainActivity extends AppCompatActivity {

	public static final String INTENT_PLAYLIST = "INTENT_PLAYLIST";
	public static final String INTENT_ENABLE_MEDIA_SESSION = "INTENT_ENABLE_MEDIA_SESSION";

	private static Demo[] DEMOS = new Demo[]{
		new Demo("Simple Playback (Clear)", "Plays an unencrypted stream using a PlayerView without any additional controls or callbacks.",
					SimplePlaybackDemo.class,
					new BundleBuilder()
							.put(SdkConsts.INTENT_URL, "http://demo.cf.castlabs.com/media/TOS/abr/Manifest_clean_sizes.mpd")
							.get()),

			new Demo("Simple Playback (Widevine)", "Plays stream protected with Widevine.",
					SimplePlaybackDemo.class,
					new BundleBuilder()
							.put(SdkConsts.INTENT_URL, "http://demo.cf.castlabs.com/media/TOS/DASH/DEMO.mpd")
							.put(SdkConsts.INTENT_DRM_CONFIGURATION, new DrmTodayConfiguration.Builder(
									DrmTodayConfiguration.DRMTODAY_STAGING,
									"purchase",
									"sessionId",
									"six",
									"dasheverywhere_demo",
									Drm.Widevine
							).get())
							.get()),

			new Demo("Live Playback (Clear)", "Plays an unencrypted DASH live stream using a PlayerView without any additional controls or callbacks.",
					SimplePlaybackDemo.class,
					new BundleBuilder()
							.put(SdkConsts.INTENT_URL, "http://livesim.dashif.org/livesim/testpic_2s/Manifest.mpd")
							.put(SdkConsts.INTENT_LIVE_CONFIGURATION, new LiveConfiguration.Builder()
									// Set to be 20 seconds away from the live edge
									//
									.liveEdgeLatencyMs(20_000)

									// HLS-only. Defines the HLS segment index counting from the
									// tail from which the live playback should start.
									//
									//.hlsLiveTailSegmentIndex(5)

									// HLS-only. Sets the target duration coefficient used for
									// defining the playlist update interval in the case when
									// the current playlist is not renewed. When forced, the
									// coefficient will be used also when the playlist is currently
									// renewed.
									//
									//.hlsPlaylistUpdateTargetDurationCoefficient(0.3f, true)
									.get())
							.get()),

			new Demo("Simple Playback (Playready)", "Plays stream protected with Playready.",
					SimplePlaybackDemo.class,
					new BundleBuilder()
							.put(SdkConsts.INTENT_URL, "http://demo.cf.castlabs.com/media/TOS/DASH/DEMO.mpd")
							.put(SdkConsts.INTENT_DRM_CONFIGURATION, new DrmTodayConfiguration.Builder(
									DrmTodayConfiguration.DRMTODAY_STAGING,
									"purchase",
									"sessionId",
									"six",
									"dasheverywhere_demo",
									Drm.Playready
							).get())
							.get()),

			new Demo("Simple Playback (OMA)", "Plays stream protected with OMA.",
					SimplePlaybackDemo.class,
					new BundleBuilder()
							.put(SdkConsts.INTENT_URL, "http://demo.cf.castlabs.com/media/TOS/DASH/DEMO.mpd")
							.put(SdkConsts.INTENT_DRM_CONFIGURATION, new DrmTodayConfiguration.Builder(
									DrmTodayConfiguration.DRMTODAY_STAGING,
									"purchase",
									"sessionId",
									"six",
									"dasheverywhere_demo",
									Drm.Oma
							).get())
							.get()),

			new Demo("Multiple Controller Playlist - Live Switching", "Creates a playlist with 3 live items in order to simulate the live channel use case.",
					PlaylistPlaybackDemo.class,
					new BundleBuilder()
							.put(PlaylistPlaybackDemo.INTENT_PLAYLIST_PLAY_BACKGROUND_KEY, true)
							.put(PlaylistPlaybackDemo.INTENT_PLAYLIST_BACK_PLAYERS_KEY, 1)
							.put(PlaylistPlaybackDemo.INTENT_PLAYLIST_FORWARD_PLAYERS_KEY, 1)
							.put(INTENT_PLAYLIST,
									new PlayerConfig[]{
											new PlayerConfig.Builder("http://livesim.dashif.org/livesim/testpic_2s/Manifest.mpd")
													.get(),
											new PlayerConfig.Builder("http://livesim.dashif.org/livesim/start_1800/testpic_2s/Manifest.mpd")
													.get(),
											new PlayerConfig.Builder("http://livesim.dashif.org/livesim/start_900/testpic_2s/Manifest.mpd")
													.get()
									}
							)
							.get()),

			new Demo("Multiple Controller Playlist demo", "Creates a playlist with 3 items; TOS Clear, BBB Drm, TOS Widevine. This demo uses the MultiControllerPlaylist.",
					PlaylistPlaybackDemo.class,
					new BundleBuilder()
							.put(PlaylistPlaybackDemo.INTENT_PLAYLIST_PLAY_BACKGROUND_KEY, false)
							.put(PlaylistPlaybackDemo.INTENT_PLAYLIST_BACK_PLAYERS_KEY, 0)
							.put(PlaylistPlaybackDemo.INTENT_PLAYLIST_FORWARD_PLAYERS_KEY, 1)
							.put(INTENT_PLAYLIST,
									new PlayerConfig[]{
											new PlayerConfig.Builder("http://demo.cf.castlabs.com/media/TOS/abr/Manifest_clean_sizes.mpd")
													// Specify a TrickplayConfiguration (optional).
													// This tells the player which strategy to use
													// for Trickplay (speedup) playback.
													//
													// We only need to apply this config to the first
													// item since we're keeping it on PlayerController
													// change in PlaylistPlaybackDemo
													// (playlistController.setItemChangeFlags)
													.trickplayConfiguration(new TrickplayConfiguration.Builder()
															.speedupMode(TrickplayConfiguration.SpeedupMode.SEEK)
															//.speedupMode(TrickplayConfiguration.SpeedupMode.DECODER)
															.preferTrickPlayTracks(true)
															//.speed(10)
															//.keepAudioEnabled(false)
															//.maxTrackBitrate(0)
															.get())
													// In case we wanted to start playing with Trickplay
													//.enableTrickplayMode(true)
													.get(),
											new PlayerConfig.Builder("http://demo.cf.castlabs.com/media/bbb_abr/Manifest.mpd")
													.drmConfiguration(new DrmTodayConfiguration.Builder(
														DrmTodayConfiguration.DRMTODAY_PRODUCTION,
														"purchase",
														"default",
														"client_qa",
														"BBB_TEST",
														Drm.BestAvailable
													).get())
													.get(),
											new PlayerConfig.Builder("http://demo.cf.castlabs.com/media/TOS/DASH/DEMO.mpd")
													.drmConfiguration(new DrmTodayConfiguration.Builder(
															DrmTodayConfiguration.DRMTODAY_STAGING,
															"purchase",
															"sessionId",
															"six",
															"dasheverywhere_demo",
															Drm.Widevine
													).get())
													.get()
									}
							)
							.get()),

			new Demo("Single Controller Playlist demo", "Creates a playlist with 3 protected items; TOS, BBB, TOS. This demo uses the SingleControllerPlaylist.",
					ConcatenatingPlaybackDemo.class,
					new BundleBuilder()
							.put(INTENT_PLAYLIST,
									new PlayerConfig[]{
											new PlayerConfig.Builder("http://demo.cf.castlabs.com/media/TOS/DASH/DEMO.mpd")
													.drmConfiguration(new DrmTodayConfiguration.Builder(
															DrmTodayConfiguration.DRMTODAY_STAGING,
															"purchase",
															"sessionId",
															"six",
															"dasheverywhere_demo",
															Drm.Widevine
													).get())
													.get(),
											new PlayerConfig.Builder("http://demo.cf.castlabs.com/media/bbb_abr/Manifest.mpd")
													.drmConfiguration(new DrmTodayConfiguration.Builder(
															DrmTodayConfiguration.DRMTODAY_PRODUCTION,
															"purchase",
															"default",
															"client_qa",
															"BBB_TEST",
															Drm.BestAvailable
													).get())
													.get(),
											new PlayerConfig.Builder("http://demo.cf.castlabs.com/media/shows/tos/Manifest.mpd")
													.drmConfiguration(new DrmTodayConfiguration.Builder(
															DrmTodayConfiguration.DRMTODAY_STAGING,
															"purchase",
															"p0",
															"six",
															"tos_next_gen_demo",
															Drm.Widevine
													).get())
													.get()
									}
							)
							.get()),

			new Demo("ABR and Buffer Configuration", "Demonstrates Intent parameters to configure the buffer and ABR behaviour",
					AbrPlaybackDemo.class,
					new BundleBuilder()
							.put(SdkConsts.INTENT_URL, "http://demo.cf.castlabs.com/media/TOS/abr/Manifest_clean_sizes.mpd")
							.put(SdkConsts.INTENT_ABR_CONFIGURATION, new AbrConfiguration.Builder()
									// We want to start with the lowest quality while we don't have any
									// bitrate estimates. But once we collected enough information,
									// switch to ABR mode
									//
									.initialTrackSelection(SdkConsts.VIDEO_QUALITY_LOWEST, true)

									// While we have no bandwidth information, lets assume that we
									// have at least 2Mbit while we collect information. This will
									// influence the initial ABR selection
									//
									//.maxInitialBitrate(2000000)

									// By default we only use 0.75 (i.e. 75%) of the bandwidth
									// estimation, since estimations are usually not very accurate
									// but you can trust the estimator more and we use 95% of the
									// estimated value in this case
									//.bandwidthFraction(0.95f)

									// By default, we we expect to have at least 10 seconds of
									// buffered data before we can switch up the quality. That
									// gives some safe guards around building up a stable buffer
									// when the bandwidth estimation is not very accurate.
									// In this example, we increase this to 20 seconds to demonstrate
									// the effect. With 20 seconds, the player will playout the
									// initially selected quality for at least 20 seconds before
									// switching up
									// .minDurationForQualityIncrease(20, TimeUnit.SECONDS)

									// Similar to the min duration for quality increase. By default
									// we will not switch down the quality if we have more than 25
									// seconds of buffer. That avoids that the player will react
									// to short fluctuations in the network capacity. In this example
									// we set the duration to 10 seconds, which means that player
									// will hold the current quality event longer, but you increase
									// the risk of a buffer under-run. Please note that in order to
									// test this, you need to slow down you network capacity during
									// playback.
									// .maxDurationForQualityDecrease(10, TimeUnit.SECONDS)

									// Use this to switch the underlying algorithm
									// .method(AbrConfiguration.METHOD_ITERATE)

									.get())
							.put(SdkConsts.INTENT_BUFFER_CONFIGURATION, new BufferConfiguration.Builder()
									// By default the player uses 16 MB of buffer. Use
									// this to allocate a larger buffer. NOTE: This is allocated
									// on the heap space of your application and you can not
									// increase this value arbitrarily. Also play fair with other
									// applications on the device and try to establish a small
									// memory footprint, which increases the chances that your
									// application will not be killed quickly when put into
									// background
									//.bufferSizeBytes(32 * 1024 * 1024)

									// The default buffer will oscillate between "fill" and
									// "drain" state which helps to save battery when we have
									// a good amount of data in the buffer.
									// The default ranges are 15s low and 60s high, which means
									// the buffer will fill until high, i.e. 60s, and then drain
									// until low, i.e. 15s. This parameter controls the lower
									// bound. We can put this to 30 seconds to make sure
									// that we try to kee a 30 second buffer at all times.
									//
									// NOTE: If you test this, consider using a fixed low initial
									// track and keep it (see initialTrackSelection above),
									// otherwise ABR might select very high qualities for this
									// test asset, which consume a lot of memory and you might
									// never see the buffer filled up.
									//.lowMediaTime(30, TimeUnit.SECONDS)
									//.highMediaTime(120, TimeUnit.SECONDS)

									// Adjust the amount of data that the player will be waiting for
									// before starting playback, initially or after a seek.
									// There is a separate parameter for the rebuffer start, which
									// is considered after a buffer under-run.
									// In this example, we increase the time significantly so see
									// the effect.
									//
									// NOTE: Reducing this value will increase the start time but
									// also increases the risk of a buffer under-run
									//.minPlaybackStart(30, TimeUnit.SECONDS)
									//.minRebufferStart(30, TimeUnit.SECONDS)

									//.prioritizeTimeOverSizeThresholds(false)

									// Enable (or not) draining of the buffer while device is charging.
									// If this is enabled, the buffer will bounce around the maximum
									// in order maintain a high health.
									//
									// NOTE: This flag will *only* change the buffer behaviour while
									// charging. Enabling this can potentially reduce the chances of
									// hitting a buffer underrun at the expense of a higher power
									// consumption, depending on the type of network.
									//
									// The default value is true.
									//.drainWhileCharging(true)

									// Set some amount of back buffer. This is how much media time
									// will be available (still buffered) behind the current playback
									// position. This allows to perform a quick seek operation backwards
									// without having to re-download the content.
									//
									// This setting is independent from all the other ones, and will
									// always be respected.
									//
									// NOTE: Use with caution. If a high enough value is set, it can
									// lead to the buffer never being released, and eventually
									// running out of memory.
									//.backBufferDuration(20, TimeUnit.SECONDS)
									.get())
							.get()),

			new Demo("Network Configuration", "Demonstrates Intent parameters to configure network related parameters",
					SimplePlaybackDemo.class,
					new BundleBuilder()
							.put(SdkConsts.INTENT_URL, "http://demo.cf.castlabs.com/media/TOS/abr/Manifest_clean_sizes.mpd")
							// Network parameters are bundled in the network configuration.
							// That can be passed as an intent parameter
							.put(SdkConsts.INTENT_NETWORK_CONFIGURATION, new NetworkConfiguration.Builder()
									// You can configure the global connection and read timeouts.
									// The connection timeout is used when a connection is opened
									// while the read timeout is used when we read data from an
									// open connection. Using the general method will use the values
									// for both manifests and segment downloads
									.connectionTimeoutMs(5000)
									.readTimeoutMs(8000)

									// You can also specify the timeouts separately for
									// manifest and segments downloads
									.manifestConnectionTimeoutMs(3000)
									.manifestReadTimeoutMs(5000)
									.segmentsConnectionTimeoutMs(8000)
									.segmentsConnectionTimeoutMs(10000)

									// Configure the global retry behaviour. This will be applied
									// for both segment and manifest. Please take a look at the
									// API documentation of the RetryConfiguration for more
									// information about the effect of the parameters and how
									// the backoff algorithm works
									.retryConfiguration(new RetryConfiguration.Builder()
											// We allow for 5 attempts at most. That means the
											// first load and then at most 4 retries in case
											// of any download errors
											.maxAttempts(5)
											// We configure the base delay for the first retry
											.baseDelayMs(500)
											.get())

									// Similar to the timeouts you can also set separate
									// retry configurations for manifest and segment downloads
									.manifestRetryConfiguration(new RetryConfiguration.Builder()
											.maxAttempts(2)
											.baseDelayMs(1000)
											.get())
									.segmentsRetryConfiguration(new RetryConfiguration.Builder()
											.maxAttempts(4)
											.baseDelayMs(800)
											.get())
									.get())
							.get()),

			new Demo("Low Latency Configuration (default)", "Demonstrates Intent parameters to configure low latency default parameters",
					SimplePlaybackDemo.class,
					// Apply low latency default profile to the specified Bundle.
					// Note that some of LiveConfiguration values will be overwritten by LowLatencyProfile values
					new LowLatencyProfile().applyTo(
						new BundleBuilder()
								.put(SdkConsts.INTENT_URL, "http://livesim.dashif.org/livesim/testpic_2s/Manifest.mpd")
								.put(SdkConsts.INTENT_LIVE_CONFIGURATION, new LiveConfiguration.Builder()
										.liveEdgeLatencyMs(20_000)
									.get())
							.get())),

			new Demo("Low Latency Configuration (custom)", "Demonstrates Intent parameters to configure low latency custom parameters",
					SimplePlaybackDemo.class,
					// Build the custom low latency profile and apply it to the specified Bundle.
					// See description of each LowLatency parameter in the corresponding API section.
					// Note that some of LiveConfiguration values will be overwritten by LowLatencyProfile values
					new LowLatencyProfile.Builder()
							.customUtcTimingElement(new CustomUtcTimingElement(SdkConsts.SCHEME_ID_UTC_TIMING_ELEMENT_NTP, "time.google.com", true))
							.hlsLiveTailSegmentIndex(0)
							.hlsPlaylistUpdateTargetDurationCoefficient(0.5f, false)
							.liveEdgeLatencyMs(0)
							.minPlaybackStartMs(100)
							.minRebufferStartMs(100)
						.get().applyTo(new BundleBuilder()
							.put(SdkConsts.INTENT_URL, "http://livesim.dashif.org/livesim/testpic_2s/Manifest.mpd")
							.put(SdkConsts.INTENT_LIVE_CONFIGURATION, new LiveConfiguration.Builder()
									.liveEdgeLatencyMs(20_000)
								.get())
							.get())),

			new Demo("Simple Playback (IMA Ads)", "Loads and plays single pre-roll, mid-roll and post-roll ads during content playback.",
					SimplePlaybackDemo.class,
					new BundleBuilder()
							.put(SdkConsts.INTENT_URL, "http://demo.cf.castlabs.com/media/TOS/abr/Manifest_clean_sizes.mpd")
							.put(SdkConsts.INTENT_ADVERTS_DATA, new ImaAdRequest("https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpost&cmsid=496&vid=short_onecue&correlator=").toAdRequest())
							.get()),

			new Demo("Simple Playback (Manually scheduled IMA Ads)", "Enables manual ad scheduling, allowing for mid-playback ad requests",
						SimplePlaybackDemo.class,
						new BundleBuilder()
								.put(SdkConsts.INTENT_URL, "http://demo.cf.castlabs.com/media/TOS/abr/Manifest_clean_sizes.mpd")
								.put(SdkConsts.INTENT_AD_SCHEDULE, SdkConsts.AD_SCHEDULE_MANUAL)
								.get()),

			new Demo("Live Playback (Manually scheduled IMA Ads)", "Enables manual ad scheduling, allowing for mid-playback ad requests",
						SimplePlaybackDemo.class,
						new BundleBuilder()
							.put(SdkConsts.INTENT_URL, "http://livesim.dashif.org/livesim/testpic_2s/Manifest.mpd")
							.put(SdkConsts.INTENT_AD_SCHEDULE, SdkConsts.AD_SCHEDULE_MANUAL)
							.get()),

			new Demo("Simple VOD Playback (IMA DAI DASH Ads)", "Loads and plays single pre-roll, mid-roll and post-roll ads during content playback.",
					SimplePlaybackDemo.class,
					new BundleBuilder()
						.put(SdkConsts.INTENT_CONTENT_TYPE, SdkConsts.CONTENT_TYPE_DASH)
						.put(SdkConsts.INTENT_ADVERTS_DATA, new ImaStreamRequest("2474148", "bbb-clear", null).toAdRequest())
						.get()),

			new Demo("Simple VOD Playback (IMA DAI HLS Ads)", "Loads and plays single pre-roll, mid-roll and post-roll ads during content playback.",
					SimplePlaybackDemo.class,
					new BundleBuilder()
						.put(SdkConsts.INTENT_CONTENT_TYPE, SdkConsts.CONTENT_TYPE_HLS)
						.put(SdkConsts.INTENT_ADVERTS_DATA, new ImaStreamRequest("19463", "googleio-highlights", null).toAdRequest())
						.get()),

			new Demo("Simple Live Playback (IMA DAI HLS Ads)", "Loads and plays single pre-roll, mid-roll and post-roll ads during content playback.",
					SimplePlaybackDemo.class,
					new BundleBuilder()
						.put(SdkConsts.INTENT_CONTENT_TYPE, SdkConsts.CONTENT_TYPE_HLS)
						.put(SdkConsts.INTENT_ADVERTS_DATA, new ImaStreamRequest("sN_IYUG8STe1ZzhIIE_ksA", null).toAdRequest())
						.get()),

			new Demo("Playback Service", "Plays a stream using the PlayerService and allows background audio playback.",
					SimplePlayServiceDemo.class,
					new BundleBuilder()
							.put(SdkConsts.INTENT_URL, "http://demo.cf.castlabs.com/media/TOS/abr/Manifest_clean_sizes.mpd")
							.get()),

			new Demo("Offline Key Storage (Widevine)", "Plays stream protected with Widevine and stored the license on the device. The first " +
					"playback of this entry will fetch the licence, the second playback will use stored offline key.",
					SimplePlaybackDemo.class,
					new BundleBuilder()
							.put(SdkConsts.INTENT_URL, "http://demo.cf.castlabs.com/media/TOS/DASH/DEMO.mpd")
							.put(SdkConsts.INTENT_DRM_CONFIGURATION, new DrmTodayConfiguration.Builder(
									DrmTodayConfiguration.DRMTODAY_STAGING,
									"purchase",
									"sessionId",
									"six",
									"dasheverywhere_demo",
									Drm.Widevine)
									.offlineId("dasheverywhere_demo") // we use the asset ID as offline ID
									.get())
							.get()),

			new Demo("Widevine DRM proxy (HDCP + secure)", "Uses Googles widevine test proxy",
					SimplePlaybackDemo.class,
					new BundleBuilder()
							.put(SdkConsts.INTENT_URL, "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd")
							.put(SdkConsts.INTENT_DRM_CONFIGURATION,
									new DrmConfiguration(
											"https://proxy.uat.widevine.com/proxy?video_id=efd045b1eb61888a&provider=widevine_test",
											true, Drm.Widevine))
							.get()),

			new Demo("External Smooth Streaming (Playready)", "Plays a playready encrypted example stream using a custom DRM callback",
					SimplePlaybackDemo.class,
					new BundleBuilder()
							.put(SdkConsts.INTENT_URL, "http://playready.directtaps.net/smoothstreaming/SSWSS720H264PR/SuperSpeedway_720.ism/Manifest")
							.put(SdkConsts.INTENT_CONTENT_TYPE, SdkConsts.CONTENT_TYPE_SMOOTHSTREAMING)
							.put(SdkConsts.INTENT_DRM_CONFIGURATION,
									new DrmConfiguration(
											"http://playready.directtaps.net/pr/svc/rightsmanager.asmx",
											true, Drm.Playready))
							.get()),

			new Demo("Seamless CDN switching upon segment download failure", "Requests and switches to fallback CDN upon segment download failure.",
					SimplePlaybackDemo.class,
					new BundleBuilder()
							// The URL has segments for the first 24secs only, then 404 error is returned
							.put(SdkConsts.INTENT_URL, "https://demo.cf.castlabs.com/media/QA/QA_BBB_single_4/Manifest.mpd")
							.put(SdkConsts.INTENT_DRM_CONFIGURATION, new DrmTodayConfiguration.Builder(
									DrmTodayConfiguration.DRMTODAY_STAGING,
									"purchase",
									"p0",
									"six",
									null,
									Drm.Widevine
							).get())
							.get()),

			new Demo("Thumbnail Track", "Plays an unencrypted stream, side-loading a Thumbnail track.",
					SimplePlaybackDemo.class,
					new BundleBuilder()
							.put(SdkConsts.INTENT_URL, "http://demo.cf.castlabs.com/media/TOS/abr/Manifest_clean_sizes.mpd")
							.put(SdkConsts.INTENT_SIDELOADED_TRACKS_ARRAYLIST, new ArrayList<SideloadedTrack>(){{
								// Bif Subtitles
								add(new SideloadedTrack.ThumbnailBuilder()
										.url("http://demo.castlabs.com/media/TOS/thumbs/thumbs.bif")
										.thumbnailType(ThumbnailDataTrack.TYPE_BIF)
										.get());
								// Vtt Subtitles
								//add(new SideloadedTrack.ThumbnailBuilder()
								//		.url("http://demo.castlabs.com/media/TOS/thumbs/thumbs.vtt")
								//		.thumbnailType(ThumbnailDataTrack.TYPE_WEBVTT_INDEX)
								//		.get());
							}})
							.get()),

			new Demo("Simple Playback (Clear) with Trickplay controls", "Plays an unencrypted stream using a PlayerView without on-screen controls for modifying trickplay params.",
					TrickplaySettingsPlaybackDemo.class,
					new BundleBuilder()
							.put(SdkConsts.INTENT_URL, "http://demo.cf.castlabs.com/media/TOS/abr/Manifest_clean_sizes.mpd")
							.get()),

			new Demo("Dual ToS L1 + L3", "Plays two protected assets side by side. One is WV Level1 and the other one Level3.",
					TwinPlayerDemo.class,
					new BundleBuilder()
							.put(TwinPlayerDemo.BUNDLE_1,
									new PlayerConfig.Builder("http://demo.cf.castlabs.com/media/TOS/DASH/DEMO.mpd")
											.drmConfiguration(new DrmTodayConfiguration.Builder(
													DrmTodayConfiguration.DRMTODAY_STAGING,
													"purchase",
													"sessionId",
													"six",
													"dasheverywhere_demo",
													Drm.Widevine)
													.get())
											// You can force a specific decoder to be used
											//.videoCodec("decoder.decodervendor.videodecoder.secure")
											.get())
							.put(TwinPlayerDemo.BUNDLE_2,
									new PlayerConfig.Builder("http://demo.cf.castlabs.com/media/TOS/DASH/DEMO.mpd")
											.drmConfiguration(new DrmTodayConfiguration.Builder(
													DrmTodayConfiguration.DRMTODAY_STAGING,
													"purchase",
													"sessionId",
													"six",
													"dasheverywhere_demo",
													Drm.Widevine)
													// If trying to play two protected streams, it's
													// likely you have to force one of them to use software
													// drm (Widevine Level 3). This depends on the device
													// having two hardware CDM implementations
													.forceWidevineL3(true)
													.get())
											// You can force a specific decoder to be used
											//.videoCodec("decoder.decodervendor.videodecoder.secondary")
											.get())
							.get()),
			new Demo("Playback with Widevine license renewals", "Plays stream with Widevine licenses having validity of 60s and renews licenses periodically",
				SimplePlaybackDemo.class,
				new BundleBuilder()
					.put(SdkConsts.INTENT_URL, "http://demo.cf.castlabs.com/media/bbb_abr/Manifest.mpd")
					.put(SdkConsts.INTENT_DRM_CONFIGURATION, new DrmTodayConfiguration.Builder(
						DrmTodayConfiguration.DRMTODAY_STAGING,
						"purchase",
						"crtjson:{\"profile\": {\"rental\": {\"absoluteExpiration\": \"2050-11-23T15:20:03Z\", \"playDuration\": 60000}}, \"outputProtection\": {\"enforce\": false, \"digital\": false, \"analogue\": false}, \"storeLicense\": true}",
						"client_dev",
						"BBB_TEST",
						Drm.Widevine
					).offlineId("BBB_TEST").renewalThreshold(20, TimeUnit.SECONDS).get())
					.get()),

			new Demo("Playback with multiple Widevine keys and clear lead", "Plays stream with Widevine keys not being available for all the video qualities",
				SimplePlaybackDemo.class,
				new BundleBuilder()
					.put(SdkConsts.INTENT_URL, "https://demo.cf.castlabs.com/public/Transcodes/OUT/TRANS-57/Sony4K-DASH-STAGING/Manifest.mpd")
					.put(SdkConsts.INTENT_DRM_CONFIGURATION, new DrmTodayConfiguration.Builder(
						DrmTodayConfiguration.DRMTODAY_STAGING,
						"purchase",
						"crtjson:{\"profile\":{\"purchase\":{}},\"storeLicense\":false,\"op\":{\"config\":{\"HD\":{\"WidevineM\":{\"deny\":true},\"PlayReady\":{\"deny\":true},\"FairPlay\":{\"deny\":true},\"OMA\":{\"deny\":true}},\"UHD\":{\"WidevineM\":{\"deny\":true},\"PlayReady\":{\"deny\":true},\"FairPlay\":{\"deny\":true},\"OMA\":{\"deny\":true}}}}}",
						"six",
						"19848484",
						Drm.Widevine
					).get())
					.get()),

			new Demo("WisePlay", "Plays stream protected with Wiseplay.",
					SimplePlaybackDemo.class,
					new BundleBuilder()
							.put(SdkConsts.INTENT_URL, "https://demo.cf.castlabs.com/media/bbb_abr/Manifest_new.mpd")
							.put(SdkConsts.INTENT_DRM_CONFIGURATION, new DrmTodayConfiguration.Builder(
									DrmTodayConfiguration.DRMTODAY_STAGING,
									"purchase",
									"sessionId",
									"six",
									null,
									Drm.Wiseplay
							).get())
							.get()),

			new Demo("Debug Plugin", "Plot the debug overlay on the player view",
					DebugPluginActivity.class,
					new BundleBuilder()
							.put(SdkConsts.INTENT_URL, "http://demo.cf.castlabs.com/media/TOS/abr/Manifest_clean_sizes.mpd")
							.get()),
			new Demo("IPTV - Clear", "Plays clear IPTV RTP Stream",
					IPTVClearPlayback.class,
					new BundleBuilder()
							.put(SdkConsts.INTENT_URL, "rtp://233.49.82.198:7500"
							).get()),
			new Demo("IPTV - Clear", "Plays clear IPTV UDP Stream",
					IPTVClearPlayback.class,
					new BundleBuilder()
							.put(SdkConsts.INTENT_URL, "udp://224.0.0.5:1234"
							).get()),
	};

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		ListView demosList = (ListView) findViewById(R.id.demos_list);
		List<Demo> demos = Arrays.asList(DEMOS);
		final ArrayAdapter<Demo> demoAdapter = new ArrayAdapter<Demo>(this, R.layout.demo_list_item, demos) {
			@Override
			public View getView(int position, View convertView, ViewGroup parent) {
				View view = convertView == null ? getLayoutInflater().inflate(R.layout.demo_list_item, parent, false) : convertView;
				Demo item = getItem(position);
				((TextView) view.findViewById(R.id.title)).setText(item.name);
				((TextView) view.findViewById(R.id.description)).setText(item.description);
				return view;
			}
		};

		demosList.setAdapter(demoAdapter);
		demosList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
			@Override
			public void onItemClick(AdapterView<?> parent, View view, final int position, long id) {
				demoAdapter.getItem(position).start(MainActivity.this);
			}
		});
	}


	static class Demo {
		public final String name;
		public final String description;
		private final Class<? extends Activity> activityClass;
		private final Bundle bundle;

		Demo(String name, String description, Class<? extends Activity> activityClass, Bundle bundle) {
			this.name = name;
			this.description = description;
			this.activityClass = activityClass;
			this.bundle = bundle;
		}

		void start(Context context) {
			context.startActivity(new Intent(context, activityClass).putExtras(bundle));
		}
	}


	static class BundleBuilder {
		private final Bundle bundle;
		private final JSONObject metadata;

		BundleBuilder() {
			this.bundle = new Bundle();
			metadata = new JSONObject();
		}

		BundleBuilder addMetaData(String key, Object value) {
			try {
				metadata.put(key, value);
			} catch(JSONException e) {}
			return this;
		}

		BundleBuilder put(String key, String value) {
			bundle.putString(key, value);
			return this;
		}

		BundleBuilder put(String key, int value) {
			bundle.putInt(key, value);
			return this;
		}

		BundleBuilder put(String key, boolean value) {
			bundle.putBoolean(key, value);
			return this;
		}

		BundleBuilder put(String key, Parcelable value) {
			bundle.putParcelable(key, value);
			return this;
		}

		BundleBuilder put(String key, ArrayList<? extends Parcelable> value) {
			bundle.putParcelableArrayList(key, value);
			return this;
		}

		BundleBuilder put(String key, Parcelable[] value) {
			bundle.putParcelableArray(key, value);
			return this;
		}

		BundleBuilder put(String key, long value) {
			bundle.putLong(key, value);
			return this;
		}

		Bundle get() {
			if (metadata.length() != 0) {
				bundle.putString(PlayerConfig.INTENT_METADATA, metadata.toString());
			}
			return bundle;
		}
	}
}
