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