/*
 * ************************************************************************
 *
 * ADOBE SYSTEMS INCORPORATED
 * Copyright 2013 Adobe Systems Incorporated
 * All Rights Reserved.
 * NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the
 * terms of the Adobe license agreement accompanying it.  If you have received this file from a
 * source other than Adobe, then your use, modification, or distribution of it requires the prior
 * written permission of Adobe.
 *
 **************************************************************************
 */

package com.adobe.primetime.reference.manager;

import android.content.Context;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;

import com.adobe.mediacore.*;
import com.adobe.mediacore.MediaPlayerStatus;
import com.adobe.mediacore.metadata.AdSignalingMode;
import com.adobe.mediacore.metadata.Metadata;
import com.adobe.mediacore.metadata.Notification;
import com.adobe.mediacore.info.Profile;
import com.adobe.mediacore.metadata.TimedMetadata;
import com.adobe.mediacore.qos.DeviceInformation;
import com.adobe.mediacore.qos.LoadInformation;
import com.adobe.mediacore.qos.QOSProvider;
import com.adobe.mediacore.timeline.Timeline;
import com.adobe.mediacore.utils.TimeRange;
import com.adobe.primetime.reference.PrimetimeReference;
import com.adobe.primetime.reference.SplunkHttpServiceConnector;
import com.adobe.primetime.reference.config.IPlaybackConfig;
import com.adobe.primetime.reference.ui.catalog.VideoItem;

import java.util.ArrayList;
import java.util.List;

/**
 *
 * The PlaybackManager handles the main playback functionality of the
 * CoreHLSPlayer by wrapping the PSDK's DefaultMediaPlayer object
 *
 */
public class PlaybackManager implements IManager {
	private IPlaybackConfig playbackConfig;
	private MediaPlayer mediaPlayer;
	private Context context;
	private int currentVolume;
	private boolean isPrepared;
	private boolean isBuffering;

	SplunkHttpServiceConnector splunkConnector;
	private final String LOG_TAG = PrimetimeReference.LOG_APP_NAME
			+ PlaybackManager.class.getSimpleName();
	private final ArrayList<PlaybackManagerEventListener> eventListeners = new ArrayList<PlaybackManagerEventListener>();

	/**
	 *
	 * Class constructor. This method begins the PlaybackManager's
	 * initialisation process.
	 *
	 * @param playbackConfig
	 *            which is used to configure playback settings for the manager
	 * @param mediaPlayer
	 *             the PSDK's implementation of MediaPlayer which the manager
	 *            wraps
	 *
	 */
	public PlaybackManager(IPlaybackConfig playbackConfig, MediaPlayer mediaPlayer, Context context) {
		this.playbackConfig = playbackConfig;
		this.mediaPlayer = mediaPlayer;
		this.context = context;

		this.mediaPlayer.addEventListener(MediaPlayerEvent.STATUS_CHANGED, statusEventListener);
		this.mediaPlayer.addEventListener(MediaPlayerEvent.SIZE_AVAILABLE, sizeAvailableEventListener);
		this.mediaPlayer.addEventListener(MediaPlayerEvent.SEEK_BEGIN, seekBeginEventListener);
		this.mediaPlayer.addEventListener(MediaPlayerEvent.SEEK_END, seekEndEventListener);
		this.mediaPlayer.addEventListener(MediaPlayerEvent.BUFFERING_BEGIN, bufferBeginEventListener);
		this.mediaPlayer.addEventListener(MediaPlayerEvent.BUFFERING_END, bufferEndEventListener);
		this.mediaPlayer.addEventListener(MediaPlayerEvent.OPERATION_FAILED, notificationEventListener);
		this.mediaPlayer.addEventListener(MediaPlayerEvent.RATE_SELECTED, rateSelectedEventListener);
		this.mediaPlayer.addEventListener(MediaPlayerEvent.LOAD_INFORMATION_AVAILABLE, loadInfoEventListener);
		this.mediaPlayer.addEventListener(MediaPlayerEvent.RATE_PLAYING, playbackRatePlayingEventListener);
		this.mediaPlayer.addEventListener(MediaPlayerEvent.TIMELINE_UPDATED, timelineUpdatedEventListener);

	}

	/**
	 * This method initiates the process of PlaybackManager clean-up. It
	 * de-registers any event handlers on the DefaultMediaPlayer instance.
	 *
	 */
	@Override
	public void destroy() {
		mediaPlayer.removeEventListener(MediaPlayerEvent.STATUS_CHANGED, statusEventListener);
		mediaPlayer.removeEventListener(MediaPlayerEvent.SIZE_AVAILABLE, sizeAvailableEventListener);
		mediaPlayer.removeEventListener(MediaPlayerEvent.SEEK_BEGIN, seekBeginEventListener);
		mediaPlayer.removeEventListener(MediaPlayerEvent.SEEK_END, seekEndEventListener);
		mediaPlayer.removeEventListener(MediaPlayerEvent.BUFFERING_BEGIN, bufferBeginEventListener);
		mediaPlayer.removeEventListener(MediaPlayerEvent.BUFFERING_END, bufferEndEventListener);
		mediaPlayer.removeEventListener(MediaPlayerEvent.OPERATION_FAILED, notificationEventListener);
		mediaPlayer.removeEventListener(MediaPlayerEvent.RATE_SELECTED, rateSelectedEventListener);
		mediaPlayer.removeEventListener(MediaPlayerEvent.LOAD_INFORMATION_AVAILABLE, loadInfoEventListener);
		mediaPlayer.removeEventListener(MediaPlayerEvent.RATE_PLAYING, playbackRatePlayingEventListener);
		mediaPlayer.removeEventListener(MediaPlayerEvent.TIMELINE_UPDATED, timelineUpdatedEventListener);

        vaManager.destroy();

		if (splunkConnector != null)
		{
			splunkConnector.destroy();
			splunkConnector = null;
		}

		if (viewGroup != null) {
			View view = mediaPlayer.getView();
			if (view != null)
			{
				viewGroup.removeView(view);
			}

			WebView webView = mediaPlayer.getCustomAdView();
			if (webView != null)
			{
				viewGroup.removeView(webView);
			}
		}
	}

	/**
	 * PlaybackManagerEventListener: listeners that can respond appropriately to
	 * Video Events
	 */
	public interface PlaybackManagerEventListener {

		/**
		 * Handle buffer start event
		 */
        void onBufferStart();

		/**
		 * Handle buffer complete event
		 */
        void onBufferComplete();

		/**
		 * Handle seeking event when seeking started
		 *
		 */
        void onSeeking();

		/**
		 * Handle seek complete event
		 *
		 * @param localAdjustedTime
		 *            the timestamp in the current playback timeline where the
		 *            seek operation took place
		 */
        void onSeekCompleted(long localAdjustedTime);

		/**
		 * Handle the event when the media player has successfully prepared the
		 * media
		 */
        void onPrepared();

		/**
		 * Handle the event when the playback of the media resource has started
		 */
        void onPlaying();

		/**
		 * Handle the event when playback rate changes
		 */
		void onRateChange(float rate);

		/**
		 * Handle the event when the end of the media resource has been reached
		 */
        void onComplete();

		/**
		 * Handle the event when the media resource size is available
		 *
		 * @param height
		 *            the height of the media resource size
		 * @param width
		 *            the width of the media resourcd size
		 */
        void onDimensionsChange(float height, float width);

		/**
		 * Handle the event when the media player changes its state
		 *
		 * @param status
		 *            the new status the playback changed to
		 */
        void onStateChange(MediaPlayerStatus status);

		/**
		 * Handle the event when a playback error occurs
		 *
		 * @param error
		 *            the PSDKErrorCode object that includes error code
		 *
		 */
        void onError(PSDKErrorCode error);
        
        
        void onNativeError(int errorCode, int subErrorCode, String drmErrorString);

		/**
		 * Handle the event when the media player has successfully updated the
		 * media from live assests refresh
		 *
		 * @param localSeekableRange
		 *            the new local seekable range from the media resource
		 * @param timeline
		 *            the new timeline from the media resource
		 */
        void onUpdate(TimeRange localSeekableRange, Timeline<?> timeline);
	}

	public void addEventListener(PlaybackManagerEventListener eventListener) {
		eventListeners.add(eventListener);
	}

	public void removeEventListener(PlaybackManagerEventListener eventListener) {
		eventListeners.remove(eventListener);
	}

	private BillingManager billingManager;
	public void setBillingManager(BillingManager billingManager)
	{
		this.billingManager = billingManager;
	}

    private VAManager vaManager;
    public void setVAManager(VAManager vaManager)
    {
        this.vaManager = vaManager;
    }

    private ViewGroup viewGroup;
    public void setViewGroup(ViewGroup viewGroup)
    {
        View view = mediaPlayer.getView();
        WebView webView = mediaPlayer.getCustomAdView();
        if (this.viewGroup != null)
        {
            if (view != null) {
                this.viewGroup.removeView(view);
            }

            if (webView != null) {
                this.viewGroup.removeView(webView);
            }
        }

        this.viewGroup = viewGroup;

        // add MediaPlayerView
        if (view != null) {
            this.viewGroup.addView(view);

            android.widget.FrameLayout.LayoutParams params = (android.widget.FrameLayout.LayoutParams) view.getLayoutParams();
            params.gravity = Gravity.CENTER;
            view.setLayoutParams(params);
        }

        // Add the CustomAdView if available. If not, it is added when the MediaPlayer is Prepared.
        if (webView != null) {
            addCustomAdView();
        }


    }

	/**
	 *
	 * This convenience method provides an easy way to configure the
	 * PlaybackManager for playback. This method creates a media resource using
	 * AdsManager and the resource URL.
	 *
	 * @param videoItem
	 *            object representing a single video's content
	 * @param adsManager
	 *            the feature manager to handle ad metadata setup in the player
	 */
	public void setupVideo(VideoItem videoItem, AdsManager adsManager) {

        if (InstantOnManager.contains(videoItem.getId()))
        {
            InstantOnItem instantOnItem = InstantOnManager.remove(videoItem.getId());
            PrimetimeReference.logger.i(LOG_TAG, "Using InstantOn item " + instantOnItem.toString());
            mediaPlayer.replaceCurrentItem(instantOnItem.getMediaPlayerItem());
        }
        else {
			MediaResource playerResource = videoItem.toMediaResource();

			MediaPlayerItemConfig config = new MediaPlayerItemConfig(context);
			config.setAdvertisingMetadata(adsManager.getAdvertisingMetadata());
			adsManager.registerAdClientFactory(config);
			adsManager.setCustomTags(config);
			adsManager.adjustAdSignalingMode(config, videoItem);

			if (playbackConfig.isCustomPositionPrefEnabled() && config.getAdvertisingMetadata() != null) {
				long initialTime = playbackConfig.retrieveStartTimePref();

				// set initial playback time to AdvertisingMetadata
				// may be used in custom ContentFactory to set custom ad selection policies
				config.getAdvertisingMetadata().setValue("initial_playback_time", Long.toString(initialTime));
			}

			if (billingManager != null)
			{
				// update billing configuration based on application settings
				billingManager.setBillingConfiguration(config);
			}

            if (vaManager != null)
            {
                // create video analytics before initializing media player
                vaManager.createVAProvider(mediaPlayer, playerResource.getMetadata());
            }

            if (playbackConfig.isRevenueOptimizationEnabled())
            {
            	mediaPlayer.setRevenueOptimizationReportingLevel(Notification.Type.INFO);
				if (splunkConnector != null)
				{
					splunkConnector.destroy();
				}
				splunkConnector = new SplunkHttpServiceConnector();
				splunkConnector.configure(mediaPlayer, playbackConfig.getRevenueOptimizationEndpointUrl(), playbackConfig.getRevenueOptimizationAuthString(), playbackConfig.getRevenueOptimizationGZipEnabled(), playbackConfig.getRevenueOptimizationInterval(), new QOSProvider(context).getDeviceInformation());
            }

            mediaPlayer.replaceCurrentResource(playerResource, config);
        }

		// Set buffer control parameters from settings
		// needed for both normal and instant on use cases as instant on uses the initial buffer time only when pre-buffering
		setBufferControlParams();

		if(playbackConfig.forceOpenMAXAL()) {
			mediaPlayer.setCustomConfiguration("forceOMXAL");
		}
	}

	/**
	 * Play the media resource prepared in the player If the player is in
	 * PREPARED, or PAUSED state, play the video from its current time If the
	 * player is in COMPLETE state, play the video from the beginning If the
	 * player is in PLAYING state, do nothing
	 *
	 * All other states will be ignored
	 */
	public void play() {
		switch (mediaPlayer.getStatus()) {
			case COMPLETE:
				mediaPlayer.seek(0);
				break;
			case PREPARED:
			case PAUSED:
			case PLAYING: // if in trick play mode, resumes normal play
				PrimetimeReference.logger.i(LOG_TAG + "#play()", "Starting playback.");
				mediaPlayer.play();
				break;
			case SUSPENDED:
				// can no longer play from SUSPENDED state. call restore() instead
			default:
				PrimetimeReference.logger.d(LOG_TAG + "#play()", "Ignoring play event due to current player state: " + mediaPlayer.getStatus());
		}
	}

	/**
	 * Pause the media resource playing in the player
	 *
	 * The player has to be in PLAYING state. All other states will be ignored
	 */
	public void pause() {
		PrimetimeReference.logger.i(LOG_TAG + "#pause()", "Pausing the player instance.");
		if (mediaPlayer.getStatus() == MediaPlayerStatus.PLAYING || isCustomAdViewActive()) {
			mediaPlayer.pause();
		}
	}

	/**
	 *
	 * This convenience method provides an easy way to toggle between play and
	 * pause. If the current playback status is PLAYING this method pauses the
	 * content and returns false. If the current playback status is PAUSED this
	 * method plays the content and returns true. If the PlaybackManager detects
	 * that the current playhead is outwith the seekable range the method shifts
	 * the playhead to the live playhead. If the current playback status is
	 * COMPLETED this method resets the player to beginning of the content and
	 * returns false.
	 *
	 * @return true if the new state is play and false if new state is pause.
	 */
	public boolean playPauseVideo() {
        MediaPlayerStatus status = mediaPlayer.getStatus();
        switch (status)
        {
            case PLAYING:
                mediaPlayer.pause();
                return false;
            case COMPLETE:
            case PREPARED:
            case PAUSED:
                mediaPlayer.play();
                return true;
        }
		return false;
	}

	/**
	 * Performs a seeking operation on the relative timeline (timeline resolved
	 * with ads) for the playback. When seek completes, the play head does not
	 * always move to the given seek position. PlaybackManager returns the adjusted
	 * time that play head actually moves to on the onSeekComplete event.
	 *
	 * @param relativePosition the seek position the play head should move to
	 */
	public void seek(long relativePosition) {
		mediaPlayer.seek(relativePosition);
	}

	/**
	 * Performs a seeking operation within primary content of the media item. It will
	 * only seek to places in the timeline with primary content, it will not seek to
	 * places with ad content.
	 * @param localPosition
	 * 			  the local seek position the play head should move
	 */
	public void seekToLocal(long localPosition) {
		mediaPlayer.seekToLocal(localPosition);
	}

	/**
	 * Suspend the media player and release video resources. Pauses playback and puts player in SUSPENDED state.
	 * Should be called whenever the player goes into the background.
	 */
	public void suspend() {
        try {
            mediaPlayer.suspend();
        }
        catch (MediaPlayerException e)
        {
            if (e.getErrorCode() == PSDKErrorCode.ILLEGAL_STATE)
            {
                PrimetimeReference.logger.e(LOG_TAG, "Suspend failed as player is in IllegalState. " +
                        "Current player state: " + mediaPlayer.getStatus());
            }
            else
            {
                PrimetimeReference.logger.e(LOG_TAG, "Suspend failed from Media Error " + e.getErrorCode());
            }
        }
	}

	/**
	 * Restores the media player from the SUSPENDED state and acquires video resources. Puts player in PAUSED state.
	 * Should be called whenever the player returns from the background.
	 */
	public void restore() {
		if (mediaPlayer.getStatus() == MediaPlayerStatus.SUSPENDED) {
			mediaPlayer.restore();
		}
	}

	/**
	 *
	 * This convenience method provides an easy way to mute the
	 * PlaybackManager's volume
	 *
	 */
	public void mute() {
		setVolume(0);
	}

	/**
	 *
	 * This convenience method provides an easy way to unmute the
	 * PlaybackManager
	 *
	 */
	public void unmute() {
		setVolume(currentVolume);
	}

	/**
	 *
	 * This setter method configures the volume of the PlaybackManager.
	 *
	 * @param volume
	 *            the desired volume within the range 0-100
	 *
	 */
	public void setVolume(int volume) {
		PrimetimeReference.logger.i(LOG_TAG + "#setVolume",
				"Changing volume to [" + String.valueOf(volume) + "].");
		mediaPlayer.setVolume(volume);
		if (volume > 0) {
			currentVolume = volume;
		}
	}

	/**
	 *
	 * This getter method returns the PlaybackManager's current volume level.
	 *
	 * @return volume the current volume
	 *
	 */
	public int getVolume() {
		return currentVolume;
	}

	/**
	 * Determine if the media resource is a live event
	 *
	 * @return true if the media resource is a live stream, false if it is a VOD
	 *         asset
	 */
	public boolean isLive() {
        MediaPlayerItem item = mediaPlayer.getCurrentItem();
        return item != null && item.isLive();
	}

	/**
	 * Determine if playback has completed
	 *
	 * @return true if playback is completed, false otherwise
	 */
	public boolean isCompleted() {
		return mediaPlayer.getStatus() == MediaPlayerStatus.COMPLETE;
	}

	/**
	 * Determine if playback is paused
	 *
	 * @return true if playback is paused, false otherwise
	 */
	public boolean isPaused() {
		return mediaPlayer.getStatus() == MediaPlayerStatus.PAUSED;
	}

	/**
	 * Determine if playback is currently playing
	 *
	 * @return true if playback is currently playing, false otherwise
	 */
	public boolean isPlaying() {
		return mediaPlayer.getStatus() == MediaPlayerStatus.PLAYING || isCustomAdViewActive();
	}

	/**
	 * Returns whether the current resource is protected.
	 *
	 * @return true if the resource is protected, false otherwise.
	 */
	public boolean isProtected() {
		return mediaPlayer.getCurrentItem().isProtected();
	}

	/**
	 * Determine if the media player is prepared to play the item. If the player
	 * is already playing the item, it is considered as prepared
	 *
	 * @return true if the media player is prepared, false otherwise
	 */
	public boolean isPrepared() {
		return isPrepared;
	}

	/**
	 * Determine if the playback is currently buffering
	 *
	 * @return true if the playback is buffering, false otherwise
	 */
	public boolean isBuffering() {
		return isBuffering;
	}

	/**
	 * Returns the player's buffer range
	 *
	 * @return TimeRange the player's buffer range
	 */
	public TimeRange getBuffedRange() {
		return mediaPlayer.getBufferedRange();
	}

	/**
	 * Get the status of the player in String format
	 *
	 * @return PlayerState the player status
	 */
	public MediaPlayerStatus getStatus() {
		return mediaPlayer.getStatus();
	}

	/**
	 * This getter method returns the current playhead time of the currently
	 * loaded content.
	 *
	 * @return the current playback time in long value
	 */
	public long getCurrentTime() {
		return mediaPlayer.getCurrentTime();
	}

	/**
	 * Determine if trick play is supported with the current player item
	 *
	 */
	public boolean isTrickPlaySupported() {
		return mediaPlayer.getCurrentItem().isTrickPlaySupported();
	}

	/**
	 * Get the available playback rates from the current player item.
	 *
	 * @return a list of float value representing the play rates
	 */
	public String[] getAvailablePlaybackRates() {
		List<Float> rates = mediaPlayer.getCurrentItem().getAvailablePlaybackRates();
		String[] rateStrings = new String[rates.size()];
		for(int i=0; i<rates.size(); i++) {
			float rate = rates.get(i);
			rateStrings[i] = String.valueOf(rate);
		}
		return rateStrings;
	}

	/**
	 * Get the current play rate
	 */
	public float getPlayRate() {
		return mediaPlayer.getRate();
	}

	/**
	 * Set the play rate to the given rate
	 * @param rate the play rate represent in float value
	 * @return true if the new rate is successfully set, false otherwise
	 */
	public void setPlayRate(float rate) {
		mediaPlayer.setRate(rate);
	}

	/**
	 * Returns the current full playbackRange object.
	 *
	 * @return the current playback range
	 */
	public TimeRange getPlaybackRange() {
		return mediaPlayer.getPlaybackRange();
	}

	/**
	 * Get the start time time of the current playback range
	 *
	 * @return long the start timestamp in the current playback range
	 */
	public long getPlaybackRangeStart() {
		return mediaPlayer.getPlaybackRange().getBegin();
	}

	/**
	 * Get the end time of the current playback range
	 *
	 * @return long the end timestamp in the current playback range
	 */
	public long getPlaybackRangeEnd() {
		return mediaPlayer.getPlaybackRange().getEnd();
	}

	/**
	 *
	 * This getter method returns the local time of the primary content.
	 *
	 * @return the local time in long value
	 *
	 */
	public long getLocalTime() {
		return mediaPlayer.getLocalTime();
	}

	public TimeRange getLocalSeekRange() {

		Timeline timeline = mediaPlayer.getTimeline();

		TimeRange seekableRange = mediaPlayer.getSeekableRange();

		TimeRange localSeekableRange = TimeRange.createRange(
				timeline.convertToLocalTime(seekableRange.getBegin()),
				timeline.convertToLocalTime(seekableRange.getDuration()));
		return localSeekableRange;

	}

	public long getLocalSeekRangeStart() {

		Timeline timeline = mediaPlayer.getTimeline();
		return timeline.convertToLocalTime(mediaPlayer.getSeekableRange().getBegin());
	}

	public long getLocalSeekRangeEnd() {

		Timeline timeline = mediaPlayer.getTimeline();
		return timeline.convertToLocalTime(mediaPlayer.getSeekableRange().getEnd());
	}

	/**
	 * Returns the player's timeline.
	 *
	 * @return the player's timeline with timeline markers.
	 */
	@SuppressWarnings("rawtypes")
	public Timeline getTimeline() {
		return mediaPlayer.getTimeline();
	}

	/**
	 * Returns the TimedMetadata of the currently loaded content.
	 *
	 * @return a list of timed metadata
	 *
	 */
	public List<TimedMetadata> getTimedMetadata() {
		return mediaPlayer.getCurrentItem().getTimedMetadata();
	}

	/**
	 * Determine if ABR control is enabled
	 *
	 * @return true if ABR control is enabled, false otherwise
	 */
	private boolean isAbrControlEnabled() {
		return playbackConfig.isABRControlEnabled();
	}

	/**
	 * This method configures the ABR control parameters from the
	 * IPlaybackConfig object.
	 *
	 */
	public void setAbrControlParams() {
		if (isAbrControlEnabled()) {
			int initialBitRate = playbackConfig.getABRInitialBitRate();
			int minBitRate = playbackConfig.getABRMinBitRate();
			int maxBitRate = playbackConfig.getABRMaxBitRate();
			int minTrickplayBitRate = playbackConfig.getABRMinTrickplayBitRate();
			int maxTrickplayBitRate = playbackConfig.getABRMaxTrickplayBitRate();
			int maxTrickplayBandwidthUsage = playbackConfig.getABRMaxTrickplayBandwidthUsage(); 
			ABRControlParameters.ABRPolicy abrPolicy = playbackConfig
					.getABRPolicy();
			abrPolicy = abrPolicy != null ? abrPolicy : ABRControlParameters.ABRPolicy.ABR_MODERATE;
			mediaPlayer.setABRControlParameters(new ABRControlParameters(
					initialBitRate, minBitRate, maxBitRate, abrPolicy,  minTrickplayBitRate, maxTrickplayBitRate, maxTrickplayBandwidthUsage, ABRControlParameters.DEFAULT_MAX_PLAYOUT_RATE));
		}
	}

	/**
	 *
	 * This method configures the Buffer control parameters from the
	 * IPlaybackConfig object.
	 *
	 */
	public void setBufferControlParams() {
		try {
			mediaPlayer.setBufferControlParameters(getBufferParamsFromSettings());
		} catch (IllegalArgumentException e) {
			PrimetimeReference.logger.w(LOG_TAG + "#setBufferControlParams",
							"Unable to apply buffering params: " + e.getMessage() + ".");
		}
	}

	/**
	 *
	 * This getter method returns the currently configured PlaybackManager
	 * buffering profile.
	 *
	 *
	 * public BufferControlParameters getBufferControlParams() { return
	 * mediaPlayer.getBufferControlParameters(); }
	 *
	 * /** Create a buffer control parameter from bufferring config
	 *
	 * @return BufferControlParameters with the initial buffer time and play
	 *         buffer time value
	 */
	private BufferControlParameters getBufferParamsFromSettings() {
		long initBufferTime = playbackConfig.getInitBufferTime();
		long playBufferTime = playbackConfig.getBufferTime();
		return BufferControlParameters.createDual((int)initBufferTime, (int)playBufferTime);
	}

	/**
	 * Log the bitrate profiles of the media resource
	 */
	private void showProfiles() {
        MediaPlayerItem item = mediaPlayer.getCurrentItem();
        if (item == null)
        {
            return;
        }

		List<Profile> profiles = mediaPlayer.getCurrentItem().getProfiles();
        if (profiles != null) {
            for (Profile profile : profiles) {
                PrimetimeReference.logger.i(LOG_TAG + "#showProfiles()", "Profile bitrate: "
                        + profile.getBitRate());
            }
        }
	}

    /**
     * Sets the CustomAdView to the ViewGroup for display.
     * The CustomAdView is available once the MediaPlayer is PREPARED.
     * The ViewGroup must first be set by calling {@link #setViewGroup(ViewGroup) setViewGroup}
     */
    private void addCustomAdView()
    {
        if (viewGroup != null) {
            WebView customAdView = mediaPlayer.getCustomAdView();
            if (customAdView != null) {
                viewGroup.addView(customAdView);
                customAdView.setPadding(0, 0, 0, 0);
                customAdView.setInitialScale(100);
                customAdView.bringToFront();
            }
        }
    }

	/**
	 * Test if the MediaPlayer's CustomAdView is actively being displayed.
	 * @return true if the CustomAdView is being displayed, false otherwise.
	 */
	public boolean isCustomAdViewActive()
	{
		CustomAdView customAdView = mediaPlayer.getCustomAdView();
		return customAdView != null && customAdView.isActive();
	}

	private final PlaybackRateSelectedEventListener rateSelectedEventListener = new PlaybackRateSelectedEventListener() {

		@Override
		public void onRateSelected(PlaybackRateEvent event) {

			PrimetimeReference.logger.i(LOG_TAG
                            + "::MediaPlayer.PlaybackRateSelectedEventListener#onRateSelected()",
                    "Playback rate changed to [" + event.getRate() + "].");

		}
	};

	/**
	 * StatusEventListener that intercepts PSDK status change events
	 */
	private final StatusChangeEventListener statusEventListener = new StatusChangeEventListener() {

		@Override
		public void onStatusChanged(MediaPlayerStatusChangeEvent event) {
			MediaPlayerStatus status = event.getStatus();
			PrimetimeReference.logger.i(LOG_TAG + "::MediaPlayer.StatusChangeEventListener#onStateChanged()",
					"Player status changed to [" + status + "].");

			switch (status)
			{
				case INITIALIZED:
					// checks to see if custom start position is enabled
					if (playbackConfig.isCustomPositionPrefEnabled())
					{
                        long initialTime = playbackConfig.retrieveStartTimePref();
						mediaPlayer.prepareToPlay(initialTime);
					}
					else
					{
						mediaPlayer.prepareToPlay();
					}

					break;

				case PREPARED:
					if (mediaPlayer.getCurrentItem() == null) {
						// This could happen if we were to call reset prematurely.
						return;
					}
					isPrepared = true;
                    addCustomAdView();
					showProfiles();
					setAbrControlParams();
					for (PlaybackManagerEventListener listener : eventListeners) {
						listener.onPrepared();
					}

					break;

				case PLAYING:
					for (PlaybackManagerEventListener listener : eventListeners) {
						listener.onPlaying();
					}
					break;
				case COMPLETE:
					for (PlaybackManagerEventListener listener : eventListeners) {
						listener.onComplete();
					}
					break;

				case ERROR:
					String error = event.getMetadata().getValue("PSDK_ERROR_CODE");
                    if (error != null)
                    {
                        PSDKErrorCode mediaError = PSDKErrorCode.valueOf(Integer.parseInt(error));
                        PrimetimeReference.logger.e(LOG_TAG
                                        + "::MediaPlayer.StatusChangeEventListener#onStatusChanged()",
                                        "Error: " + mediaError + ".");

                        for (PlaybackManagerEventListener listener : eventListeners) {
                            listener.onError(mediaError);
                        }
                    } else {
                        String nativeErrorString = event.getMetadata().getValue("NATIVE_ERROR_CODE");
                        String nativeSubErrorString = event.getMetadata().getValue("NATIVE_SUBERROR_CODE");
                        String nativeDRMErrorString = event.getMetadata().getValue("DRM_ERROR_STRING");

                        int nativeError = 0;
                        int nativeSubError = 0;

                        if (nativeErrorString != null)
                            nativeError = Integer.parseInt(nativeErrorString);

                        if (nativeSubErrorString != null)
                            nativeSubError = Integer.parseInt(nativeSubErrorString);


                        for (PlaybackManagerEventListener listener : eventListeners) {
                            listener.onNativeError(nativeError, nativeSubError, nativeDRMErrorString);
                        }

                    }
					break;

				case SUSPENDED:
				default:
					for (PlaybackManagerEventListener listener : eventListeners) {
						listener.onStateChange(status);
					}
			}
		}


	};

	/**
	 * PlaybackRateChangeEventListener that intercepts PSDK playback rate change events
	 */
	private final PlaybackRatePlayingEventListener playbackRatePlayingEventListener = new PlaybackRatePlayingEventListener() {
		@Override
		public void onRatePlaying(PlaybackRateEvent playbackRateEvent) {
			for (PlaybackManagerEventListener listener : eventListeners)
			{
				listener.onRateChange(playbackRateEvent.getRate());
			}
		}
	};

	/**
	 * SizeAvailableEventListener that intercepts PSDK size available events
	 */
	private final SizeAvailableEventListener sizeAvailableEventListener = new SizeAvailableEventListener() {

		/**
		 * When the size of the media is available, notify the listeners to
		 * handle the event
		 */
		@Override
		public void onSizeAvailable(SizeAvailableEvent event) {
			for (PlaybackManagerEventListener listener : eventListeners) {
				listener.onDimensionsChange(event.getHeight(), event.getWidth());
			}
		}

	};


	/**
	 * SeekBeginEventListener that intercepts PSDK seek start events
	 */
	private final SeekBeginEventListener seekBeginEventListener = new SeekBeginEventListener() {

		/**
		 * When the seek operation starts, log the event
		 */
		@Override
		public void onSeekBegin(SeekEvent event) {
			PrimetimeReference.logger.i(LOG_TAG
							+ "::SeekBeginEventListener#onSeekBegin()",
					"Seek starting.");
			for (PlaybackManagerEventListener listener : eventListeners) {
				listener.onSeeking();
			}
		}

	};

	/**
	 * SeekEndEventListener that intercepts PSDK seek end events
	 */
	private final SeekEndEventListener seekEndEventListener = new SeekEndEventListener() {

		/**
		 * When the seek operation completes, notify the listeners to handle the
		 * event
		 */
		@Override
		public void onSeekEnd(SeekEvent event) {
			double adjustedTime = event.getActualPosition();
			PrimetimeReference.logger.i(LOG_TAG
							+ "::SeekEndEventListener#onSeekEnd()",
					"Seek complete at position: " + adjustedTime + ".");
			for (PlaybackManagerEventListener listener : eventListeners) {
				listener.onSeekCompleted((long) adjustedTime);
			}
		}

	};

	/**
	 * BufferingBeginEventListener that intercepts PSDK buffer start events
	 */
	private final BufferingBeginEventListener bufferBeginEventListener = new BufferingBeginEventListener() {

		/**
		 * When the buffering starts, notify the listeners to handle the event
		 */
		@Override
		public void onBufferingBegin(BufferEvent event) {
			PrimetimeReference.logger.i(LOG_TAG
							+ "::BufferingBeginEventListener#onBufferBegin()",
					"Buffering started.");
			isBuffering = true;
			for (PlaybackManagerEventListener listener : eventListeners) {
				listener.onBufferStart();
			}
		}

	};

	/**
	 * BufferingEndEventListener that intercepts PSDK buffer end events
	 */
	private final BufferingEndEventListener bufferEndEventListener = new BufferingEndEventListener() {

		/**
		 * When the buffering completes, notify the listeners to handle the
		 * event
		 */
		@Override
		public void onBufferingEnd(BufferEvent event) {
			PrimetimeReference.logger.i(LOG_TAG
							+ "::BufferingEndEventListener#onBufferEnd()",
					"Buffering complete.");
			isBuffering = false;
			for (PlaybackManagerEventListener listener : eventListeners) {
				listener.onBufferComplete();
			}
		}

	};

	private final TimelineUpdatedEventListener timelineUpdatedEventListener = new TimelineUpdatedEventListener() {

		@Override
		public void onTimelineUpdated(TimelineEvent event) {

			for (PlaybackManagerEventListener listener : eventListeners) {
				listener.onUpdate(getLocalSeekRange(), event.getTimeline());
			}
		}
	};

	/**
	 * NotificationEventListener that intercepts PSDK notification events
	 */
	private final NotificationEventListener notificationEventListener = new NotificationEventListener() {

        /**
         * An example of dumping the information within a Notification. Here, we don't handle the event in
         * any way except to print a log message.
         * @param event The NotificationEvent
         */
		@Override
		public void onNotification(NotificationEvent event) {

            Notification notification = event.getNotification();
            Notification.Type type = notification.getType();

            StringBuilder sb = new StringBuilder();

            sb.append("[").append(type).append("]");
            sb.append(" Player operation failed (").append(notification.getCode()).append(")");

            Metadata metadata = notification.getMetadata();

            if (metadata != null)
            {
                for (String key : metadata.keySet())
                {
                    sb.append(", ").append(key).append(" = ").append(metadata.getValue(key));
                }
            }

            Notification inner = notification.getInnerNotification();

            if (inner != null) {
                sb.append(" :: Inner Notification [").append(inner.getType()).append("](").append(inner.getCode()).append(")");


                Metadata innerMetadata = inner.getMetadata();

                if (innerMetadata != null) {
                    for (String iKey : innerMetadata.keySet()) {
                        sb.append(", ").append(iKey).append(" = ").append(innerMetadata.getValue(iKey));
                    }
                }
            }

            switch(type)
            {
                case ERROR:
                    PrimetimeReference.logger.e(LOG_TAG, sb.toString());
                    break;
                case WARNING:
                    PrimetimeReference.logger.w(LOG_TAG, sb.toString());
                    break;
                default:
                    PrimetimeReference.logger.i(LOG_TAG, sb.toString());
                    break;
            }

        }

	};

	/**
	 * LoadInformationEventListener that intercepts PSDK load info events
	 */
	private final LoadInformationEventListener loadInfoEventListener = new LoadInformationEventListener() {

		@Override
		public void onLoadInformation(LoadInformationEvent event) {
			LoadInformation loadInfo = event.getLoadInfomation();
			PrimetimeReference.logger.i(
					LOG_TAG + "::LoadInformationEventListener#onLoadInfomation()",
					"Url: " + loadInfo.getUrl() + ", size: "
							+ loadInfo.getSize()
							+ " bytes, download duration: "
							+ loadInfo.getDownloadDuration() + "ms");
		}

	};




	public static final String SETTINGS_ABR_CTRL_ENABLED = "settings_abr_enabled";
	public static final String SETTINGS_ABR_INITIAL_BITRATE = "settings_abr_initial_bit_rate";
	public static final String SETTINGS_ABR_MIN_BITRATE = "settings_abr_min_bit_rate";
	public static final String SETTINGS_ABR_MAX_BITRATE = "settings_abr_max_bit_rate";
	public static final String SETTINGS_ABR_MIN_TRICKPLAY_BITRATE = "settings_abr_min_trickplay_bit_rate";
	public static final String SETTINGS_ABR_MAX_TRICKPLAY_BITRATE = "settings_abr_max_trickplay_bit_rate";
	public static final String SETTINGS_ABR_MAX_TRICKPLAY_BANDWIDTHUSAGE = "settings_abr_max_trickplay_bandwidth_usage"; 
	public static final String SETTINGS_ABR_POLICY = "settings_abr__policy";

	public static final int DEFAULT_INIT_BIT_RATE = 0;
	public static final int DEFAULT_MIN_BIT_RATE = 0;
	public static final int DEFAULT_MAX_BIT_RATE = 2500000;
	public static final int DEFAULT_MIN_TRICKPLAY_BIT_RATE = 0;
	public static final int DEFAULT_MAX_TRICKPLAY_BIT_RATE = 2147483647;
	public static final int DEFAULT_MAX_TRICKPLAY_BANDWIDTH_USAGE = 2147483647; 
	public static final boolean DEFAULT_ABR_CTRL_ENABLED = false;

	public static final String SETTINGS_BUFFER_INIT = "settings_init_buffer";
	public static final String SETTINGS_BUFFER_TIME = "settings_buffer_time";

	public static final int DEFAULT_INIT_BUFFER = 2000;
	public static final int DEFAULT_BUFFER_TIME = 30000;

	public static final String SETTINGS_FORCE_OMXAL = "settings_force_omxal";
	public static final boolean DEFAULT_FORCE_OMXAL = false;

	public static final String SETTINGS_DVR_START_TIME_ENABLED = "settings_dvr_start_time_enabled";
	public static final String SETTINGS_DVR_START_TIME = "settings_dvr_start_time";
	public static final String SETTINGS_DVR_SEEKING_OUTSIDE_ENABLED = "settings_dvr_seeking_outside_enabled";
	public static final String SETTINGS_DVR_SEEKING_CLP_ENABLED = "settings_dvr_seeking_clp_enabled";
	public static final String SETTINGS_DVR_SEEKING_OFFSET = "settings_dvr_seeking_offset";


	public static final boolean DEFAULT_DVR_START_TIME_ENABLED = false;
	public static final long CUSTOM_DVR_START_TIME = 30000;
	public static final boolean DEFAULT_DVR_SEEKING_OUTSIDE_ENABLED = false;
	public static final boolean DEFAULT_DVR_SEEKING_CLP_ENABLED = false;
	public static final long CUSTOM_DVR_SEEKING_OFFSET = 0;

	public static final String SETTINGS_REVENUE_OPTIMIZATION = "settings_revenue_optimization";
	public static final boolean DEFAULT_REVENUE_OPTIMIZATION = false;

	public static final String SETTINGS_REVENUE_OPTIMIZATION_GZIP = "settings_revenue_optimization_gzip";
	public static final boolean DEFAULT_REVENUE_OPTIMIZATION_GZIP = true;

	public static final String SETTINGS_REVENUE_OPTIMIZATION_INTERVAL = "settings_revenue_optimization_interval";
	public static final int DEFAULT_REVENUE_OPTIMIZATION_INTERVAL = 5000;

	public static final String SETTINGS_REVENUE_OPTIMIZATION_ENDPOINT = "settings_revenue_optimization_endpoint";
    public static final String DEFAULT_REVENUE_OPTIMIZATION_ENDPOINT = "https://splunk-us-hec-external.adobelaas.com:443/services/collector/event";

	public static final String SETTINGS_REVENUE_OPTIMIZATION_AUTHSTRING = "settings_revenue_optimization_authstring";
	public static final String DEFAULT_REVENUE_OPTIMIZATION_AUTHSTRING = "Splunk 1F5535BA-422B-4D62-9250-56E215E00328";
}
