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 com.google.android.exoplayer2.C;

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 {
	/**
	 * Default for {@link #INTENT_WRAP_PLAYLIST} (false)
	 */
	public static final boolean DEFAULT_INTENT_WRAP_PLAYLIST = false;
	/**
	 * Default for {@link #INTENT_RANDOM_NEXT_PLAYLIST} (false)
	 */
	public static final boolean DEFAULT_INTENT_RANDOM_NEXT_PLAYLIST = false;
	/**
	 * Default for {@link #INTENT_WAIT_VIDEO_FRAME_PLAYLIST} (true)
	 */
	public static final boolean DEFAULT_INTENT_WAIT_VIDEO_FRAME_PLAYLIST = true;
	/**
	 * Default for {@link #INTENT_STOP_ON_ERROR_PLAYLIST} (true)
	 */
	public static final boolean DEFAULT_INTENT_STOP_ON_ERROR_PLAYLIST = true;
	/**
	 * Default for {@link #INTENT_AUTO_NEXT_PLAYLIST_MS} (disabled)
	 */
	public static final long DEFAULT_INTENT_AUTO_NEXT_PLAYLIST_MS = C.TIME_UNSET;

	/**
	 * Intent key to use when providing an array of {@link PlayerConfig}s (playlist)
	 */
	public static final String INTENT_PLAYLIST = "INTENT_PLAYLIST";

	/**
	 * Intent flag to enable wrapping of the playlist
	 * Default is {@link #DEFAULT_INTENT_WRAP_PLAYLIST}
	 */
	public static final String INTENT_WRAP_PLAYLIST = "INTENT_WRAP_PLAYLIST";
	/**
	 * Intent flag to enable random delay to switch to the next item in the playlist, the range is [0, INTENT_AUTO_NEXT_PLAYLIST_MS]
	 * Default is {@link #DEFAULT_INTENT_RANDOM_NEXT_PLAYLIST}
	 */
	public static final String INTENT_RANDOM_NEXT_PLAYLIST = "INTENT_RANDOM_NEXT_PLAYLIST";
	/**
	 * Intent flag to enable auto switch to the next item in the playlist before the first video frame is rendered on screen
	 * Default is {@link #DEFAULT_INTENT_WAIT_VIDEO_FRAME_PLAYLIST}
	 */
	public static final String INTENT_WAIT_VIDEO_FRAME_PLAYLIST = "INTENT_WAIT_VIDEO_FRAME_PLAYLIST";
	/**
	 * Intent flag to enable auto switch to the next item in the playlist upon error
	 * Default is {@link #DEFAULT_INTENT_STOP_ON_ERROR_PLAYLIST}
	 */
	public static final String INTENT_STOP_ON_ERROR_PLAYLIST = "INTENT_STOP_ON_ERROR_PLAYLIST";
	/**
	 * Intent flag to enable auto switch to the next item in the playlist with the specified delay in ms
	 * Default is {@link #DEFAULT_INTENT_AUTO_NEXT_PLAYLIST_MS}
	 */
	public static final String INTENT_AUTO_NEXT_PLAYLIST_MS = "INTENT_AUTO_NEXT_PLAYLIST_MS";

	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",
									Drm.Widevine
							).get())
							.get()),

			new Demo("Simple Playback of 2 Widevine items", "DASH-Widevine - DASH-Widevine",
					SimplePlaybackDemo.class,
					new BundleBuilder()
							.put(INTENT_WRAP_PLAYLIST, true)
							.put(INTENT_AUTO_NEXT_PLAYLIST_MS, 3000L)
							.put(INTENT_RANDOM_NEXT_PLAYLIST, true)
							.put(INTENT_WAIT_VIDEO_FRAME_PLAYLIST, true)
							.put(INTENT_STOP_ON_ERROR_PLAYLIST, true)
							.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",
											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",
											Drm.Widevine
										).get())
										.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("Key Rotation (Widevine)", "Plays stream where keys are rotated",
					SimplePlaybackDemo.class,
					new BundleBuilder()
							.put(SdkConsts.INTENT_URL, "https://demo.cf.castlabs.com/public/SUPPORT/TRAN-72/Manifest.mpd")
							.put(SdkConsts.INTENT_DRM_CONFIGURATION, new DrmTodayConfiguration.Builder(
									DrmTodayConfiguration.DRMTODAY_STAGING,
									"purchase",
									"default",
									"six",
									Drm.Widevine
							).keyRotation(true).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",
															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",
															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",
															Drm.Widevine
													).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",
															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")
						.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",
									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.Builder()
										.url("https://proxy.uat.widevine.com/proxy?video_id=efd045b1eb61888a&provider=widevine_test")
										.playClearSamplesWithoutKeys(true)
										.drm(Drm.Widevine)
										.get())
							.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.Builder()
									.url("http://playready.directtaps.net/pr/svc/rightsmanager.asmx")
									.playClearSamplesWithoutKeys(true)
									.drm(Drm.Playready)
									.get())
							.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",
									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",
													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",
													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",
						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",
						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",
									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;
		}
	}
}
