Support DASH Live TTML subtitles.
Also add missing file.
This commit is contained in:
parent
bb024fda08
commit
9d4e177347
@ -41,6 +41,7 @@ import com.google.android.exoplayer.drm.DrmSessionManager;
|
||||
import com.google.android.exoplayer.drm.MediaDrmCallback;
|
||||
import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
|
||||
import com.google.android.exoplayer.text.TextTrackRenderer;
|
||||
import com.google.android.exoplayer.text.ttml.TtmlParser;
|
||||
import com.google.android.exoplayer.text.webvtt.WebvttParser;
|
||||
import com.google.android.exoplayer.upstream.BufferPool;
|
||||
import com.google.android.exoplayer.upstream.DataSource;
|
||||
@ -274,8 +275,8 @@ public class DashRendererBuilder implements RendererBuilder,
|
||||
SampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl,
|
||||
TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player,
|
||||
DemoPlayer.TYPE_TEXT);
|
||||
textRenderer = new TextTrackRenderer(textSampleSource, new WebvttParser(), player,
|
||||
mainHandler.getLooper());
|
||||
textRenderer = new TextTrackRenderer(textSampleSource, player, mainHandler.getLooper(),
|
||||
new TtmlParser(), new WebvttParser());
|
||||
}
|
||||
|
||||
// Invoke the callback.
|
||||
|
@ -233,8 +233,8 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
|
||||
ChunkSampleSource ttmlSampleSource = new ChunkSampleSource(textChunkSource, loadControl,
|
||||
TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player,
|
||||
DemoPlayer.TYPE_TEXT);
|
||||
textRenderer = new TextTrackRenderer(ttmlSampleSource, new TtmlParser(), player,
|
||||
mainHandler.getLooper());
|
||||
textRenderer = new TextTrackRenderer(ttmlSampleSource, player, mainHandler.getLooper(),
|
||||
new TtmlParser());
|
||||
}
|
||||
|
||||
// Invoke the callback.
|
||||
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.demo.full.player;
|
||||
|
||||
/**
|
||||
* Exception thrown when the required level of DRM is not supported.
|
||||
*/
|
||||
public final class UnsupportedDrmException extends Exception {
|
||||
|
||||
public static final int REASON_NO_DRM = 0;
|
||||
public static final int REASON_UNSUPPORTED_SCHEME = 1;
|
||||
public static final int REASON_UNKNOWN = 2;
|
||||
|
||||
public final int reason;
|
||||
|
||||
public UnsupportedDrmException(int reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public UnsupportedDrmException(int reason, Exception cause) {
|
||||
super(cause);
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
}
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.google.android.exoplayer;
|
||||
|
||||
import com.google.android.exoplayer.util.MimeTypes;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
@ -86,6 +87,15 @@ public class MediaFormat {
|
||||
sampleRate, bitrate, initializationData);
|
||||
}
|
||||
|
||||
public static MediaFormat createTtmlFormat() {
|
||||
return createFormatForMimeType(MimeTypes.APPLICATION_TTML);
|
||||
}
|
||||
|
||||
public static MediaFormat createFormatForMimeType(String mimeType) {
|
||||
return new MediaFormat(mimeType, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||
NO_VALUE, null);
|
||||
}
|
||||
|
||||
@TargetApi(16)
|
||||
private MediaFormat(android.media.MediaFormat format) {
|
||||
this.frameworkMediaFormat = format;
|
||||
|
@ -272,16 +272,23 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback {
|
||||
downstreamPositionUs = positionUs;
|
||||
chunkSource.continueBuffering(positionUs);
|
||||
updateLoadControl();
|
||||
|
||||
boolean haveSamples = false;
|
||||
if (isPendingReset() || mediaChunks.isEmpty()) {
|
||||
return false;
|
||||
// No sample available.
|
||||
} else if (mediaChunks.getFirst().sampleAvailable()) {
|
||||
// There's a sample available to be read from the current chunk.
|
||||
return true;
|
||||
haveSamples = true;
|
||||
} else {
|
||||
// It may be the case that the current chunk has been fully read but not yet discarded and
|
||||
// that the next chunk has an available sample. Return true if so, otherwise false.
|
||||
return mediaChunks.size() > 1 && mediaChunks.get(1).sampleAvailable();
|
||||
haveSamples = mediaChunks.size() > 1 && mediaChunks.get(1).sampleAvailable();
|
||||
}
|
||||
|
||||
if (!haveSamples) {
|
||||
maybeThrowLoadableException();
|
||||
}
|
||||
return haveSamples;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -380,7 +387,8 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback {
|
||||
}
|
||||
|
||||
private void maybeThrowLoadableException() throws IOException {
|
||||
if (currentLoadableException != null && currentLoadableExceptionCount > minLoadableRetryCount) {
|
||||
if (currentLoadableException != null && (currentLoadableExceptionFatal
|
||||
|| currentLoadableExceptionCount > minLoadableRetryCount)) {
|
||||
throw currentLoadableException;
|
||||
}
|
||||
}
|
||||
|
@ -58,6 +58,7 @@ import java.util.ArrayList;
|
||||
public static final int TYPE_uuid = 0x75756964;
|
||||
public static final int TYPE_senc = 0x73656E63;
|
||||
public static final int TYPE_pasp = 0x70617370;
|
||||
public static final int TYPE_TTML = 0x54544D4C;
|
||||
|
||||
public final int type;
|
||||
|
||||
|
@ -428,7 +428,8 @@ public final class FragmentedMp4Extractor implements Extractor {
|
||||
private static Track parseTrak(ContainerAtom trak) {
|
||||
ContainerAtom mdia = trak.getContainerAtomOfType(Atom.TYPE_mdia);
|
||||
int trackType = parseHdlr(mdia.getLeafAtomOfType(Atom.TYPE_hdlr).data);
|
||||
Assertions.checkState(trackType == Track.TYPE_AUDIO || trackType == Track.TYPE_VIDEO);
|
||||
Assertions.checkState(trackType == Track.TYPE_AUDIO || trackType == Track.TYPE_VIDEO
|
||||
|| trackType == Track.TYPE_TEXT);
|
||||
|
||||
Pair<Integer, Long> header = parseTkhd(trak.getLeafAtomOfType(Atom.TYPE_tkhd).data);
|
||||
int id = header.first;
|
||||
@ -528,6 +529,8 @@ public final class FragmentedMp4Extractor implements Extractor {
|
||||
parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize);
|
||||
mediaFormat = audioSampleEntry.first;
|
||||
trackEncryptionBoxes[i] = audioSampleEntry.second;
|
||||
} else if (childAtomType == Atom.TYPE_TTML) {
|
||||
mediaFormat = MediaFormat.createTtmlFormat();
|
||||
}
|
||||
stsd.setPosition(childStartPosition + childAtomSize);
|
||||
}
|
||||
|
@ -30,6 +30,10 @@ public final class Track {
|
||||
* Type of an audio track.
|
||||
*/
|
||||
public static final int TYPE_AUDIO = 0x736F756E;
|
||||
/**
|
||||
* Type of a text track.
|
||||
*/
|
||||
public static final int TYPE_TEXT = 0x74657874;
|
||||
/**
|
||||
* Type of a hint track.
|
||||
*/
|
||||
|
@ -358,8 +358,9 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
||||
MediaFormat format = MediaFormat.createAudioFormat(mimeType, -1, trackElement.numChannels,
|
||||
trackElement.sampleRate, csd);
|
||||
return format;
|
||||
} else if (streamElement.type == StreamElement.TYPE_TEXT) {
|
||||
return MediaFormat.createFormatForMimeType(streamElement.tracks[trackIndex].mimeType);
|
||||
}
|
||||
// TODO: Do subtitles need a format? MediaFormat supports KEY_LANGUAGE.
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -58,8 +58,9 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
|
||||
private final TextRenderer textRenderer;
|
||||
private final SampleSource source;
|
||||
private final MediaFormatHolder formatHolder;
|
||||
private final SubtitleParser subtitleParser;
|
||||
private final SubtitleParser[] subtitleParsers;
|
||||
|
||||
private int parserIndex;
|
||||
private int trackIndex;
|
||||
|
||||
private long currentPositionUs;
|
||||
@ -73,21 +74,22 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
|
||||
|
||||
/**
|
||||
* @param source A source from which samples containing subtitle data can be read.
|
||||
* @param subtitleParser A subtitle parser that will parse Subtitle objects from the source.
|
||||
* @param textRenderer The text renderer.
|
||||
* @param textRendererLooper The looper associated with the thread on which textRenderer should be
|
||||
* invoked. If the renderer makes use of standard Android UI components, then this should
|
||||
* normally be the looper associated with the applications' main thread, which can be
|
||||
* obtained using {@link android.app.Activity#getMainLooper()}. Null may be passed if the
|
||||
* renderer should be invoked directly on the player's internal rendering thread.
|
||||
* @param subtitleParsers An array of available subtitle parsers. Where multiple parsers are able
|
||||
* to render a subtitle, the one with the lowest index will be preferred.
|
||||
*/
|
||||
public TextTrackRenderer(SampleSource source, SubtitleParser subtitleParser,
|
||||
TextRenderer textRenderer, Looper textRendererLooper) {
|
||||
public TextTrackRenderer(SampleSource source, TextRenderer textRenderer,
|
||||
Looper textRendererLooper, SubtitleParser... subtitleParsers) {
|
||||
this.source = Assertions.checkNotNull(source);
|
||||
this.subtitleParser = Assertions.checkNotNull(subtitleParser);
|
||||
this.textRenderer = Assertions.checkNotNull(textRenderer);
|
||||
this.textRendererHandler = textRendererLooper == null ? null : new Handler(textRendererLooper,
|
||||
this);
|
||||
this.textRendererHandler = textRendererLooper == null ? null
|
||||
: new Handler(textRendererLooper, this);
|
||||
this.subtitleParsers = Assertions.checkNotNull(subtitleParsers);
|
||||
formatHolder = new MediaFormatHolder();
|
||||
}
|
||||
|
||||
@ -101,12 +103,15 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
|
||||
} catch (IOException e) {
|
||||
throw new ExoPlaybackException(e);
|
||||
}
|
||||
for (int i = 0; i < source.getTrackCount(); i++) {
|
||||
if (subtitleParser.canParse(source.getTrackInfo(i).mimeType)) {
|
||||
trackIndex = i;
|
||||
for (int i = 0; i < subtitleParsers.length; i++) {
|
||||
for (int j = 0; j < source.getTrackCount(); j++) {
|
||||
if (subtitleParsers[i].canParse(source.getTrackInfo(j).mimeType)) {
|
||||
parserIndex = i;
|
||||
trackIndex = j;
|
||||
return TrackRenderer.STATE_PREPARED;
|
||||
}
|
||||
}
|
||||
}
|
||||
return TrackRenderer.STATE_IGNORE;
|
||||
}
|
||||
|
||||
@ -115,7 +120,7 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
|
||||
source.enable(trackIndex, positionUs);
|
||||
parserThread = new HandlerThread("textParser");
|
||||
parserThread.start();
|
||||
parserHelper = new SubtitleParserHelper(parserThread.getLooper(), subtitleParser);
|
||||
parserHelper = new SubtitleParserHelper(parserThread.getLooper(), subtitleParsers[parserIndex]);
|
||||
seekToInternal(positionUs);
|
||||
}
|
||||
|
||||
@ -189,6 +194,7 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
|
||||
int result = source.readData(trackIndex, positionUs, formatHolder, sampleHolder, false);
|
||||
if (result == SampleSource.SAMPLE_READ) {
|
||||
parserHelper.startParseOperation();
|
||||
textRendererNeedsUpdate = false;
|
||||
} else if (result == SampleSource.END_OF_STREAM) {
|
||||
inputStreamEnded = true;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user