Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update dev-l #63

Merged
merged 4 commits into from Oct 1, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -45,6 +45,8 @@ public interface ChunkSource {
* the supplied {@link MediaFormat}. Other implementations do nothing.
* <p>
* Only called when the source is enabled.
*
* @param out The {@link MediaFormat} on which the maximum video dimensions should be set.
*/
void getMaxVideoDimensions(MediaFormat out);

Expand Down
@@ -0,0 +1,108 @@
/*
* 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.chunk;

import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.TrackInfo;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSpec;

import java.io.IOException;
import java.util.List;

/**
* A chunk source that provides a single chunk containing a single sample.
* <p>
* An example use case for this implementation is to act as the source for loading out-of-band
* subtitles, where subtitles for the entire video are delivered as a single file.
*/
public class SingleSampleChunkSource implements ChunkSource {

private final DataSource dataSource;
private final DataSpec dataSpec;
private final Format format;
private final long durationUs;
private final MediaFormat mediaFormat;
private final TrackInfo trackInfo;

/**
* @param dataSource A {@link DataSource} suitable for loading the sample data.
* @param dataSpec Defines the location of the sample.
* @param format The format of the sample.
* @param durationUs The duration of the sample in microseconds.
* @param mediaFormat The sample media format. May be null.
*/
public SingleSampleChunkSource(DataSource dataSource, DataSpec dataSpec, Format format,
long durationUs, MediaFormat mediaFormat) {
this.dataSource = dataSource;
this.dataSpec = dataSpec;
this.format = format;
this.durationUs = durationUs;
this.mediaFormat = mediaFormat;
trackInfo = new TrackInfo(format.mimeType, durationUs);
}

@Override
public TrackInfo getTrackInfo() {
return trackInfo;
}

@Override
public void getMaxVideoDimensions(MediaFormat out) {
// Do nothing.
}

@Override
public void enable() {
// Do nothing.
}

@Override
public void continueBuffering(long playbackPositionUs) {
// Do nothing.
}

@Override
public void getChunkOperation(List<? extends MediaChunk> queue, long seekPositionUs,
long playbackPositionUs, ChunkOperationHolder out) {
if (!queue.isEmpty()) {
// We've already provided the single sample.
return;
}
out.chunk = initChunk();
}

@Override
public void disable(List<? extends MediaChunk> queue) {
// Do nothing.
}

@Override
public IOException getError() {
return null;
}

@Override
public void onChunkLoadError(Chunk chunk, Exception e) {
// Do nothing.
}

private SingleSampleMediaChunk initChunk() {
return new SingleSampleMediaChunk(dataSource, dataSpec, format, 0, 0, durationUs, -1,
mediaFormat);
}

}
Expand Up @@ -20,20 +20,18 @@
import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.dash.mpd.AdaptationSet;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.VerboseLogUtil;

import android.annotation.TargetApi;
import android.os.Handler;
import android.os.Handler.Callback;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
* A {@link TrackRenderer} for textual subtitles. The actual rendering of each line of text to a
Expand Down Expand Up @@ -63,7 +61,6 @@ public interface TextRenderer {
private final Handler textRendererHandler;
private final TextRenderer textRenderer;
private final SampleSource source;
private final SampleHolder sampleHolder;
private final MediaFormatHolder formatHolder;
private final SubtitleParser subtitleParser;

Expand All @@ -73,6 +70,8 @@ public interface TextRenderer {
private boolean inputStreamEnded;

private Subtitle subtitle;
private SubtitleParserHelper parserHelper;
private HandlerThread parserThread;
private int nextSubtitleEventIndex;
private boolean textRendererNeedsUpdate;

Expand All @@ -94,7 +93,6 @@ public TextTrackRenderer(SampleSource source, SubtitleParser subtitleParser,
this.textRendererHandler = textRendererLooper == null ? null : new Handler(textRendererLooper,
this);
formatHolder = new MediaFormatHolder();
sampleHolder = new SampleHolder(true);
}

@Override
Expand All @@ -119,6 +117,9 @@ protected int doPrepare() throws ExoPlaybackException {
@Override
protected void onEnabled(long timeUs, boolean joining) {
source.enable(trackIndex, timeUs);
parserThread = new HandlerThread("textParser");
parserThread.start();
parserHelper = new SubtitleParserHelper(parserThread.getLooper(), subtitleParser);
seekToInternal(timeUs);
}

Expand All @@ -136,7 +137,7 @@ private void seekToInternal(long timeUs) {
|| subtitle.getLastEventTime() <= timeUs)) {
subtitle = null;
}
resetSampleData();
parserHelper.flush();
clearTextRenderer();
syncNextEventIndex(timeUs);
textRendererNeedsUpdate = subtitle != null;
Expand All @@ -152,9 +153,27 @@ protected void doSomeWork(long timeUs) throws ExoPlaybackException {

currentPositionUs = timeUs;

// We're iterating through the events in a subtitle. Set textRendererNeedsUpdate if we advance
// to the next event.
if (subtitle != null) {
if (parserHelper.isParsing()) {
return;
}

Subtitle dequeuedSubtitle = null;
if (subtitle == null) {
try {
dequeuedSubtitle = parserHelper.getAndClearResult();
} catch (IOException e) {
throw new ExoPlaybackException(e);
}
}

if (subtitle == null && dequeuedSubtitle != null) {
// We've dequeued a new subtitle. Sync the event index and update the subtitle.
subtitle = dequeuedSubtitle;
syncNextEventIndex(timeUs);
textRendererNeedsUpdate = true;
} else if (subtitle != null) {
// We're iterating through the events in a subtitle. Set textRendererNeedsUpdate if we
// advance to the next event.
long nextEventTimeUs = getNextEventTime();
while (nextEventTimeUs <= timeUs) {
nextSubtitleEventIndex++;
Expand All @@ -170,26 +189,16 @@ protected void doSomeWork(long timeUs) throws ExoPlaybackException {
// We don't have a subtitle. Try and read the next one from the source, and if we succeed then
// sync and set textRendererNeedsUpdate.
if (subtitle == null) {
boolean resetSampleHolder = false;
try {
SampleHolder sampleHolder = parserHelper.getSampleHolder();
int result = source.readData(trackIndex, timeUs, formatHolder, sampleHolder, false);
if (result == SampleSource.SAMPLE_READ) {
resetSampleHolder = true;
InputStream subtitleInputStream =
new ByteArrayInputStream(sampleHolder.data.array(), 0, sampleHolder.size);
subtitle = subtitleParser.parse(subtitleInputStream, null, sampleHolder.timeUs);
syncNextEventIndex(timeUs);
textRendererNeedsUpdate = true;
parserHelper.startParseOperation();
} else if (result == SampleSource.END_OF_STREAM) {
inputStreamEnded = true;
}
} catch (IOException e) {
resetSampleHolder = true;
throw new ExoPlaybackException(e);
} finally {
if (resetSampleHolder) {
resetSampleData();
}
}
}

Expand All @@ -208,7 +217,9 @@ protected void doSomeWork(long timeUs) throws ExoPlaybackException {
protected void onDisabled() {
source.disable(trackIndex);
subtitle = null;
resetSampleData();
parserThread.quit();
parserThread = null;
parserHelper = null;
clearTextRenderer();
}

Expand Down Expand Up @@ -255,12 +266,6 @@ private long getNextEventTime() {
: (subtitle.getEventTime(nextSubtitleEventIndex));
}

private void resetSampleData() {
if (sampleHolder.data != null) {
sampleHolder.data.position(0);
}
}

private void updateTextRenderer(long timeUs) {
String text = subtitle.getText(timeUs);
log("updateTextRenderer; text=: " + text);
Expand Down Expand Up @@ -296,7 +301,7 @@ private void invokeTextRenderer(String text) {

private void log(String logMessage) {
if (VerboseLogUtil.isTagEnabled(TAG)) {
Log.v(TAG, "type=" + AdaptationSet.TYPE_TEXT + ", " + logMessage);
Log.v(TAG, logMessage);
}
}

Expand Down
Expand Up @@ -79,15 +79,21 @@ public long getLastEventTime() {

@Override
public String getText(long timeUs) {
StringBuilder subtitleStringBuilder = new StringBuilder();
StringBuilder stringBuilder = new StringBuilder();

for (int i = 0; i < cueTimesUs.length; i += 2) {
if ((cueTimesUs[i] <= timeUs) && (timeUs < cueTimesUs[i + 1])) {
subtitleStringBuilder.append(cueText[i / 2]);
stringBuilder.append(cueText[i / 2]);
}
}

return (subtitleStringBuilder.length() > 0) ? subtitleStringBuilder.toString() : null;
int stringLength = stringBuilder.length();
if (stringLength > 0 && stringBuilder.charAt(stringLength - 1) == '\n') {
// Adjust the length to remove the trailing newline character.
stringLength -= 1;
}

return stringLength == 0 ? null : stringBuilder.substring(0, stringLength);
}

}
Expand Up @@ -53,6 +53,15 @@ public final class DataSpec {
*/
public final String key;

/**
* Construct a {@link DataSpec} for the given uri and with {@link #key} set to null.
*
* @param uri {@link #uri}.
*/
public DataSpec(Uri uri) {
this(uri, 0, C.LENGTH_UNBOUNDED, null);
}

/**
* Construct a {@link DataSpec} for which {@link #uriIsFullStream} is true.
*
Expand Down