TunableTvView.java revision 87c1cbb508c96538d1e69ba1fdf3d9f4d52710d6
1package com.android.tv.ui; 2 3import android.content.Context; 4import android.media.tv.TvInputInfo; 5import android.media.tv.TvView; 6import android.util.AttributeSet; 7import android.util.Log; 8import android.view.SurfaceHolder; 9import android.view.SurfaceView; 10 11import com.android.tv.data.Channel; 12import com.android.tv.data.StreamInfo; 13import com.android.tv.util.TvInputManagerHelper; 14import com.android.tv.util.Utils; 15 16public class TunableTvView extends TvView implements StreamInfo { 17 private static final boolean DEBUG = true; 18 private static final String TAG = "TunableTvView"; 19 20 private static final int DELAY_FOR_SURFACE_RELEASE = 300; 21 22 private float mVolume; 23 private long mChannelId = Channel.INVALID_ID; 24 private TvInputManagerHelper mInputManagerHelper; 25 private boolean mStarted; 26 private TvInputInfo mInputInfo; 27 private OnTuneListener mOnTuneListener; 28 private int mVideoFormat = StreamInfo.VIDEO_DEFINITION_LEVEL_UNKNOWN; 29 private int mAudioChannelCount = StreamInfo.AUDIO_CHANNEL_COUNT_UNKNOWN; 30 private boolean mHasClosedCaption = false; 31 private SurfaceView mSurface; 32 33 private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() { 34 @Override 35 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } 36 37 @Override 38 public void surfaceCreated(SurfaceHolder holder) { } 39 40 @Override 41 public void surfaceDestroyed(SurfaceHolder holder) { 42 // TODO: It is a hack to wait to release a surface at TIS. If there is a way to 43 // know when the surface is released at TIS, we don't need this hack. 44 try { 45 if (DEBUG) Log.d(TAG, "Sleep to wait destroying a surface"); 46 Thread.sleep(DELAY_FOR_SURFACE_RELEASE); 47 if (DEBUG) Log.d(TAG, "Wake up from sleeping"); 48 } catch (InterruptedException e) { 49 e.printStackTrace(); 50 } 51 } 52 }; 53 54 private final TvInputListener mListener = 55 new TvInputListener() { 56 @Override 57 public void onError(String inputId, int errorCode) { 58 if (errorCode == TvView.ERROR_BUSY) { 59 Log.w(TAG, "Failed to bind an input"); 60 long channelId = mChannelId; 61 mChannelId = Channel.INVALID_ID; 62 mInputInfo = null; 63 if (mOnTuneListener != null) { 64 mOnTuneListener.onTuned(false, channelId); 65 mOnTuneListener = null; 66 } 67 } else if (errorCode == TvView.ERROR_TV_INPUT_DISCONNECTED) { 68 Log.w(TAG, "Session is released by crash"); 69 long channelId = mChannelId; 70 mChannelId = Channel.INVALID_ID; 71 mInputInfo = null; 72 if (mOnTuneListener != null) { 73 mOnTuneListener.onUnexpectedStop(channelId); 74 mOnTuneListener = null; 75 } 76 } 77 } 78 79 @Override 80 public void onVideoStreamChanged(String inputId, int width, int height, 81 boolean interlaced) { 82 mVideoFormat = Utils.getVideoDefinitionLevelFromSize(width, height); 83 if (mOnTuneListener != null) { 84 mOnTuneListener.onStreamInfoChanged(TunableTvView.this); 85 } 86 } 87 88 @Override 89 public void onAudioStreamChanged(String inputId, int channelCount) { 90 mAudioChannelCount = channelCount; 91 if (mOnTuneListener != null) { 92 mOnTuneListener.onStreamInfoChanged(TunableTvView.this); 93 } 94 } 95 96 @Override 97 public void onClosedCaptionStreamChanged(String inputId, boolean hasClosedCaption) { 98 mHasClosedCaption = hasClosedCaption; 99 if (mOnTuneListener != null) { 100 mOnTuneListener.onStreamInfoChanged(TunableTvView.this); 101 } 102 } 103 }; 104 105 public TunableTvView(Context context) { 106 this(context, null, 0); 107 } 108 109 public TunableTvView(Context context, AttributeSet attrs) { 110 this(context, attrs, 0); 111 } 112 113 public TunableTvView(Context context, AttributeSet attrs, int defStyleAttr) { 114 super(context, attrs, defStyleAttr); 115 for (int i = 0; i < getChildCount(); ++i) { 116 if (getChildAt(i) instanceof SurfaceView) { 117 mSurface = (SurfaceView) getChildAt(i); 118 mSurface.getHolder().addCallback(mSurfaceHolderCallback); 119 return; 120 } 121 } 122 throw new RuntimeException("TvView does not have SurfaceView."); 123 } 124 125 public void start(TvInputManagerHelper tvInputManagerHelper) { 126 mInputManagerHelper = tvInputManagerHelper; 127 if (mStarted) { 128 return; 129 } 130 mStarted = true; 131 } 132 133 public void stop() { 134 if (!mStarted) { 135 return; 136 } 137 mStarted = false; 138 reset(); 139 mChannelId = Channel.INVALID_ID; 140 mInputInfo = null; 141 mOnTuneListener = null; 142 } 143 144 public boolean isPlaying() { 145 return mStarted; 146 } 147 148 public boolean tuneTo(long channelId, OnTuneListener listener) { 149 if (!mStarted) { 150 throw new IllegalStateException("TvView isn't started"); 151 } 152 if (DEBUG) Log.d(TAG, "tuneTo " + channelId); 153 mVideoFormat = StreamInfo.VIDEO_DEFINITION_LEVEL_UNKNOWN; 154 mAudioChannelCount = StreamInfo.AUDIO_CHANNEL_COUNT_UNKNOWN; 155 mHasClosedCaption = false; 156 String inputId = Utils.getInputIdForChannel(getContext(), channelId); 157 TvInputInfo inputInfo = mInputManagerHelper.getTvInputInfo(inputId); 158 if (inputInfo == null || !mInputManagerHelper.isAvailable(inputInfo)) { 159 return false; 160 } 161 mOnTuneListener = listener; 162 mChannelId = channelId; 163 if (!inputInfo.equals(mInputInfo)) { 164 reset(); 165 // TODO: It is a hack to wait to release a surface at TIS. If there is a way to 166 // know when the surface is released at TIS, we don't need this hack. 167 try { 168 Thread.sleep(DELAY_FOR_SURFACE_RELEASE); 169 } catch (InterruptedException e) { 170 e.printStackTrace(); 171 } 172 mInputInfo = inputInfo; 173 } 174 setTvInputListener(mListener); 175 tune(mInputInfo.getId(), Utils.getChannelUri(mChannelId)); 176 if (mOnTuneListener != null) { 177 // TODO: Add a callback for tune complete and call onTuned when it was successful. 178 mOnTuneListener.onTuned(true, mChannelId); 179 } 180 return true; 181 } 182 183 @Override 184 public TvInputInfo getCurrentTvInputInfo() { 185 return mInputInfo; 186 } 187 188 public long getCurrentChannelId() { 189 return mChannelId; 190 } 191 192 public void setPip(boolean isPip) { 193 mSurface.setZOrderMediaOverlay(isPip); 194 } 195 196 @Override 197 public void setStreamVolume(float volume) { 198 if (!mStarted) { 199 throw new IllegalStateException("TvView isn't started"); 200 } 201 if (DEBUG) 202 Log.d(TAG, "setStreamVolume " + volume); 203 mVolume = volume; 204 super.setStreamVolume(volume); 205 } 206 207 public interface OnTuneListener { 208 void onTuned(boolean success, long channelId); 209 void onUnexpectedStop(long channelId); 210 void onStreamInfoChanged(StreamInfo info); 211 } 212 213 @Override 214 public int getVideoDefinitionLevel() { 215 return mVideoFormat; 216 } 217 218 @Override 219 public int getAudioChannelCount() { 220 return mAudioChannelCount; 221 } 222 223 @Override 224 public boolean hasClosedCaption() { 225 return mHasClosedCaption; 226 } 227} 228