1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.usbtuner.exoplayer;
18
19import android.media.AudioFormat;
20import android.media.MediaCodec.CryptoException;
21import android.media.MediaDataSource;
22import android.os.Handler;
23import android.os.Looper;
24import android.support.annotation.IntDef;
25import android.view.Surface;
26
27import com.google.android.exoplayer.DummyTrackRenderer;
28import com.google.android.exoplayer.ExoPlaybackException;
29import com.google.android.exoplayer.ExoPlayer;
30import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
31import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
32import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
33import com.google.android.exoplayer.TrackRenderer;
34import com.google.android.exoplayer.audio.AudioCapabilities;
35import com.google.android.exoplayer.audio.AudioTrack;
36import com.android.usbtuner.data.Cea708Data;
37import com.android.usbtuner.data.Cea708Data.CaptionEvent;
38import com.android.usbtuner.exoplayer.Cea708TextTrackRenderer.CcListener;
39import com.android.usbtuner.exoplayer.ac3.Ac3TrackRenderer;
40
41import java.lang.annotation.Retention;
42import java.lang.annotation.RetentionPolicy;
43
44/**
45 * MPEG-2 TS stream player implementation using ExoPlayer.
46 */
47public class MpegTsPlayer implements ExoPlayer.Listener,
48        MediaCodecVideoTrackRenderer.EventListener, Ac3TrackRenderer.EventListener {
49    private int mCaptionServiceNumber = Cea708Data.EMPTY_SERVICE_NUMBER;
50
51    /**
52     * Interface definition for building specific track renderers.
53     */
54    public interface RendererBuilder {
55        void buildRenderers(MpegTsPlayer mpegTsPlayer, MediaDataSource dataSource,
56                RendererBuilderCallback callback);
57    }
58
59    /**
60     * Interface definition for {@link RendererBuilder#buildRenderers} to notify the result.
61     */
62    public interface RendererBuilderCallback {
63        void onRenderers(String[][] trackNames, TrackRenderer[] renderers);
64        void onRenderersError(Exception e);
65    }
66
67    /**
68     * Interface definition for a callback to be notified of changes in player state.
69     */
70    public interface Listener {
71        void onStateChanged(int generation, boolean playWhenReady, int playbackState);
72        void onError(int generation, Exception e);
73        void onVideoSizeChanged(int generation, int width, int height,
74                float pixelWidthHeightRatio);
75        void onDrawnToSurface(MpegTsPlayer player, Surface surface);
76        void onAudioUnplayable(int generation);
77    }
78
79    /**
80     * Interface definition for a callback to be notified of changes on video display.
81     */
82    public interface VideoEventListener {
83        /**
84         * Notifies the caption event.
85         */
86        void onEmitCaptionEvent(CaptionEvent event);
87
88        /**
89         * Notifies the discovered caption service number.
90         */
91        void onDiscoverCaptionServiceNumber(int serviceNumber);
92    }
93
94    // Constants pulled into this class for convenience.
95    @IntDef({STATE_IDLE, STATE_PREPARING, STATE_BUFFERING, STATE_READY, STATE_ENDED})
96    @Retention(RetentionPolicy.SOURCE)
97    public @interface PlaybackState {}
98    public static final int STATE_IDLE = ExoPlayer.STATE_IDLE;
99    public static final int STATE_PREPARING = ExoPlayer.STATE_PREPARING;
100    public static final int STATE_BUFFERING = ExoPlayer.STATE_BUFFERING;
101    public static final int STATE_READY = ExoPlayer.STATE_READY;
102    public static final int STATE_ENDED = ExoPlayer.STATE_ENDED;
103
104    public static final int RENDERER_COUNT = 3;
105    public static final int MIN_BUFFER_MS = 200;
106    public static final int MIN_REBUFFER_MS = 500;
107
108    @IntDef({TRACK_TYPE_VIDEO, TRACK_TYPE_AUDIO, TRACK_TYPE_TEXT})
109    @Retention(RetentionPolicy.SOURCE)
110    public @interface TrackType {}
111    public static final int TRACK_TYPE_VIDEO = 0;
112    public static final int TRACK_TYPE_AUDIO = 1;
113    public static final int TRACK_TYPE_TEXT = 2;
114
115    @IntDef({RENDERER_BUILDING_STATE_IDLE, RENDERER_BUILDING_STATE_BUILDING,
116        RENDERER_BUILDING_STATE_BUILT})
117    @Retention(RetentionPolicy.SOURCE)
118    public @interface RendererBuildingState {}
119    private static final int RENDERER_BUILDING_STATE_IDLE = 1;
120    private static final int RENDERER_BUILDING_STATE_BUILDING = 2;
121    private static final int RENDERER_BUILDING_STATE_BUILT = 3;
122
123    private final RendererBuilder mRendererBuilder;
124    private final ExoPlayer mPlayer;
125    private final Handler mMainHandler;
126    private final int mPlayerGeneration;
127    private final AudioCapabilities mAudioCapabilities;
128
129    private Listener mListener;
130    @RendererBuildingState private int mRendererBuildingState;
131    @PlaybackState private int mLastReportedPlaybackState;
132    private boolean mLastReportedPlayWhenReady;
133
134    private Surface mSurface;
135    private InternalRendererBuilderCallback mBuilderCallback;
136    private TrackRenderer mVideoRenderer;
137    private TrackRenderer mAudioRenderer;
138
139    private String[][] mTrackNames;
140    private int[] mSelectedTracks;
141
142    private Cea708TextTrackRenderer mTextRenderer;
143    private CcListener mCcListener;
144    private VideoEventListener mVideoEventListener;
145
146    public MpegTsPlayer(int playerGeneration, RendererBuilder rendererBuilder, Handler handler,
147            AudioCapabilities capabilities, Listener listener) {
148        mRendererBuilder = rendererBuilder;
149        mPlayer = ExoPlayer.Factory.newInstance(RENDERER_COUNT, MIN_BUFFER_MS, MIN_REBUFFER_MS);
150        mPlayer.addListener(this);
151        mMainHandler = handler;
152        mPlayerGeneration = playerGeneration;
153        mAudioCapabilities = capabilities;
154        mLastReportedPlaybackState = STATE_IDLE;
155        mRendererBuildingState = RENDERER_BUILDING_STATE_IDLE;
156        mSelectedTracks = new int[RENDERER_COUNT];
157        mCcListener = new MpegTsCcListener();
158        mListener = listener;
159    }
160
161    public void setVideoEventListener(VideoEventListener videoEventListener) {
162        mVideoEventListener = videoEventListener;
163    }
164
165    public void setCaptionServiceNumber(int captionServiceNumber) {
166        mCaptionServiceNumber = captionServiceNumber;
167        if (mTextRenderer != null) {
168            mPlayer.sendMessage(mTextRenderer,
169                    Cea708TextTrackRenderer.MSG_SERVICE_NUMBER, mCaptionServiceNumber);
170        }
171    }
172
173    public void setSurface(Surface surface) {
174        mSurface = surface;
175        pushSurface(false);
176    }
177
178    public Surface getSurface() {
179        return mSurface;
180    }
181
182    public void blockingClearSurface() {
183        mSurface = null;
184        pushSurface(true);
185    }
186
187    public String[] getTracks(int type) {
188        return mTrackNames == null ? null : mTrackNames[type];
189    }
190
191    public int getSelectedTrackIndex(int type) {
192        return mSelectedTracks[type];
193    }
194
195    public void selectTrack(int type, int index) {
196        if (mSelectedTracks[type] == index) {
197            return;
198        }
199        mSelectedTracks[type] = index;
200        pushTrackSelection(type, true);
201    }
202
203    public void prepare(MediaDataSource source) {
204        if (mRendererBuildingState == RENDERER_BUILDING_STATE_BUILT) {
205            mPlayer.stop();
206        }
207        if (mBuilderCallback != null) {
208            mBuilderCallback.cancel();
209        }
210        mRendererBuildingState = RENDERER_BUILDING_STATE_BUILDING;
211        maybeReportPlayerState();
212        mBuilderCallback = new InternalRendererBuilderCallback();
213        mRendererBuilder.buildRenderers(this, source, mBuilderCallback);
214    }
215
216    /* package */ void onRenderers(String[][] trackNames, TrackRenderer[] renderers) {
217        mBuilderCallback = null;
218
219        // Normalize the results.
220        if (trackNames == null) {
221            trackNames = new String[RENDERER_COUNT][];
222        }
223        for (int i = 0; i < RENDERER_COUNT; i++) {
224            if (renderers[i] == null) {
225                // Convert a null renderer to a dummy renderer.
226                renderers[i] = new DummyTrackRenderer();
227            }
228        }
229        mVideoRenderer = renderers[TRACK_TYPE_VIDEO];
230        mAudioRenderer = renderers[TRACK_TYPE_AUDIO];
231        mTextRenderer = (Cea708TextTrackRenderer) renderers[TRACK_TYPE_TEXT];
232        mTextRenderer.setCcListener(mCcListener);
233        mPlayer.sendMessage(
234                mTextRenderer, Cea708TextTrackRenderer.MSG_SERVICE_NUMBER, mCaptionServiceNumber);
235        mTrackNames = trackNames;
236        mRendererBuildingState = RENDERER_BUILDING_STATE_BUILT;
237        pushSurface(false);
238        mPlayer.prepare(renderers);
239        pushTrackSelection(TRACK_TYPE_VIDEO, true);
240        pushTrackSelection(TRACK_TYPE_AUDIO, true);
241        pushTrackSelection(TRACK_TYPE_TEXT, true);
242    }
243
244    /* package */ void onRenderersError(Exception e) {
245        mBuilderCallback = null;
246        if (mListener != null) {
247            mListener.onError(mPlayerGeneration, e);
248        }
249        mRendererBuildingState = RENDERER_BUILDING_STATE_IDLE;
250        maybeReportPlayerState();
251    }
252
253    public void setPlayWhenReady(boolean playWhenReady) {
254        mPlayer.setPlayWhenReady(playWhenReady);
255    }
256
257    public void seekTo(long positionMs) {
258        mPlayer.seekTo(positionMs);
259    }
260
261    public void release() {
262        if (mBuilderCallback != null) {
263            mBuilderCallback.cancel();
264            mBuilderCallback = null;
265        }
266        mRendererBuildingState = RENDERER_BUILDING_STATE_IDLE;
267        mSurface = null;
268        mListener = null;
269        mPlayer.release();
270    }
271
272    @PlaybackState public int getPlaybackState() {
273        if (mRendererBuildingState == RENDERER_BUILDING_STATE_BUILDING) {
274            return STATE_PREPARING;
275        }
276        return mPlayer.getPlaybackState();
277    }
278
279    public boolean isPlaying() {
280        @PlaybackState int state = getPlaybackState();
281        return (state == STATE_READY || state == STATE_BUFFERING)
282                && mPlayer.getPlayWhenReady();
283    }
284
285    public boolean isBuffering() {
286        return getPlaybackState() == STATE_BUFFERING;
287    }
288
289    public long getCurrentPosition() {
290        return mPlayer.getCurrentPosition();
291    }
292
293    public long getDuration() {
294        return mPlayer.getDuration();
295    }
296
297    public int getBufferedPercentage() {
298        return mPlayer.getBufferedPercentage();
299    }
300
301    public boolean getPlayWhenReady() {
302        return mPlayer.getPlayWhenReady();
303    }
304
305    public void setVolume(float volume) {
306        mPlayer.sendMessage(mAudioRenderer, MediaCodecAudioTrackRenderer.MSG_SET_VOLUME, volume);
307    }
308
309    public void setAudioTrack(boolean enable) {
310        mPlayer.sendMessage(mAudioRenderer, Ac3TrackRenderer.MSG_SET_AUDIO_TRACK, enable ? 1 : 0);
311    }
312
313    public boolean isAc3Playable() {
314        return mAudioCapabilities != null
315                && mAudioCapabilities.supportsEncoding(AudioFormat.ENCODING_AC3);
316    }
317
318    public void onAudioUnplayable() {
319        if (mListener != null) {
320            mListener.onAudioUnplayable(mPlayerGeneration);
321        }
322    }
323
324    /* package */ Looper getPlaybackLooper() {
325        return mPlayer.getPlaybackLooper();
326    }
327
328    /* package */ Handler getMainHandler() {
329        return mMainHandler;
330    }
331
332    @Override
333    public void onPlayerStateChanged(boolean playWhenReady, int state) {
334        maybeReportPlayerState();
335    }
336
337    @Override
338    public void onPlayerError(ExoPlaybackException exception) {
339        mRendererBuildingState = RENDERER_BUILDING_STATE_IDLE;
340        if (mListener != null) {
341            mListener.onError(mPlayerGeneration, exception);
342        }
343    }
344
345    @Override
346    public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
347            float pixelWidthHeightRatio) {
348        if (mListener != null) {
349            mListener.onVideoSizeChanged(mPlayerGeneration, width, height, pixelWidthHeightRatio);
350        }
351    }
352
353    @Override
354    public void onDecoderInitialized(String decoderName, long elapsedRealtimeMs,
355            long initializationDurationMs) {
356        // TODO
357    }
358
359    @Override
360    public void onDecoderInitializationError(DecoderInitializationException e) {
361        // Do nothing.
362    }
363
364    @Override
365    public void onAudioTrackInitializationError(AudioTrack.InitializationException e) {
366        onAudioUnplayable();
367    }
368
369    @Override
370    public void onAudioTrackWriteError(AudioTrack.WriteException e) {
371        // Do nothing.
372    }
373
374    @Override
375    public void onCryptoError(CryptoException e) {
376        // Do nothing.
377    }
378
379    @Override
380    public void onPlayWhenReadyCommitted() {
381        // Do nothing.
382    }
383
384    @Override
385    public void onDrawnToSurface(Surface surface) {
386        if (mListener != null) {
387            mListener.onDrawnToSurface(this, surface);
388        }
389    }
390
391    @Override
392    public void onDroppedFrames(int count, long elapsed) {
393        // Do nothing.
394    }
395
396    private void maybeReportPlayerState() {
397        boolean playWhenReady = mPlayer.getPlayWhenReady();
398        @PlaybackState int playbackState = getPlaybackState();
399        if (mLastReportedPlayWhenReady != playWhenReady
400                || mLastReportedPlaybackState != playbackState) {
401            if (mListener != null) {
402                if (playbackState == STATE_ENDED) {
403                    mListener.onStateChanged(mPlayerGeneration, playWhenReady, STATE_ENDED);
404                }
405                else if (playbackState == STATE_READY) {
406                    mListener.onStateChanged(mPlayerGeneration, playWhenReady, STATE_READY);
407                }
408            }
409            mLastReportedPlayWhenReady = playWhenReady;
410            mLastReportedPlaybackState = playbackState;
411        }
412    }
413
414    private void pushSurface(boolean blockForSurfacePush) {
415        if (mRendererBuildingState != RENDERER_BUILDING_STATE_BUILT) {
416            return;
417        }
418
419        if (blockForSurfacePush) {
420            mPlayer.blockingSendMessage(
421                    mVideoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, mSurface);
422        } else {
423            mPlayer.sendMessage(
424                    mVideoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, mSurface);
425        }
426    }
427
428    private void pushTrackSelection(@TrackType int type, boolean allowRendererEnable) {
429        if (mRendererBuildingState != RENDERER_BUILDING_STATE_BUILT) {
430            return;
431        }
432        mPlayer.setSelectedTrack(type, allowRendererEnable ? 0 : -1);
433    }
434
435    private class MpegTsCcListener implements CcListener {
436
437        @Override
438        public void emitEvent(CaptionEvent captionEvent) {
439            if (mVideoEventListener != null) {
440                mVideoEventListener.onEmitCaptionEvent(captionEvent);
441            }
442        }
443
444        @Override
445        public void discoverServiceNumber(int serviceNumber) {
446            if (mVideoEventListener != null) {
447                mVideoEventListener.onDiscoverCaptionServiceNumber(serviceNumber);
448            }
449        }
450    }
451
452    private class InternalRendererBuilderCallback implements RendererBuilderCallback {
453        private boolean canceled;
454
455        public void cancel() {
456            canceled = true;
457        }
458
459        @Override
460        public void onRenderers(String[][] trackNames, TrackRenderer[] renderers) {
461            if (!canceled) {
462                MpegTsPlayer.this.onRenderers(trackNames, renderers);
463            }
464        }
465
466        @Override
467        public void onRenderersError(Exception e) {
468            if (!canceled) {
469                MpegTsPlayer.this.onRenderersError(e);
470            }
471        }
472    }
473}
474