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 package com.android.tv.tuner.exoplayer;
17 
18 import com.google.android.exoplayer.C;
19 import com.google.android.exoplayer.MediaFormat;
20 import com.google.android.exoplayer.MediaFormatHolder;
21 import com.google.android.exoplayer.SampleHolder;
22 import com.google.android.exoplayer.SampleSource;
23 import com.google.android.exoplayer.SampleSource.SampleSourceReader;
24 import com.google.android.exoplayer.util.Assertions;
25 import java.io.IOException;
26 import java.util.ArrayList;
27 import java.util.List;
28 
29 /** {@link SampleSource} that extracts sample data using a {@link SampleExtractor}. */
30 public final class MpegTsSampleSource implements SampleSource, SampleSourceReader {
31 
32     private static final int TRACK_STATE_DISABLED = 0;
33     private static final int TRACK_STATE_ENABLED = 1;
34     private static final int TRACK_STATE_FORMAT_SENT = 2;
35 
36     private final SampleExtractor mSampleExtractor;
37     private final List<Integer> mTrackStates = new ArrayList<>();
38     private final List<Boolean> mPendingDiscontinuities = new ArrayList<>();
39 
40     private boolean mPrepared;
41     private IOException mPreparationError;
42     private int mRemainingReleaseCount;
43 
44     private long mLastSeekPositionUs;
45     private long mPendingSeekPositionUs;
46 
47     /**
48      * Creates a new sample source that extracts samples using {@code mSampleExtractor}.
49      *
50      * @param sampleExtractor a sample extractor for accessing media samples
51      */
MpegTsSampleSource(SampleExtractor sampleExtractor)52     public MpegTsSampleSource(SampleExtractor sampleExtractor) {
53         mSampleExtractor = Assertions.checkNotNull(sampleExtractor);
54     }
55 
56     @Override
register()57     public SampleSourceReader register() {
58         mRemainingReleaseCount++;
59         return this;
60     }
61 
62     @Override
prepare(long positionUs)63     public boolean prepare(long positionUs) {
64         if (!mPrepared) {
65             if (mPreparationError != null) {
66                 return false;
67             }
68             try {
69                 if (mSampleExtractor.prepare()) {
70                     int trackCount = mSampleExtractor.getTrackFormats().size();
71                     mTrackStates.clear();
72                     mPendingDiscontinuities.clear();
73                     for (int i = 0; i < trackCount; ++i) {
74                         mTrackStates.add(i, TRACK_STATE_DISABLED);
75                         mPendingDiscontinuities.add(i, false);
76                     }
77                     mPrepared = true;
78                 } else {
79                     return false;
80                 }
81             } catch (IOException e) {
82                 mPreparationError = e;
83                 return false;
84             }
85         }
86         return true;
87     }
88 
89     @Override
getTrackCount()90     public int getTrackCount() {
91         Assertions.checkState(mPrepared);
92         return mSampleExtractor.getTrackFormats().size();
93     }
94 
95     @Override
getFormat(int track)96     public MediaFormat getFormat(int track) {
97         Assertions.checkState(mPrepared);
98         return mSampleExtractor.getTrackFormats().get(track);
99     }
100 
101     @Override
enable(int track, long positionUs)102     public void enable(int track, long positionUs) {
103         Assertions.checkState(mPrepared);
104         Assertions.checkState(mTrackStates.get(track) == TRACK_STATE_DISABLED);
105         mTrackStates.set(track, TRACK_STATE_ENABLED);
106         mSampleExtractor.selectTrack(track);
107         seekToUsInternal(positionUs, positionUs != 0);
108     }
109 
110     @Override
disable(int track)111     public void disable(int track) {
112         Assertions.checkState(mPrepared);
113         Assertions.checkState(mTrackStates.get(track) != TRACK_STATE_DISABLED);
114         mSampleExtractor.deselectTrack(track);
115         mPendingDiscontinuities.set(track, false);
116         mTrackStates.set(track, TRACK_STATE_DISABLED);
117     }
118 
119     @Override
continueBuffering(int track, long positionUs)120     public boolean continueBuffering(int track, long positionUs) {
121         return mSampleExtractor.continueBuffering(positionUs);
122     }
123 
124     @Override
readDiscontinuity(int track)125     public long readDiscontinuity(int track) {
126         if (mPendingDiscontinuities.get(track)) {
127             mPendingDiscontinuities.set(track, false);
128             return mLastSeekPositionUs;
129         }
130         return NO_DISCONTINUITY;
131     }
132 
133     @Override
readData( int track, long positionUs, MediaFormatHolder formatHolder, SampleHolder sampleHolder)134     public int readData(
135             int track, long positionUs, MediaFormatHolder formatHolder, SampleHolder sampleHolder) {
136         Assertions.checkState(mPrepared);
137         Assertions.checkState(mTrackStates.get(track) != TRACK_STATE_DISABLED);
138         if (mPendingDiscontinuities.get(track)) {
139             return NOTHING_READ;
140         }
141         if (mTrackStates.get(track) != TRACK_STATE_FORMAT_SENT) {
142             mSampleExtractor.getTrackMediaFormat(track, formatHolder);
143             mTrackStates.set(track, TRACK_STATE_FORMAT_SENT);
144             return FORMAT_READ;
145         }
146 
147         mPendingSeekPositionUs = C.UNKNOWN_TIME_US;
148         return mSampleExtractor.readSample(track, sampleHolder);
149     }
150 
151     @Override
maybeThrowError()152     public void maybeThrowError() throws IOException {
153         if (mPreparationError != null) {
154             throw mPreparationError;
155         }
156         if (mSampleExtractor != null) {
157             mSampleExtractor.maybeThrowError();
158         }
159     }
160 
161     @Override
seekToUs(long positionUs)162     public void seekToUs(long positionUs) {
163         Assertions.checkState(mPrepared);
164         seekToUsInternal(positionUs, false);
165     }
166 
167     @Override
getBufferedPositionUs()168     public long getBufferedPositionUs() {
169         Assertions.checkState(mPrepared);
170         return mSampleExtractor.getBufferedPositionUs();
171     }
172 
173     @Override
release()174     public void release() {
175         Assertions.checkState(mRemainingReleaseCount > 0);
176         if (--mRemainingReleaseCount == 0) {
177             mSampleExtractor.release();
178         }
179     }
180 
seekToUsInternal(long positionUs, boolean force)181     private void seekToUsInternal(long positionUs, boolean force) {
182         // Unless forced, avoid duplicate calls to the underlying extractor's seek method
183         // in the case that there have been no interleaving calls to readSample.
184         if (force || mPendingSeekPositionUs != positionUs) {
185             mLastSeekPositionUs = positionUs;
186             mPendingSeekPositionUs = positionUs;
187             mSampleExtractor.seekTo(positionUs);
188             for (int i = 0; i < mTrackStates.size(); ++i) {
189                 if (mTrackStates.get(i) != TRACK_STATE_DISABLED) {
190                     mPendingDiscontinuities.set(i, true);
191                 }
192             }
193         }
194     }
195 }
196