1 /*
2  * Copyright (C) 2019 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 
17 package com.android.tv.tuner.exoplayer2;
18 
19 import com.google.android.exoplayer2.C;
20 import com.google.android.exoplayer2.FormatHolder;
21 import com.google.android.exoplayer2.SeekParameters;
22 import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
23 import com.google.android.exoplayer2.source.MediaPeriod;
24 import com.google.android.exoplayer2.source.SampleStream;
25 import com.google.android.exoplayer2.source.TrackGroupArray;
26 import com.google.android.exoplayer2.trackselection.TrackSelection;
27 import com.google.android.exoplayer2.util.Assertions;
28 
29 import java.io.IOException;
30 import java.util.ArrayList;
31 import java.util.List;
32 
33 /** A {@link MediaPeriod} that extracts data using an {@link SampleExtractor}. */
34 /* package */ final class MpegTsMediaPeriod implements MediaPeriod, SampleExtractor.Callback {
35 
36     private static final int TRACK_STATE_DISABLED = 0;
37     private static final int TRACK_STATE_ENABLED = 1;
38     private static final int TRACK_STATE_FORMAT_SENT = 2;
39 
40     private final SampleExtractor mExtractor;
41     private final ArrayList<SampleStreamImpl> mSampleStreams = new ArrayList<>();
42     private final List<Integer> mTrackStates = new ArrayList<>();
43     private final List<Boolean> mPendingDiscontinuities = new ArrayList<>();
44 
45     private boolean mPrepared;
46     private long mLastSeekPositionUs;
47     private long mPendingSeekPositionUs;
48     private Callback mCallback;
49     private IOException mExceptionOnPrepare;
50 
MpegTsMediaPeriod(SampleExtractor extractor)51     public MpegTsMediaPeriod(SampleExtractor extractor) {
52         this.mExtractor = extractor;
53     }
54 
55     @Override
prepare(Callback callback, long positionUs)56     public void prepare(Callback callback, long positionUs) {
57         mCallback = callback;
58         try {
59             mExtractor.prepare(this);
60         } catch (IOException e) {
61             mExceptionOnPrepare = e;
62         }
63     }
64 
enable(int track)65     private void enable(int track) {
66         Assertions.checkState(mPrepared);
67         Assertions.checkState(mTrackStates.get(track) == TRACK_STATE_DISABLED);
68         mTrackStates.set(track, TRACK_STATE_ENABLED);
69         mExtractor.selectTrack(track);
70     }
71 
disable(int track)72     private void disable(int track) {
73         Assertions.checkState(mPrepared);
74         Assertions.checkState(mTrackStates.get(track) != TRACK_STATE_DISABLED);
75         mExtractor.deselectTrack(track);
76         mPendingDiscontinuities.set(track, false);
77         mTrackStates.set(track, TRACK_STATE_DISABLED);
78     }
79 
80     @Override
maybeThrowPrepareError()81     public void maybeThrowPrepareError() throws IOException {
82         if (mExceptionOnPrepare != null) {
83             IOException e = mExceptionOnPrepare;
84             mExceptionOnPrepare = null;
85             throw e;
86         }
87     }
88 
89     @Override
getTrackGroups()90     public TrackGroupArray getTrackGroups() {
91         return mExtractor.getTrackGroups();
92     }
93 
94     @Override
selectTracks( TrackSelection[] selections, boolean[] mayRetainStreamFlags, SampleStream[] streams, boolean[] streamResetFlags, long positionUs)95     public long selectTracks(
96             TrackSelection[] selections,
97             boolean[] mayRetainStreamFlags,
98             SampleStream[] streams,
99             boolean[] streamResetFlags,
100             long positionUs) {
101         TrackGroupArray trackGroups = mExtractor.getTrackGroups();
102         for (int i = 0; i < selections.length; i++) {
103             if (streams[i] != null && (selections[i] == null || !mayRetainStreamFlags[i])) {
104                 SampleStreamImpl stream = (SampleStreamImpl) streams[i];
105                 disable(stream.mIndex);
106                 mSampleStreams.remove(stream);
107                 streams[i] = null;
108             }
109             if (streams[i] == null && selections[i] != null) {
110                 int index = trackGroups.indexOf(selections[i].getTrackGroup());
111                 SampleStreamImpl stream = new SampleStreamImpl(index);
112                 mSampleStreams.add(stream);
113                 streams[i] = stream;
114                 streamResetFlags[i] = true;
115                 enable(index);
116             }
117         }
118         seekToUsInternal(positionUs, positionUs != 0);
119         return positionUs;
120     }
121 
122     @Override
discardBuffer(long positionUs, boolean toKeyframe)123     public void discardBuffer(long positionUs, boolean toKeyframe) {
124         // Handled by extractor
125     }
126 
127     @Override
reevaluateBuffer(long positionUs)128     public void reevaluateBuffer(long positionUs) {
129         // Do nothing.
130     }
131 
132     @Override
continueLoading(long positionUs)133     public boolean continueLoading(long positionUs) {
134         return mExtractor.continueLoading(positionUs);
135     }
136 
137     @Override
readDiscontinuity()138     public long readDiscontinuity() {
139         boolean notifyDiscontinuity = false;
140         for (int i = 0; i < mPendingDiscontinuities.size(); i++) {
141             if (mPendingDiscontinuities.get(i)) {
142                 mPendingDiscontinuities.set(i, false);
143                 notifyDiscontinuity = true;
144             }
145         }
146         return (notifyDiscontinuity ? mLastSeekPositionUs : C.TIME_UNSET);
147     }
148 
149     @Override
getNextLoadPositionUs()150     public long getNextLoadPositionUs() {
151         return mExtractor.getNextLoadPositionUs();
152     }
153 
154     @Override
getBufferedPositionUs()155     public long getBufferedPositionUs() {
156         return mExtractor.getBufferedPositionUs();
157     }
158 
159     @Override
seekToUs(long positionUs)160     public long seekToUs(long positionUs) {
161         for (int i = 0; i < mSampleStreams.size(); i++) {
162             mSampleStreams.get(i).reset();
163         }
164         seekToUsInternal(positionUs, false);
165         return positionUs;
166     }
167 
seekToUsInternal(long positionUs, boolean force)168     private void seekToUsInternal(long positionUs, boolean force) {
169         // Unless forced, avoid duplicate calls to the underlying extractor's seek method
170         // in the case that there have been no interleaving calls to readSample.
171         if (force || mPendingSeekPositionUs != positionUs) {
172             mLastSeekPositionUs = positionUs;
173             mPendingSeekPositionUs = positionUs;
174             mExtractor.seekTo(positionUs);
175             for (int i = 0; i < mTrackStates.size(); ++i) {
176                 if (mTrackStates.get(i) != TRACK_STATE_DISABLED) {
177                     mPendingDiscontinuities.set(i, true);
178                 }
179             }
180         }
181     }
182 
183     @Override
getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters)184     public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) {
185         return positionUs;
186     }
187 
188     @Override
onPrepared()189     public void onPrepared() {
190         mPrepared = true;
191         int trackCount = mExtractor.getTrackGroups().length;
192         mTrackStates.clear();
193         mPendingDiscontinuities.clear();
194         for (int i = 0; i < trackCount; ++i) {
195             mTrackStates.add(i, TRACK_STATE_DISABLED);
196             mPendingDiscontinuities.add(i, false);
197         }
198         mCallback.onPrepared(this);
199     }
200 
release()201     public void release() {
202         mExtractor.release();
203     }
204 
205     private final class SampleStreamImpl implements SampleStream {
206 
207         private static final int STREAM_STATE_SEND_FORMAT = 0;
208         private static final int STREAM_STATE_SEND_SAMPLE = 1;
209         private static final int STREAM_STATE_END_OF_STREAM = 2;
210         private final int mIndex;
211 
212         private int streamState;
213 
SampleStreamImpl(int index)214         SampleStreamImpl(int index) {
215             mIndex = index;
216         }
217 
reset()218         void reset() {
219             if (streamState == STREAM_STATE_END_OF_STREAM) {
220                 streamState = STREAM_STATE_SEND_SAMPLE;
221             }
222         }
223 
224         @Override
isReady()225         public boolean isReady() {
226             return true;
227         }
228 
229         @Override
maybeThrowError()230         public void maybeThrowError() throws IOException {
231             mExtractor.maybeThrowError();
232         }
233 
234         @Override
readData( FormatHolder formatHolder, DecoderInputBuffer buffer, boolean requireFormat)235         public int readData(
236                 FormatHolder formatHolder, DecoderInputBuffer buffer, boolean requireFormat) {
237             Assertions.checkState(mPrepared);
238             Assertions.checkState(mTrackStates.get(mIndex) != TRACK_STATE_DISABLED);
239             if (mPendingDiscontinuities.get(mIndex)) {
240                 return C.RESULT_NOTHING_READ;
241             }
242             if (requireFormat || mTrackStates.get(mIndex) != TRACK_STATE_FORMAT_SENT) {
243                 mExtractor.getTrackMediaFormat(mIndex, formatHolder);
244                 mTrackStates.set(mIndex, TRACK_STATE_FORMAT_SENT);
245                 return C.RESULT_FORMAT_READ;
246             }
247             mPendingSeekPositionUs = C.TIME_UNSET;
248             return mExtractor.readSample(mIndex, buffer);
249         }
250 
251         @Override
skipData(long positionUs)252         public int skipData(long positionUs) {
253             if (positionUs > 0 && streamState != STREAM_STATE_END_OF_STREAM) {
254                 streamState = STREAM_STATE_END_OF_STREAM;
255                 return 1;
256             }
257             return 0;
258         }
259     }
260 }
261