package tv.emby.embyatv.playback;
import android.app.Fragment;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Typeface;
import android.media.AudioManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.v17.leanback.app.RowsFragment;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
import android.text.Html;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupMenu;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import com.squareup.okhttp.internal.Util;
import com.squareup.picasso.Picasso;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import mediabrowser.apiinteraction.EmptyResponse;
import mediabrowser.apiinteraction.Response;
import mediabrowser.apiinteraction.android.GsonJsonSerializer;
import mediabrowser.model.dlna.StreamInfo;
import mediabrowser.model.dlna.SubtitleStreamInfo;
import mediabrowser.model.dto.BaseItemDto;
import mediabrowser.model.dto.ChapterInfoDto;
import mediabrowser.model.dto.ImageOptions;
import mediabrowser.model.entities.ImageType;
import mediabrowser.model.entities.MediaStream;
import mediabrowser.model.livetv.ChannelInfoDto;
import mediabrowser.model.mediainfo.SubtitleTrackEvent;
import mediabrowser.model.mediainfo.SubtitleTrackInfo;
import tv.emby.embyatv.R;
import tv.emby.embyatv.TvApp;
import tv.emby.embyatv.base.CustomMessage;
import tv.emby.embyatv.base.IMessageListener;
import tv.emby.embyatv.integration.RecommendationManager;
import tv.emby.embyatv.itemhandling.BaseRowItem;
import tv.emby.embyatv.itemhandling.ItemRowAdapter;
import tv.emby.embyatv.livetv.ILiveTvGuide;
import tv.emby.embyatv.livetv.LiveTvGuideActivity;
import tv.emby.embyatv.livetv.TvManager;
import tv.emby.embyatv.presentation.CardPresenter;
import tv.emby.embyatv.presentation.PositionableListRowPresenter;
import tv.emby.embyatv.ui.AudioDelayPopup;
import tv.emby.embyatv.ui.GuideChannelHeader;
import tv.emby.embyatv.ui.GuidePagingButton;
import tv.emby.embyatv.ui.HorizontalScrollViewListener;
import tv.emby.embyatv.ui.ImageButton;
import tv.emby.embyatv.ui.LiveProgramDetailPopup;
import tv.emby.embyatv.ui.ObservableHorizontalScrollView;
import tv.emby.embyatv.ui.ObservableScrollView;
import tv.emby.embyatv.ui.ProgramGridCell;
import tv.emby.embyatv.ui.ScrollViewListener;
import tv.emby.embyatv.ui.ValueChangedListener;
import tv.emby.embyatv.util.InfoLayoutHelper;
import tv.emby.embyatv.util.RemoteControlReceiver;
import tv.emby.embyatv.util.Utils;
/**
* Created by Eric on 4/28/2015.
*/
public class CustomPlaybackOverlayFragment extends Fragment implements IPlaybackOverlayFragment, ILiveTvGuide {
ImageView mPoster;
ImageView mStudioImage;
ImageView mLogoImage;
TextView mTitle;
TextView mEndTime;
TextView mCurrentPos;
TextView mRemainingTime;
TextView mInfoSummary;
View mTopPanel;
View mBottomPanel;
ImageButton mPlayPauseBtn;
LinearLayout mInfoRow;
LinearLayout mButtonRow;
FrameLayout mPopupArea;
RowsFragment mPopupRowsFragment;
ArrayObjectAdapter mPopupRowAdapter;
ListRow mChapterRow;
PositionableListRowPresenter mPopupRowPresenter;
ProgressBar mCurrentProgress;
CustomPlaybackOverlayFragment mFragment;
View mNextUpPanel;
View mSmNextUpPanel;
Button mSmNextButton;
Button mSmCancelButton;
TextView mSmNextUpTitle;
TextView mSmStartsIn;
Button mNextButton;
Button mCancelButton;
TextView mNextUpTitle;
TextView mNextUpSummary;
TextView mStartsIn;
LinearLayout mNextUpInfoRow;
ImageView mNextUpPoster;
TextView mSubtitleText;
//Live guide items
public static final int PIXELS_PER_MINUTE = Utils.convertDpToPixel(TvApp.getApplication(),6);
public static final int PAGE_SIZE = 75;
RelativeLayout mTvGuide;
private TextView mDisplayDate;
private TextView mGuideTitle;
private TextView mGuideCurrentTitle;
private TextView mChannelStatus;
private TextView mFilterStatus;
private TextView mSummary;
private LinearLayout mGuideInfoRow;
private LinearLayout mChannels;
private LinearLayout mTimeline;
private LinearLayout mProgramRows;
private ScrollView mChannelScroller;
private HorizontalScrollView mTimelineScroller;
private View mGuideSpinner;
private BaseItemDto mSelectedProgram;
private ProgramGridCell mSelectedProgramView;
private boolean mGuideVisible = false;
private Calendar mCurrentGuideStart;
private Calendar mCurrentGuideEnd;
private long mCurrentLocalGuideStart;
private long mCurrentLocalGuideEnd;
private int mCurrentDisplayChannelStartNdx = 0;
private int mCurrentDisplayChannelEndNdx = 0;
private int mGuideHours = 6;
private List<ChannelInfoDto> mAllChannels;
private String mFirstFocusChannelId;
PlaybackController mPlaybackController;
private List<BaseItemDto> mItemsToPlay;
Animation fadeOut;
Animation slideUp;
Animation slideDown;
Animation showPopup;
Animation hidePopup;
Animation showNextUp;
Animation hideNextUp;
Animation showSmNextUp;
Animation hideSmNextUp;
Handler mHandler = new Handler();
Runnable mHideTask;
TvApp mApplication;
PlaybackOverlayActivity mActivity;
private AudioManager mAudioManager;
int mButtonSize;
boolean mFadeEnabled = false;
boolean mIsVisible = true;
boolean mPopupPanelVisible = false;
boolean mNextUpPanelVisible = false;
boolean mSmNextUpPanelVisible = false;
int mCurrentDuration;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mFragment = this;
mApplication = TvApp.getApplication();
mActivity = (PlaybackOverlayActivity) getActivity();
//stop any audio that may be playing
MediaManager.stopAudio();
mAudioManager = (AudioManager) mApplication.getSystemService(Context.AUDIO_SERVICE);
if (mAudioManager == null) {
mApplication.getLogger().Error("Unable to get audio manager");
Utils.showToast(getActivity(), R.string.msg_cannot_play_time);
return;
}
mActivity.setVolumeControlStream(AudioManager.STREAM_MUSIC);
mActivity.setKeyListener(keyListener);
mItemsToPlay = MediaManager.getCurrentVideoQueue();
if (mItemsToPlay == null || mItemsToPlay.size() == 0) {
Utils.showToast(mApplication, mApplication.getString(R.string.msg_no_playable_items));
mActivity.finish();
return;
}
mButtonSize = Utils.convertDpToPixel(mActivity, 28);
mApplication.setPlaybackController(new PlaybackController(mItemsToPlay, this));
mPlaybackController = mApplication.getPlaybackController();
//set up fade task
mHideTask = new Runnable() {
@Override
public void run() {
if (mIsVisible) hide();
}
};
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.vlc_player_interface, container);
// Inject the RowsFragment in the popup container
if (getChildFragmentManager().findFragmentById(R.id.rows_area) == null) {
mPopupRowsFragment = new RowsFragment();
getChildFragmentManager().beginTransaction()
.replace(R.id.rows_area, mPopupRowsFragment).commit();
} else {
mPopupRowsFragment = (RowsFragment) getChildFragmentManager()
.findFragmentById(R.id.rows_area);
}
mPopupRowPresenter = new PositionableListRowPresenter();
mPopupRowAdapter = new ArrayObjectAdapter(mPopupRowPresenter);
mPopupRowsFragment.setAdapter(mPopupRowAdapter);
mPopupRowsFragment.setOnItemViewClickedListener(itemViewClickedListener);
// And the Live Guide element
mTvGuide = (RelativeLayout) inflater.inflate(R.layout.overlay_tv_guide, null);
root.addView(mTvGuide);
mTvGuide.setVisibility(View.GONE);
root.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
//if we're not visible, show us
if (!mIsVisible) show();
//and then manage our fade timer
if (mFadeEnabled) startFadeTimer();
TvApp.getApplication().getLogger().Debug("Got touch event.");
return false;
}
});
return root;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (mItemsToPlay == null || mItemsToPlay.size() == 0) return;
mPoster = (ImageView) mActivity.findViewById(R.id.poster);
mNextUpPoster = (ImageView) mActivity.findViewById(R.id.nextUpPoster);
mStudioImage = (ImageView) mActivity.findViewById(R.id.studioImg);
mLogoImage = (ImageView) mActivity.findViewById(R.id.logoImage);
mTopPanel = mActivity.findViewById(R.id.topPanel);
mBottomPanel = mActivity.findViewById(R.id.bottomPanel);
mNextUpPanel = mActivity.findViewById(R.id.nextUpPanel);
mSmNextUpPanel = mActivity.findViewById(R.id.smNextUpPanel);
mPlayPauseBtn = (ImageButton) mActivity.findViewById(R.id.playPauseBtn);
mPlayPauseBtn.setSecondaryImage(R.drawable.lb_ic_pause);
mPlayPauseBtn.setPrimaryImage(R.drawable.play);
mPlayPauseBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPlaybackController.playPause();
}
});
mInfoRow = (LinearLayout) mActivity.findViewById(R.id.infoRow);
mNextUpInfoRow = (LinearLayout) mActivity.findViewById(R.id.nextUpInfoRow);
mButtonRow = (LinearLayout) mActivity.findViewById(R.id.buttonRow);
mInfoSummary = (TextView) mActivity.findViewById(R.id.infoSummary);
mTitle = (TextView) mActivity.findViewById(R.id.title);
mNextUpTitle = (TextView) mActivity.findViewById(R.id.nextUpTitle);
mSmNextUpTitle = (TextView) mActivity.findViewById(R.id.sm_upnext_title);
mNextUpSummary = (TextView) mActivity.findViewById(R.id.nextUpSummary);
Typeface font = Typeface.createFromAsset(mActivity.getAssets(), "fonts/Roboto-Light.ttf");
mTitle.setTypeface(font);
mNextUpTitle.setTypeface(font);
mSmNextUpTitle.setTypeface(font);
mNextUpSummary.setTypeface(font);
mInfoSummary.setTypeface(font);
mEndTime = (TextView) mActivity.findViewById(R.id.endTime);
mCurrentPos = (TextView) mActivity.findViewById(R.id.currentPos);
mRemainingTime = (TextView) mActivity.findViewById(R.id.remainingTime);
mCurrentProgress = (ProgressBar) mActivity.findViewById(R.id.playerProgress);
mPopupArea = (FrameLayout) mActivity.findViewById(R.id.popupArea);
mStartsIn = (TextView) mActivity.findViewById(R.id.startsIn);
mNextButton = (Button) mActivity.findViewById(R.id.nextButton);
mNextButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPlaybackController.next();
}
});
mCancelButton = (Button) mActivity.findViewById(R.id.cancelButton);
mCancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
mSmStartsIn = (TextView) mActivity.findViewById(R.id.sm_upnext_startsin);
mSmNextButton = (Button) mActivity.findViewById(R.id.btn_sm_upnext_next);
mSmNextButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPlaybackController.next();
}
});
mSmCancelButton = (Button) mActivity.findViewById(R.id.btn_sm_upnext_cancel);
mSmCancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
//manual subtitles
mSubtitleText = (TextView) mActivity.findViewById(R.id.offLine_subtitleText);
mSubtitleText.setTextSize(32);
mSubtitleText.setShadowLayer(1.6f,1.5f,1.3f, Color.BLACK);
updateManualSubtitlePosition();
//pre-load animations
fadeOut = AnimationUtils.loadAnimation(mActivity, R.anim.abc_fade_out);
fadeOut.setAnimationListener(hideAnimationListener);
slideDown = AnimationUtils.loadAnimation(mActivity, R.anim.abc_slide_in_top);
slideUp = AnimationUtils.loadAnimation(mActivity, R.anim.abc_slide_in_bottom);
slideUp.setAnimationListener(showAnimationListener);
setupPopupAnimations();
setupNextUpAnimations();
//live guide
Typeface roboto = TvApp.getApplication().getDefaultFont();
mDisplayDate = (TextView) mActivity.findViewById(R.id.displayDate);
mGuideTitle = (TextView) mActivity.findViewById(R.id.guideTitle);
mGuideTitle.setTypeface(roboto);
mGuideCurrentTitle = (TextView) mActivity.findViewById(R.id.guideCurrentTitle);
mGuideCurrentTitle.setTypeface(roboto);
mSummary = (TextView) mActivity.findViewById(R.id.summary);
mSummary.setTypeface(roboto);
mChannelStatus = (TextView) mActivity.findViewById(R.id.channelsStatus);
mFilterStatus = (TextView) mActivity.findViewById(R.id.filterStatus);
mChannelStatus.setTypeface(roboto);
mFilterStatus.setTypeface(roboto);
mChannelStatus.setTextColor(Color.GRAY);
mFilterStatus.setTextColor(Color.GRAY);
mGuideInfoRow = (LinearLayout) mActivity.findViewById(R.id.guideInfoRow);
mChannels = (LinearLayout) mActivity.findViewById(R.id.channels);
mTimeline = (LinearLayout) mActivity.findViewById(R.id.timeline);
mProgramRows = (LinearLayout) mActivity.findViewById(R.id.programRows);
mGuideSpinner = mActivity.findViewById(R.id.spinner);
mProgramRows.setFocusable(false);
mChannelScroller = (ScrollView) mActivity.findViewById(R.id.channelScroller);
ObservableScrollView programVScroller = (ObservableScrollView) mActivity.findViewById(R.id.programVScroller);
programVScroller.setScrollViewListener(new ScrollViewListener() {
@Override
public void onScrollChanged(ObservableScrollView scrollView, int x, int y, int oldx, int oldy) {
mChannelScroller.scrollTo(x, y);
}
});
mTimelineScroller = (HorizontalScrollView) mActivity.findViewById(R.id.timelineHScroller);
mTimelineScroller.setFocusable(false);
mTimelineScroller.setFocusableInTouchMode(false);
mTimeline.setFocusable(false);
mTimeline.setFocusableInTouchMode(false);
mChannelScroller.setFocusable(false);
mChannelScroller.setFocusableInTouchMode(false);
ObservableHorizontalScrollView programHScroller = (ObservableHorizontalScrollView) mActivity.findViewById(R.id.programHScroller);
programHScroller.setScrollViewListener(new HorizontalScrollViewListener() {
@Override
public void onScrollChanged(ObservableHorizontalScrollView scrollView, int x, int y, int oldx, int oldy) {
mTimelineScroller.scrollTo(x, y);
}
});
programHScroller.setFocusable(false);
programHScroller.setFocusableInTouchMode(false);
mChannels.setFocusable(false);
mChannelScroller.setFocusable(false);
//Register to receive message from popup
mActivity.registerMessageListener(new IMessageListener() {
@Override
public void onMessageReceived(CustomMessage message) {
if (message.equals(CustomMessage.ActionComplete)) dismissProgramOptions();
}
});
Intent intent = mActivity.getIntent();
//start playing
int startPos = intent.getIntExtra("Position", 0);
mPlaybackController.play(startPos);
mPlayPauseBtn.requestFocus();
}
private void setupPopupAnimations() {
showPopup = AnimationUtils.loadAnimation(mActivity, R.anim.abc_slide_in_bottom);
showPopup.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
mPopupArea.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationEnd(Animation animation) {
mPopupArea.requestFocus();
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
hidePopup = AnimationUtils.loadAnimation(mActivity, R.anim.abc_fade_out);
hidePopup.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
mPopupArea.setVisibility(View.GONE);
mButtonRow.requestFocus();
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
}
private void setupNextUpAnimations() {
showNextUp = AnimationUtils.loadAnimation(mActivity, R.anim.abc_slide_in_bottom);
showNextUp.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
mNextUpPanel.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationEnd(Animation animation) {
mNextButton.requestFocus();
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
hideNextUp = AnimationUtils.loadAnimation(mActivity, R.anim.abc_fade_out);
hideNextUp.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
mNextUpPanel.setVisibility(View.GONE);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
showSmNextUp = AnimationUtils.loadAnimation(mActivity, R.anim.abc_slide_in_bottom);
showSmNextUp.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
mSmNextUpPanel.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationEnd(Animation animation) {
mSmNextButton.requestFocus();
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
hideSmNextUp = AnimationUtils.loadAnimation(mActivity, R.anim.abc_fade_out);
hideSmNextUp.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
mSmNextUpPanel.setVisibility(View.GONE);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
}
private AudioManager.OnAudioFocusChangeListener mAudioFocusChanged = new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
mPlaybackController.pause();
break;
case AudioManager.AUDIOFOCUS_LOSS:
//We don't do anything here on purpose
// On the Nexus we get this notification erroneously when first starting up
// and in any instance that we navigate away from our page, we already handle
// stopping video and handing back audio focus
break;
}
}
};
private OnItemViewClickedListener itemViewClickedListener = new OnItemViewClickedListener() {
@Override
public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
RowPresenter.ViewHolder rowViewHolder, Row row) {
if (item instanceof BaseRowItem) {
BaseRowItem rowItem = (BaseRowItem)item;
switch (rowItem.getItemType()) {
case Chapter:
Long start = rowItem.getChapterInfo().getStartPositionTicks() / 10000;
mPlaybackController.seek(start);
hidePopupPanel();
break;
}
}
}
};
private View.OnKeyListener keyListener = new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (mPopupPanelVisible && (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_BUTTON_B || keyCode == KeyEvent.KEYCODE_ESCAPE)) {
//back should just hide the popup panel
hidePopupPanel();
return true;
}
if (mGuideVisible) {
if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_BUTTON_B || keyCode == KeyEvent.KEYCODE_ESCAPE) {
//go back to normal
hideGuide();
return true;
} else if ((keyCode == KeyEvent.KEYCODE_MEDIA_PLAY || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) &&
(mSelectedProgram != null && mSelectedProgram.getChannelId() != null)) {
// tune to the current channel
Utils.Beep();
switchChannel(mSelectedProgram.getChannelId());
return true;
}
else {
return false;
}
}
if (mPlaybackController.isLiveTv() && keyCode == KeyEvent.KEYCODE_MENU || keyCode == KeyEvent.KEYCODE_BUTTON_Y) {
showGuide();
return true;
}
if (mNextUpPanelVisible) {
if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_BUTTON_B || keyCode == KeyEvent.KEYCODE_ESCAPE) {
//back should just hide the popup panel and show smaller one
hideNextUpPanel();
showSmNextUpPanel();
return true;
}
return false;
}
if (mSmNextUpPanelVisible) {
if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_BUTTON_B || keyCode == KeyEvent.KEYCODE_ESCAPE || keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
//back should just hide the popup panel
hideSmNextUpPanel();
return true;
}
return false;
}
if (mIsVisible && (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_BUTTON_B || keyCode == KeyEvent.KEYCODE_ESCAPE)) {
//back should just hide the panel
hide();
return true;
}
if (keyCode != KeyEvent.KEYCODE_BACK && keyCode != KeyEvent.KEYCODE_BUTTON_B && keyCode != KeyEvent.KEYCODE_ESCAPE) {
if (mPopupPanelVisible) {
// up or down should close panel
if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN || keyCode == KeyEvent.KEYCODE_DPAD_UP) {
hidePopupPanel();
return true;
} else {
return false;
}
}
if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT && !mIsVisible && !Utils.isFireTv() && !mPlaybackController.isLiveTv()) {
Utils.Beep(100);
mPlaybackController.skip(30000);
return true;
}
if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT && !mIsVisible && !Utils.isFireTv() && !mPlaybackController.isLiveTv()) {
Utils.Beep(100);
mPlaybackController.skip(-11000);
return true;
}
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER && !mIsVisible && !mPlaybackController.isLiveTv()) {
mPlaybackController.pause();
}
//if we're not visible, show us
if (!mIsVisible) show();
//and then manage our fade timer
if (mFadeEnabled) startFadeTimer();
}
return false;
}
};
public long getCurrentLocalStartDate() { return mCurrentLocalGuideStart; }
public long getCurrentLocalEndDate() { return mCurrentLocalGuideEnd; }
private void switchChannel(String id) {
if (id == null) return;
if (mPlaybackController.getCurrentlyPlayingItem().getId().equals(id)) {
//same channel, just dismiss overlay
hideGuide();
} else {
mPlaybackController.stop();
mCurrentProgress.setVisibility(View.VISIBLE);
hideGuide();
Utils.retrieveAndPlay(id, false, mActivity);
finish();
}
}
private void startFadeTimer() {
mHandler.removeCallbacks(mHideTask);
mHandler.postDelayed(mHideTask, 6000);
}
@Override
public void onResume() {
super.onResume();
if (mAudioManager.requestAudioFocus(mAudioFocusChanged, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
mApplication.getLogger().Error("Unable to get audio focus");
Utils.showToast(getActivity(), R.string.msg_cannot_play_time);
return;
}
//Register a media button receiver so that all media button presses will come to us and not another app
mAudioManager.registerMediaButtonEventReceiver(new ComponentName(TvApp.getApplication().getPackageName(), RemoteControlReceiver.class.getName()));
//TODO implement conditional logic for api 21+
if (!mIsVisible) show(); // in case we were paused during video playback
}
@Override
public void onDestroy() {
super.onDestroy();
if (mPlaybackController != null) {
if (mPlaybackController.getCurrentlyPlayingItem() != null) RecommendationManager.getInstance().recommend(mPlaybackController.getCurrentlyPlayingItem().getId());
mPlaybackController.removePreviousQueueItems();
}
}
@Override
public void onPause() {
super.onPause();
mPlaybackController.stop();
setPlayPauseActionState(ImageButton.STATE_PRIMARY); // in case we come back
//Give back audio focus
mAudioManager.abandonAudioFocus(mAudioFocusChanged);
mApplication.getLogger().Debug("Fragment pausing. IsFinishing: "+mActivity.isFinishing());
if (!mActivity.isFinishing()) mActivity.finish(); // user hit "home" we want to back out
}
public void show() {
hideInfo();
mBottomPanel.startAnimation(slideUp);
mTopPanel.startAnimation(slideDown);
mIsVisible = true;
mPlayPauseBtn.requestFocus();
}
public void hide() {
mIsVisible = false;
mBottomPanel.startAnimation(fadeOut);
mTopPanel.startAnimation(fadeOut);
}
private void showChapterPanel() {
setFadingEnabled(false);
mPopupArea.startAnimation(showPopup);
mPopupPanelVisible = true;
}
private void hidePopupPanel(){
setFadingEnabled(true);
mPopupArea.startAnimation(hidePopup);
mPopupPanelVisible = false;
}
private void showNextUpPanel() {
if (mIsVisible) hide();
if (mPopupPanelVisible) hidePopupPanel();
mNextUpPanelVisible = true;
mNextUpPanel.startAnimation(showNextUp);
}
private void hideNextUpPanel(){
mNextUpPanel.startAnimation(hideNextUp);
mNextUpPanelVisible = false;
}
private void showSmNextUpPanel() {
if (mIsVisible) hide();
if (mPopupPanelVisible) hidePopupPanel();
mSmNextUpPanelVisible = true;
mSmNextUpPanel.startAnimation(showSmNextUp);
}
private void hideSmNextUpPanel(){
mSmNextUpPanel.startAnimation(hideSmNextUp);
mSmNextUpPanelVisible = false;
}
private void showInfo() {
mButtonRow.setVisibility(View.INVISIBLE);
mCurrentProgress.setVisibility(View.INVISIBLE);
mRemainingTime.setVisibility(View.INVISIBLE);
mCurrentPos.setVisibility(View.INVISIBLE);
mPlayPauseBtn.setVisibility(View.INVISIBLE);
mInfoSummary.setVisibility(View.VISIBLE);
setFadingEnabled(false);
}
private void hideInfo() {
mButtonRow.setVisibility(View.VISIBLE);
mCurrentProgress.setVisibility(View.VISIBLE);
mRemainingTime.setVisibility(View.VISIBLE);
mCurrentPos.setVisibility(View.VISIBLE);
mPlayPauseBtn.setVisibility(View.VISIBLE);
mInfoSummary.setVisibility(View.INVISIBLE);
}
private void showGuide() {
hide();
mPlaybackController.mVideoManager.contractVideo(Utils.convertDpToPixel(mActivity, 300));
mTvGuide.setVisibility(View.VISIBLE);
mGuideVisible = true;
Calendar now = Calendar.getInstance();
boolean needLoad = mCurrentGuideStart == null;
if (!needLoad) {
Calendar needLoadTime = (Calendar) mCurrentGuideStart.clone();
needLoadTime.add(Calendar.MINUTE, 30);
needLoad = now.after(needLoadTime);
}
if (needLoad) {
loadGuide();
}
}
private void hideGuide() {
mTvGuide.setVisibility(View.GONE);
mPlaybackController.mVideoManager.setVideoFullSize();
mGuideVisible = false;
}
private void loadGuide() {
mGuideSpinner.setVisibility(View.VISIBLE);
fillTimeLine(mGuideHours);
if (mAllChannels == null) {
TvManager.loadAllChannels(new Response<Integer>() {
@Override
public void onResponse(Integer ndx) {
if (ndx >= PAGE_SIZE) {
// last channel is not in first page so grab a set where it will be in the middle
ndx = ndx - (PAGE_SIZE / 2);
} else {
ndx = 0; // just start at beginning
}
mAllChannels = TvManager.getAllChannels();
if (mAllChannels.size() > 0) {
displayChannels(ndx, PAGE_SIZE);
} else {
mGuideSpinner.setVisibility(View.GONE);
}
}
});
}
//
}
public void displayChannels(int start, int max) {
int end = start + max;
if (end > mAllChannels.size()) end = mAllChannels.size();
mCurrentDisplayChannelStartNdx = start;
mCurrentDisplayChannelEndNdx = end - 1;
TvApp.getApplication().getLogger().Debug("*** Display channels pre-execute");
mGuideSpinner.setVisibility(View.VISIBLE);
mChannels.removeAllViews();
mProgramRows.removeAllViews();
mChannelStatus.setText("");
mFilterStatus.setText("");
TvManager.getProgramsAsync(mCurrentDisplayChannelStartNdx, mCurrentDisplayChannelEndNdx, mCurrentGuideEnd, new EmptyResponse() {
@Override
public void onResponse() {
TvApp.getApplication().getLogger().Debug("*** Programs response");
if (mDisplayProgramsTask != null) mDisplayProgramsTask.cancel(true);
mDisplayProgramsTask = new DisplayProgramsTask();
mDisplayProgramsTask.execute(mCurrentDisplayChannelStartNdx, mCurrentDisplayChannelEndNdx);
}
});
}
DisplayProgramsTask mDisplayProgramsTask;
class DisplayProgramsTask extends AsyncTask<Integer, Integer, Void> {
View firstRow;
int displayedChannels = 0;
@Override
protected void onPreExecute() {
TvApp.getApplication().getLogger().Debug("*** Display programs pre-execute");
mChannels.removeAllViews();
mProgramRows.removeAllViews();
mFirstFocusChannelId = mPlaybackController.getCurrentlyPlayingItem().getId();
if (mCurrentDisplayChannelStartNdx > 0) {
// Show a paging row for channels above
int pageUpStart = mCurrentDisplayChannelStartNdx - PAGE_SIZE;
if (pageUpStart < 0) pageUpStart = 0;
TextView placeHolder = new TextView(mActivity);
placeHolder.setHeight(LiveTvGuideActivity.PAGEBUTTON_HEIGHT);
mChannels.addView(placeHolder);
displayedChannels = 0;
mProgramRows.addView(new GuidePagingButton(mActivity, mFragment, pageUpStart, getString(R.string.lbl_load_channels)+mAllChannels.get(pageUpStart).getNumber() + " - "+mAllChannels.get(mCurrentDisplayChannelStartNdx-1).getNumber()));
}
}
@Override
protected Void doInBackground(Integer... params) {
int start = params[0];
int end = params[1];
boolean first = true;
TvApp.getApplication().getLogger().Debug("*** About to iterate programs");
LinearLayout prevRow = null;
for (int i = start; i <= end; i++) {
if (isCancelled()) return null;
final ChannelInfoDto channel = TvManager.getChannel(i);
List<BaseItemDto> programs = TvManager.getProgramsForChannel(channel.getId());
final LinearLayout row = getProgramRow(programs, channel.getId());
if (first) {
first = false;
firstRow = row;
}
// put focus on the last tuned channel
if (channel.getId().equals(mFirstFocusChannelId)) {
firstRow = row;
mFirstFocusChannelId = null; // only do this first time in not while paging around
}
// set focus parameters if we are not on first row
// this makes focus movements more predictable for the grid view
if (prevRow != null) {
TvManager.setFocusParms(row, prevRow, true);
TvManager.setFocusParms(prevRow, row, false);
}
prevRow = row;
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
GuideChannelHeader header = new GuideChannelHeader(mActivity, channel);
mChannels.addView(header);
header.loadImage();
mProgramRows.addView(row);
}
});
displayedChannels++;
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
TvApp.getApplication().getLogger().Debug("*** Display programs post execute");
if (mCurrentDisplayChannelEndNdx < mAllChannels.size()-1) {
// Show a paging row for channels below
int pageDnEnd = mCurrentDisplayChannelEndNdx + PAGE_SIZE;
if (pageDnEnd >= mAllChannels.size()) pageDnEnd = mAllChannels.size()-1;
TextView placeHolder = new TextView(mActivity);
placeHolder.setHeight(LiveTvGuideActivity.PAGEBUTTON_HEIGHT);
mChannels.addView(placeHolder);
mProgramRows.addView(new GuidePagingButton(mActivity, mFragment, mCurrentDisplayChannelEndNdx + 1, getString(R.string.lbl_load_channels)+mAllChannels.get(mCurrentDisplayChannelEndNdx+1).getNumber() + " - "+mAllChannels.get(pageDnEnd).getNumber()));
}
mChannelStatus.setText(displayedChannels+" of "+mAllChannels.size()+" channels");
mFilterStatus.setText(" for next "+ mGuideHours+" hours");
mFilterStatus.setTextColor(Color.GRAY);
mGuideSpinner.setVisibility(View.GONE);
if (firstRow != null) firstRow.requestFocus();
}
}
private int currentCellId = 0;
private LinearLayout getProgramRow(List<BaseItemDto> programs, String channelId) {
LinearLayout programRow = new LinearLayout(mActivity);
if (programs.size() == 0) {
BaseItemDto empty = new BaseItemDto();
empty.setName(" <No Program Data Available>");
empty.setChannelId(channelId);
empty.setStartDate(Utils.convertToUtcDate(new Date(mCurrentLocalGuideStart)));
empty.setEndDate(Utils.convertToUtcDate(new Date(mCurrentLocalGuideStart + (150 * 60000))));
ProgramGridCell cell = new ProgramGridCell(mActivity, mFragment, empty);
cell.setId(currentCellId++);
cell.setLayoutParams(new ViewGroup.LayoutParams(150 * PIXELS_PER_MINUTE, LiveTvGuideActivity.ROW_HEIGHT));
cell.setFocusable(true);
programRow.addView(cell);
return programRow;
}
long prevEnd = getCurrentLocalStartDate();
for (BaseItemDto item : programs) {
long start = item.getStartDate() != null ? Utils.convertToLocalDate(item.getStartDate()).getTime() : getCurrentLocalStartDate();
if (start < getCurrentLocalStartDate()) start = getCurrentLocalStartDate();
if (start > getCurrentLocalEndDate()) continue;
if (start > prevEnd) {
// fill empty time slot
BaseItemDto empty = new BaseItemDto();
empty.setName(" <No Program Data Available>");
empty.setChannelId(channelId);
empty.setStartDate(Utils.convertToUtcDate(new Date(prevEnd)));
Long duration = (start - prevEnd);
empty.setEndDate(Utils.convertToUtcDate(new Date(prevEnd + duration)));
ProgramGridCell cell = new ProgramGridCell(mActivity, mFragment, empty);
cell.setId(currentCellId++);
cell.setLayoutParams(new ViewGroup.LayoutParams(((Long) (duration / 60000)).intValue() * PIXELS_PER_MINUTE, LiveTvGuideActivity.ROW_HEIGHT));
cell.setFocusable(true);
programRow.addView(cell);
}
long end = item.getEndDate() != null ? Utils.convertToLocalDate(item.getEndDate()).getTime() : getCurrentLocalEndDate();
if (end > getCurrentLocalEndDate()) end = getCurrentLocalEndDate();
prevEnd = end;
Long duration = (end - start) / 60000;
//TvApp.getApplication().getLogger().Debug("Duration for "+item.getName()+" is "+duration.intValue());
if (duration > 0) {
ProgramGridCell program = new ProgramGridCell(mActivity, mFragment, item);
program.setId(currentCellId++);
program.setLayoutParams(new ViewGroup.LayoutParams(duration.intValue() * PIXELS_PER_MINUTE, LiveTvGuideActivity.ROW_HEIGHT));
program.setFocusable(true);
programRow.addView(program);
}
}
return programRow;
}
private void fillTimeLine(int hours) {
mCurrentGuideStart = Calendar.getInstance();
mCurrentGuideStart.set(Calendar.MINUTE, mCurrentGuideStart.get(Calendar.MINUTE) >= 30 ? 30 : 0);
mCurrentGuideStart.set(Calendar.SECOND, 0);
mCurrentGuideStart.set(Calendar.MILLISECOND, 0);
mCurrentLocalGuideStart = mCurrentGuideStart.getTimeInMillis();
mDisplayDate.setText(Utils.getFriendlyDate(mCurrentGuideStart.getTime()));
Calendar current = (Calendar) mCurrentGuideStart.clone();
mCurrentGuideEnd = (Calendar) mCurrentGuideStart.clone();
int oneHour = 60 * PIXELS_PER_MINUTE;
int halfHour = 30 * PIXELS_PER_MINUTE;
int interval = current.get(Calendar.MINUTE) >= 30 ? 30 : 60;
mCurrentGuideEnd.add(Calendar.HOUR, hours);
mCurrentLocalGuideEnd = mCurrentGuideEnd.getTimeInMillis();
mTimeline.removeAllViews();
while (current.before(mCurrentGuideEnd)) {
TextView time = new TextView(mActivity);
time.setText(android.text.format.DateFormat.getTimeFormat(mActivity).format(current.getTime()));
time.setWidth(interval == 30 ? halfHour : oneHour);
mTimeline.addView(time);
current.add(Calendar.MINUTE, interval);
//after first one, we always go on hours
interval = 60;
}
}
private Runnable detailUpdateTask = new Runnable() {
@Override
public void run() {
if (mSelectedProgram.getOverview() == null && mSelectedProgram.getId() != null) {
TvApp.getApplication().getApiClient().GetItemAsync(mSelectedProgram.getId(), TvApp.getApplication().getCurrentUser().getId(), new Response<BaseItemDto>() {
@Override
public void onResponse(BaseItemDto response) {
mSelectedProgram = response;
detailUpdateInternal();
}
@Override
public void onError(Exception exception) {
TvApp.getApplication().getLogger().ErrorException("Unable to get program details", exception);
detailUpdateInternal();
}
});
} else {
detailUpdateInternal();
}
}
};
private void detailUpdateInternal() {
mGuideTitle.setText(mSelectedProgram.getName());
mSummary.setText(mSelectedProgram.getOverview());
if (mSelectedProgram.getId() != null) {
mDisplayDate.setText(Utils.getFriendlyDate(Utils.convertToLocalDate(mSelectedProgram.getStartDate())));
//info row
InfoLayoutHelper.addInfoRow(mActivity, mSelectedProgram, mGuideInfoRow, false, false);
} else {
mGuideInfoRow.removeAllViews();
}
}
public void setSelectedProgram(ProgramGridCell programView) {
mSelectedProgramView = programView;
mSelectedProgram = programView.getProgram();
mHandler.removeCallbacks(detailUpdateTask);
mHandler.postDelayed(detailUpdateTask, 500);
}
public void dismissProgramOptions() {
if (mDetailPopup != null) mDetailPopup.dismiss();
}
private LiveProgramDetailPopup mDetailPopup;
public void showProgramOptions() {
if (mSelectedProgram == null) return;
if (mDetailPopup == null) mDetailPopup = new LiveProgramDetailPopup(mActivity, Utils.convertDpToPixel(mActivity, 600), new EmptyResponse() {
@Override
public void onResponse() {
switchChannel(mSelectedProgram.getChannelId());
}
});
mDetailPopup.setContent(mSelectedProgram, mSelectedProgramView);
mDetailPopup.show(mGuideTitle, mTitle.getLeft(), mGuideTitle.getTop() - 10);
}
private Animation.AnimationListener hideAnimationListener = new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
mTopPanel.setVisibility(View.GONE);
mBottomPanel.setVisibility(View.GONE);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
};
private Animation.AnimationListener showAnimationListener = new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
mTopPanel.setVisibility(View.VISIBLE);
mBottomPanel.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
};
private void updatePoster(BaseItemDto item, ImageView target, boolean preferSeries) {
if (getActivity() != null && !getActivity().isFinishing()) {
int height = Utils.convertDpToPixel(getActivity(), 300);
int width = Utils.convertDpToPixel(getActivity(), 150);
String posterImageUrl = Utils.getPrimaryImageUrl(item, mApplication.getApiClient(), false, false, false, preferSeries, height);
if (posterImageUrl != null) Picasso.with(getActivity()).load(posterImageUrl).skipMemoryCache().resize(width, height).centerInside().into(target);
}
}
private void updateLogo(BaseItemDto item, ImageView target) {
if (getActivity() != null && !getActivity().isFinishing()) {
int height = Utils.convertDpToPixel(getActivity(), 60);
int width = Utils.convertDpToPixel(getActivity(), 180);
String imageUrl = Utils.getLogoImageUrl(item, mApplication.getApiClient());
if (imageUrl != null) Picasso.with(getActivity()).load(imageUrl).skipMemoryCache().resize(width, height).centerInside().into(target);
}
}
private void updateStudio(BaseItemDto item) {
if (getActivity() != null && !getActivity().isFinishing()) {
int height = Utils.convertDpToPixel(mActivity, 30);
int width = Utils.convertDpToPixel(mActivity, 70);
if (item.getStudios() != null && item.getStudios().length > 0 && item.getStudios()[0].getHasPrimaryImage()) {
String studioImageUrl = Utils.getPrimaryImageUrl(item.getStudios()[0], mApplication.getApiClient(), height);
if (studioImageUrl != null)
Picasso.with(mActivity).load(studioImageUrl).resize(width, height).centerInside().into(mStudioImage);
} else {
if (item.getSeriesStudio() != null) {
String studioImageUrl = null;
try {
ImageOptions options = new ImageOptions();
options.setMaxHeight(height);
options.setImageType(ImageType.Primary);
studioImageUrl = mApplication.getApiClient().GetStudioImageUrl(URLEncoder.encode(item.getSeriesStudio(), "utf-8"), options);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
if (studioImageUrl != null)
Picasso.with(mActivity).load(studioImageUrl).resize(width, height).centerInside().into(mStudioImage);
} else {
mStudioImage.setImageResource(R.drawable.blank30x30);
}
}
}
}
public void updateEndTime(final long timeLeft) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
mEndTime.setText(timeLeft > 0 ?
mApplication.getString(R.string.lbl_ends) + android.text.format.DateFormat.getTimeFormat(TvApp.getApplication()).format(System.currentTimeMillis() + timeLeft)
: ""
);
}
});
}
private void updateCurrentDuration(BaseItemDto item) {
Long mbRuntime = item.getRunTimeTicks();
Long andDuration = mbRuntime != null ? mbRuntime / 10000: 0;
mCurrentDuration = andDuration.intValue();
}
private void addButtons(BaseItemDto item) {
mButtonRow.removeAllViews();
if (!Utils.isFireTv() && mPlaybackController.canSeek()) {
// on-screen jump buttons for Nexus
mButtonRow.addView(new ImageButton(mActivity, R.drawable.repeat, mButtonSize, new View.OnClickListener() {
@Override
public void onClick(View v) {
mPlaybackController.skip(-11000);
startFadeTimer();
}
}));
mButtonRow.addView(new ImageButton(mActivity, R.drawable.fastforward, mButtonSize, new View.OnClickListener() {
@Override
public void onClick(View v) {
mPlaybackController.skip(30000);
startFadeTimer();
}
}));
}
if (mPlaybackController.isLiveTv()) {
// prev channel button
if (TvManager.getPrevLiveTvChannel() != null) {
mButtonRow.addView(new ImageButton(mActivity, R.drawable.prev, mButtonSize, new View.OnClickListener() {
@Override
public void onClick(View v) {
switchChannel(TvManager.getPrevLiveTvChannel());
}
}));
}
// guide button
mButtonRow.addView(new ImageButton(mActivity, R.drawable.guidebutton, mButtonSize, new View.OnClickListener() {
@Override
public void onClick(View v) {
showGuide();
}
}));
} else if (!TextUtils.isEmpty(item.getOverview())) {
mButtonRow.addView(new ImageButton(mActivity, R.drawable.infoicon, mButtonSize, new View.OnClickListener() {
@Override
public void onClick(View v) {
showInfo();
}
}));
}
Boolean hasSubs = Utils.GetSubtitleStreams(mPlaybackController.getCurrentMediaSource()).size() > 0;
Boolean hasMultiAudio = Utils.GetAudioStreams(mPlaybackController.getCurrentMediaSource()).size() > 1;
if (hasMultiAudio) {
mApplication.getLogger().Debug("Multiple Audio tracks found: "+Utils.GetAudioStreams(mPlaybackController.getCurrentMediaSource()).size());
mButtonRow.addView(new ImageButton(mActivity, R.drawable.audiosel, mButtonSize, new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mPlaybackController.getCurrentStreamInfo() == null) {
TvApp.getApplication().getLogger().Warn("StreamInfo null trying to obtain audio tracks");
Utils.showToast(TvApp.getApplication(), "Unable to obtain audio track info");
return;
}
setFadingEnabled(false);
List<MediaStream> audioTracks = TvApp.getApplication().getPlaybackManager().getInPlaybackSelectableAudioStreams(mPlaybackController.getCurrentStreamInfo());
Integer currentAudioIndex = mPlaybackController.getAudioStreamIndex();
PopupMenu audioMenu = Utils.createPopupMenu(getActivity(), v, Gravity.RIGHT);
for (MediaStream audio : audioTracks) {
MenuItem item = audioMenu.getMenu().add(0, audio.getIndex(), audio.getIndex(), Utils.SafeToUpper(audio.getLanguage()) + " " + Utils.SafeToUpper(audio.getCodec()) + " (" + audio.getChannelLayout() + ")");
if (currentAudioIndex != null && currentAudioIndex == audio.getIndex()) item.setChecked(true);
}
audioMenu.getMenu().setGroupCheckable(0, true, false);
audioMenu.setOnDismissListener(new PopupMenu.OnDismissListener() {
@Override
public void onDismiss(PopupMenu menu) {
setFadingEnabled(true);
}
});
audioMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
mApplication.getLogger().Debug("Selected stream " + item.getTitle());
mPlaybackController.switchAudioStream(item.getItemId());
return true;
}
});
audioMenu.show();
}
}));
} else {
mApplication.getLogger().Debug("Only one audio track.");
}
if (hasSubs) {
mApplication.getLogger().Debug("Subtitle tracks found: " + mPlaybackController.getSubtitleStreams().size());
mButtonRow.addView(new ImageButton(mActivity, R.drawable.subt, mButtonSize, new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mPlaybackController.getCurrentStreamInfo() == null) {
TvApp.getApplication().getLogger().Warn("StreamInfo null trying to obtain subtitles");
Utils.showToast(TvApp.getApplication(), "Unable to obtain subtitle info");
return;
}
setFadingEnabled(false);
List<SubtitleStreamInfo> subtitles = mPlaybackController.getSubtitleStreams();
PopupMenu subMenu = Utils.createPopupMenu(getActivity(), v, Gravity.RIGHT);
MenuItem none = subMenu.getMenu().add(0, -1, 0, mApplication.getString(R.string.lbl_none));
int currentSubIndex = mPlaybackController.getSubtitleStreamIndex();
if (currentSubIndex < 0) none.setChecked(true);
for (SubtitleStreamInfo sub : subtitles) {
MenuItem item = subMenu.getMenu().add(0, sub.getIndex(), sub.getIndex(), Utils.FirstToUpper(sub.getName() != null ? sub.getName() : sub.getLanguage()) + (sub.getIsForced() ? mApplication.getString(R.string.lbl_parens_forced) : ""));
if (currentSubIndex == sub.getIndex()) item.setChecked(true);
}
subMenu.getMenu().setGroupCheckable(0, true, false);
subMenu.setOnDismissListener(new PopupMenu.OnDismissListener() {
@Override
public void onDismiss(PopupMenu menu) {
setFadingEnabled(true);
}
});
subMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
mApplication.getLogger().Debug("Selected subtitle " + item.getTitle());
mPlaybackController.switchSubtitleStream(item.getItemId());
return true;
}
});
subMenu.show();
}
}));
} else {
mApplication.getLogger().Debug("No sub tracks found.");
}
List<ChapterInfoDto> chapters = item.getChapters();
if (chapters != null && chapters.size() > 0) {
mButtonRow.addView(new ImageButton(mActivity, R.drawable.chapter, mButtonSize, new View.OnClickListener() {
@Override
public void onClick(View v) {
showChapterPanel();
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
int ndx = getCurrentChapterIndex(mPlaybackController.getCurrentlyPlayingItem(), mPlaybackController.getCurrentPosition() * 10000);
if (ndx > 0) {
mPopupRowPresenter.setPosition(ndx);
}
}
},500);
}
}));
//Create chapter row for later use
ItemRowAdapter chapterAdapter = new ItemRowAdapter(Utils.buildChapterItems(item), new CardPresenter(), new ArrayObjectAdapter());
chapterAdapter.Retrieve();
if (mChapterRow != null) mPopupRowAdapter.remove(mChapterRow);
mChapterRow = new ListRow(new HeaderItem(mActivity.getString(R.string.chapters)), chapterAdapter);
mPopupRowAdapter.add(mChapterRow);
}
if (mPlaybackController.hasNextItem()) {
mButtonRow.addView(new ImageButton(mActivity, R.drawable.lb_ic_skip_next, mButtonSize, new View.OnClickListener() {
@Override
public void onClick(View v) {
mPlaybackController.next();
}
}));
}
if (!mPlaybackController.isNativeMode()) {
if (mAudioPopup == null ) mAudioPopup = new AudioDelayPopup(mActivity, mBottomPanel, new ValueChangedListener<Long>() {
@Override
public void onValueChanged(Long value) {
mPlaybackController.setAudioDelay(value);
}
});
mButtonRow.addView(new ImageButton(mActivity, R.drawable.adjust, mButtonSize, new View.OnClickListener() {
@Override
public void onClick(View v) {
mAudioPopup.show(mPlaybackController.getAudioDelay());
}
}));
} else {
mButtonRow.addView(new ImageButton(mActivity, R.drawable.zoom, mButtonSize, new View.OnClickListener() {
@Override
public void onClick(View v) {
PopupMenu zoomMenu = Utils.createPopupMenu(mActivity, v, Gravity.RIGHT);
zoomMenu.getMenu().add(0, VideoManager.ZOOM_NORMAL, VideoManager.ZOOM_NORMAL, mApplication.getString(R.string.lbl_normal)).setChecked(mPlaybackController.getZoomMode() == VideoManager.ZOOM_NORMAL);
zoomMenu.getMenu().add(0, VideoManager.ZOOM_VERTICAL, VideoManager.ZOOM_VERTICAL, mApplication.getString(R.string.lbl_vertical_stretch)).setChecked(mPlaybackController.getZoomMode() == VideoManager.ZOOM_VERTICAL);
zoomMenu.getMenu().add(0, VideoManager.ZOOM_HORIZONTAL, VideoManager.ZOOM_HORIZONTAL, mApplication.getString(R.string.lbl_horizontal_stretch)).setChecked(mPlaybackController.getZoomMode() == VideoManager.ZOOM_HORIZONTAL);
zoomMenu.getMenu().add(0, VideoManager.ZOOM_FULL, VideoManager.ZOOM_FULL, mApplication.getString(R.string.lbl_zoom)).setChecked(mPlaybackController.getZoomMode() == VideoManager.ZOOM_FULL);
zoomMenu.getMenu().setGroupCheckable(0, true, false);
zoomMenu.setOnDismissListener(new PopupMenu.OnDismissListener() {
@Override
public void onDismiss(PopupMenu menu) {
setFadingEnabled(true);
}
});
zoomMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
mPlaybackController.setZoom(item.getItemId());
return true;
}
});
setFadingEnabled(false);
zoomMenu.show();
}
}));
}
}
private int getCurrentChapterIndex(BaseItemDto item, long pos) {
int ndx = 0;
TvApp.getApplication().getLogger().Debug("*** looking for chapter at pos: "+pos);
if (item.getChapters() != null) {
for (ChapterInfoDto chapter : item.getChapters()) {
TvApp.getApplication().getLogger().Debug("*** chapter "+ndx+" has pos: "+chapter.getStartPositionTicks());
if (chapter.getStartPositionTicks() > pos) return ndx - 1;
ndx++;
}
}
return ndx - 1;
}
AudioDelayPopup mAudioPopup;
@Override
public void setCurrentTime(long time) {
if (mNextUpPanelVisible) {
mStartsIn.setText(mCurrentDuration > 0 ? "Starts in " + Utils.formatMillis(mCurrentDuration - time) : "");
} else if (mSmNextUpPanelVisible) {
mSmStartsIn.setText(mCurrentDuration > 0 ? "Starts in " + Utils.formatMillis(mCurrentDuration - time) : "");
} else {
mCurrentProgress.setProgress(((Long)time).intValue());
mCurrentPos.setText(Utils.formatMillis(time));
mRemainingTime.setText(mCurrentDuration > 0 ? "-" + Utils.formatMillis(mCurrentDuration - time) : "");
}
}
@Override
public void setFadingEnabled(boolean value) {
mFadeEnabled = value;
if (mFadeEnabled) {
if (mIsVisible) startFadeTimer();
} else {
mHandler.removeCallbacks(mHideTask);
if (!mIsVisible) getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
show();
}
});
}
}
@Override
public void setPlayPauseActionState(final int state) {
if (getActivity() != null && !getActivity().isFinishing()) getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
mPlayPauseBtn.setState(state);
}
});
}
@Override
public void updateDisplay() {
BaseItemDto current = mPlaybackController.getCurrentlyPlayingItem();
if (current != null & mActivity != null && !mActivity.isFinishing()) {
if (mNextUpPanelVisible) hideNextUpPanel();
if (mSmNextUpPanelVisible) hideSmNextUpPanel();
updateCurrentDuration(current);
//set progress to match duration
mCurrentProgress.setMax(mCurrentDuration);
//set other information
mTitle.setText(current.getName());
mInfoSummary.setText(current.getOverview());
mGuideCurrentTitle.setText(" "+current.getName());
updatePoster(current, mPoster, true);
updateLogo(current, mLogoImage);
updateStudio(current);
addButtons(current);
InfoLayoutHelper.addInfoRow(mActivity, current, mInfoRow, true, false, mPlaybackController.getCurrentMediaSource().GetDefaultAudioStream(mPlaybackController.getAudioStreamIndex()));
StreamInfo stream = mPlaybackController.getCurrentStreamInfo();
if (stream != null) {
switch (stream.getPlayMethod()) {
case Transcode:
InfoLayoutHelper.addBlockText(mActivity, mInfoRow, "Trans" + (mPlaybackController.mVideoManager.isNativeMode() ? "/I" : "/V"));
break;
case DirectStream:
case DirectPlay:
InfoLayoutHelper.addBlockText(mActivity, mInfoRow, "Direct" + (mPlaybackController.mVideoManager.isNativeMode() ? "/I" : "/V"));
break;
}
}
if (mIsVisible) mPlayPauseBtn.requestFocus();
}
}
@Override
public void nextItemThresholdHit(BaseItemDto nextItem) {
mApplication.getLogger().Debug("Next Item is " + nextItem.getName());
//we need to retrieve full item for all info
mApplication.getApiClient().GetItemAsync(nextItem.getId(), mApplication.getCurrentUser().getId(), new Response<BaseItemDto>() {
@Override
public void onResponse(BaseItemDto response) {
mNextUpTitle.setText("Up Next... " + response.getName());
mSmNextUpTitle.setText("Up Next... " + response.getName());
mNextUpSummary.setText(response.getOverview());
InfoLayoutHelper.addInfoRow(mActivity, response, mNextUpInfoRow, true, true);
updatePoster(response, mNextUpPoster, true);
showNextUpPanel();
}
});
}
@Override
public void finish() {
getActivity().finish();
}
private SubtitleTrackInfo mManualSubs;
private long lastReportedPosMs;
private void updateManualSubtitlePosition() {
/*
* Adjust subtitles margin based on Screen dimes
*/
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mSubtitleText.getLayoutParams();
DisplayMetrics dm = new DisplayMetrics();
mActivity.getWindowManager().getDefaultDisplay().getMetrics(dm);
params.topMargin = (dm.heightPixels / 2) - 140;
params.rightMargin = params.leftMargin = dm.widthPixels / 4;
mSubtitleText.setLayoutParams(params);
}
public void addManualSubtitles(SubtitleTrackInfo info) {
mManualSubs = info;
lastReportedPosMs = 0;
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
mSubtitleText.setVisibility(View.INVISIBLE);
mSubtitleText.setText("");
}
});
}
public void showSubLoadingMsg(final boolean show) {
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (show) {
mSubtitleText.setText(R.string.msg_subtitles_loading);
mSubtitleText.setVisibility(View.VISIBLE);
} else {
mSubtitleText.setVisibility(View.INVISIBLE);
mSubtitleText.setText("");
}
}
});
}
public void updateSubtitles(long positionMs) {
if (lastReportedPosMs > 0){
if (Math.abs(lastReportedPosMs - positionMs) < 500) {
return;
}
}
if (mManualSubs == null) {
return;
}
long positionTicks = positionMs * 10000;
for (SubtitleTrackEvent caption : mManualSubs.getTrackEvents()) {
if (positionTicks >= caption.getStartPositionTicks() && positionTicks <= caption.getEndPositionTicks()) {
setTimedText(caption);
return;
}
}
setTimedText(null);
}
private void setTimedText(final SubtitleTrackEvent textObj) {
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (textObj == null) {
mSubtitleText.setVisibility(View.INVISIBLE);
return;
}
String text = textObj.getText();
if (text == null || text.length() == 0) {
mSubtitleText.setVisibility(View.INVISIBLE);
return;
}
mSubtitleText.setText(Html.fromHtml(text));
mSubtitleText.setVisibility(View.VISIBLE);
}
});
}}