Commit 1d315217 by Rik Heijdens

Merge branch 'dev' of github.com:/google/ExoPlayer into mediaformat-id-dash

Syncing my fork
parents 1cefde0d 0545c58d
Showing with 945 additions and 215 deletions
# Release notes #
### Current dev branch (from r1.5.0) ###
### Current dev branch (from r1.5.1) ###
* [Nothing yet]
### r1.5.0 ###
* Enable smooth frame release by default.
* Added OkHttpDataSource extension.
* AndroidTV: Correctly detect 4K display size on Bravia devices.
* FMP4: Handle non-sample data in mdat boxes.
* TTML: Fix parsing of some colors on Jellybean.
* SmoothStreaming: Ignore tfdt boxes.
* Misc bug fixes.
### r1.5.0 ###
......
......@@ -21,7 +21,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:1.2.3'
classpath 'com.novoda:bintray-release:0.3.2'
classpath 'com.novoda:bintray-release:0.3.4'
}
}
......
......@@ -16,8 +16,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.exoplayer.demo"
android:versionCode="1500"
android:versionName="1.5.0"
android:versionCode="1501"
android:versionName="1.5.1"
android:theme="@style/RootTheme">
<uses-permission android:name="android.permission.INTERNET"/>
......@@ -41,9 +41,20 @@
</activity>
<activity android:name="com.google.android.exoplayer.demo.PlayerActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:launchMode="singleInstance"
android:label="@string/application_name"
android:theme="@style/PlayerTheme"/>
android:theme="@style/PlayerTheme">
<intent-filter>
<action android:name="com.google.android.exoplayer.demo.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:scheme="content"/>
<data android:scheme="asset"/>
<data android:scheme="file"/>
</intent-filter>
</activity>
</application>
......
......@@ -78,13 +78,19 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
DemoPlayer.Listener, DemoPlayer.CaptionListener, DemoPlayer.Id3MetadataListener,
AudioCapabilitiesReceiver.Listener {
// For use within demo app code.
public static final String CONTENT_ID_EXTRA = "content_id";
public static final String CONTENT_TYPE_EXTRA = "content_type";
public static final int TYPE_DASH = 0;
public static final int TYPE_SS = 1;
public static final int TYPE_HLS = 2;
public static final int TYPE_OTHER = 3;
public static final String CONTENT_TYPE_EXTRA = "content_type";
public static final String CONTENT_ID_EXTRA = "content_id";
// For use when launching the demo app using adb.
private static final String CONTENT_EXT_EXTRA = "type";
private static final String EXT_DASH = ".mpd";
private static final String EXT_SS = ".ism";
private static final String EXT_HLS = ".m3u8";
private static final String TAG = "PlayerActivity";
private static final int MENU_GROUP_TRACKS = 1;
......@@ -129,11 +135,6 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
contentUri = intent.getData();
contentType = intent.getIntExtra(CONTENT_TYPE_EXTRA, -1);
contentId = intent.getStringExtra(CONTENT_ID_EXTRA);
setContentView(R.layout.player_activity);
View root = findViewById(R.id.root);
root.setOnTouchListener(new OnTouchListener() {
......@@ -150,7 +151,8 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
root.setOnKeyListener(new OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) {
if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE
|| keyCode == KeyEvent.KEYCODE_MENU) {
return false;
}
return mediaController.dispatchKeyEvent(event);
......@@ -186,8 +188,20 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
}
@Override
public void onNewIntent(Intent intent) {
releasePlayer();
playerPosition = 0;
setIntent(intent);
}
@Override
public void onResume() {
super.onResume();
Intent intent = getIntent();
contentUri = intent.getData();
contentType = intent.getIntExtra(CONTENT_TYPE_EXTRA,
inferContentType(contentUri, intent.getStringExtra(CONTENT_EXT_EXTRA)));
contentId = intent.getStringExtra(CONTENT_ID_EXTRA);
configureSubtitleView();
if (player == null) {
preparePlayer(true);
......@@ -597,4 +611,28 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
return CaptionStyleCompat.createFromCaptionStyle(captioningManager.getUserStyle());
}
/**
* Makes a best guess to infer the type from a media {@link Uri} and an optional overriding file
* extension.
*
* @param uri The {@link Uri} of the media.
* @param fileExtension An overriding file extension.
* @return The inferred type.
*/
private static int inferContentType(Uri uri, String fileExtension) {
String lastPathSegment = !TextUtils.isEmpty(fileExtension) ? "." + fileExtension
: uri.getLastPathSegment();
if (lastPathSegment == null) {
return TYPE_OTHER;
} else if (lastPathSegment.endsWith(EXT_DASH)) {
return TYPE_DASH;
} else if (lastPathSegment.endsWith(EXT_SS)) {
return TYPE_SS;
} else if (lastPathSegment.endsWith(EXT_HLS)) {
return TYPE_HLS;
} else {
return TYPE_OTHER;
}
}
}
......@@ -219,8 +219,8 @@ public class DashRendererBuilder implements RendererBuilder {
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
DemoPlayer.TYPE_VIDEO);
TrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource,
drmSessionManager, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null,
TrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context, videoSampleSource,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, drmSessionManager, true,
mainHandler, player, 50);
// Build the audio renderer.
......
......@@ -61,8 +61,8 @@ public class ExtractorRendererBuilder implements RendererBuilder {
DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
ExtractorSampleSource sampleSource = new ExtractorSampleSource(uri, dataSource, allocator,
BUFFER_SEGMENT_COUNT * BUFFER_SEGMENT_SIZE);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
null, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null, player.getMainHandler(),
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context,
sampleSource, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, player.getMainHandler(),
player, 50);
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource,
null, true, player.getMainHandler(), player, AudioCapabilities.getCapabilities(context));
......
......@@ -148,8 +148,8 @@ public class HlsRendererBuilder implements RendererBuilder {
variantIndices, HlsChunkSource.ADAPTIVE_MODE_SPLICE);
HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, loadControl,
BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_VIDEO);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, mainHandler, player, 50);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context,
sampleSource, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, mainHandler, player, 50);
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource,
null, true, player.getMainHandler(), player, AudioCapabilities.getCapabilities(context));
MetadataTrackRenderer<Map<String, Object>> id3Renderer = new MetadataTrackRenderer<>(
......
......@@ -163,9 +163,9 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder {
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
DemoPlayer.TYPE_VIDEO);
TrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource,
drmSessionManager, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null,
mainHandler, player, 50);
TrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context, videoSampleSource,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, drmSessionManager, true, mainHandler,
player, 50);
// Build the audio renderer.
DataSource audioDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
......
......@@ -16,9 +16,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.google.android.exoplayer.demo.webm"
android:versionCode="1500"
android:versionName="1.5.0"
package="com.google.android.exoplayer.demo.vp9opus"
android:versionCode="1501"
android:versionName="1.5.1"
android:theme="@style/RootTheme">
<uses-permission android:name="android.permission.INTERNET"/>
......@@ -35,7 +35,7 @@
android:allowBackup="false"
android:icon="@drawable/ic_launcher">
<activity android:name="com.google.android.exoplayer.demo.webm.SampleChooserActivity"
<activity android:name="com.google.android.exoplayer.demo.vp9opus.SampleChooserActivity"
android:label="@string/app_name"
android:configChanges="keyboardHidden">
<intent-filter>
......@@ -44,12 +44,12 @@
</intent-filter>
</activity>
<activity android:name="com.google.android.exoplayer.demo.webm.VideoPlayer"
<activity android:name="com.google.android.exoplayer.demo.vp9opus.VideoPlayer"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/app_name"
android:theme="@style/PlayerTheme"/>
<activity android:name="com.google.android.exoplayer.demo.webm.FilePickerActivity"
<activity android:name="com.google.android.exoplayer.demo.vp9opus.FilePickerActivity"
android:theme="@android:style/Theme.Dialog"/>
</application>
......
......@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer.demo.webm;
package com.google.android.exoplayer.demo.vp9opus;
import com.google.android.exoplayer.DefaultLoadControl;
import com.google.android.exoplayer.LoadControl;
......
......@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer.demo.webm;
package com.google.android.exoplayer.demo.vp9opus;
import android.app.Activity;
import android.app.ListActivity;
......
......@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer.demo.webm;
package com.google.android.exoplayer.demo.vp9opus;
import android.app.Activity;
import android.content.Context;
......
......@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer.demo.webm;
package com.google.android.exoplayer.demo.vp9opus;
import com.google.android.exoplayer.AspectRatioFrameLayout;
import com.google.android.exoplayer.ExoPlaybackException;
......
......@@ -77,7 +77,7 @@ publish {
userOrg = 'google'
groupId = 'com.google.android.exoplayer'
artifactId = 'exoplayer'
version = 'r1.5.0'
version = 'r1.5.1'
description = 'The ExoPlayer library.'
website = 'https://github.com/google/ExoPlayer'
}
......@@ -196,6 +196,11 @@ public class DefaultEbmlReaderTest extends TestCase {
}
@Override
public boolean isLevel1Element(int id) {
return false;
}
@Override
public void startMasterElement(int id, long contentPosition, long contentSize) {
events.add(formatEvent(id, "start contentPosition=" + contentPosition
+ " contentSize=" + contentSize));
......
......@@ -95,17 +95,26 @@ public class VarintReaderTest extends TestCase {
int bytesRead = input.read(new byte[1], 0, 1);
assertEquals(1, bytesRead);
// End of input allowed.
long result = reader.readUnsignedVarint(input, true, false);
assertEquals(-1, result);
long result = reader.readUnsignedVarint(input, true, false, 8);
assertEquals(C.RESULT_END_OF_INPUT, result);
// End of input not allowed.
try {
reader.readUnsignedVarint(input, false, false);
reader.readUnsignedVarint(input, false, false, 8);
fail();
} catch (EOFException e) {
// Expected.
}
}
public void testReadVarintExceedsMaximumAllowedLength() throws IOException, InterruptedException {
VarintReader reader = new VarintReader();
DataSource dataSource = buildDataSource(DATA_8_BYTE_0);
dataSource.open(new DataSpec(Uri.parse(TEST_URI)));
ExtractorInput input = new DefaultExtractorInput(dataSource, 0, C.LENGTH_UNBOUNDED);
long result = reader.readUnsignedVarint(input, false, true, 4);
assertEquals(C.RESULT_MAX_LENGTH_EXCEEDED, result);
}
public void testReadVarint() throws IOException, InterruptedException {
VarintReader reader = new VarintReader();
testReadVarint(reader, true, DATA_1_BYTE_0, 1, 0);
......@@ -183,7 +192,7 @@ public class VarintReaderTest extends TestCase {
DataSource dataSource = buildDataSource(data);
dataSource.open(new DataSpec(Uri.parse(TEST_URI)));
ExtractorInput input = new DefaultExtractorInput(dataSource, 0, C.LENGTH_UNBOUNDED);
long result = reader.readUnsignedVarint(input, false, removeMask);
long result = reader.readUnsignedVarint(input, false, removeMask, 8);
assertEquals(expectedLength, input.getPosition());
assertEquals(expectedValue, result);
}
......@@ -198,7 +207,7 @@ public class VarintReaderTest extends TestCase {
dataSource.open(new DataSpec(Uri.parse(TEST_URI), position, C.LENGTH_UNBOUNDED, null));
input = new DefaultExtractorInput(dataSource, position, C.LENGTH_UNBOUNDED);
try {
result = reader.readUnsignedVarint(input, false, removeMask);
result = reader.readUnsignedVarint(input, false, removeMask, 8);
position = input.getPosition();
} catch (IOException e) {
// Expected. We'll try again from the position that the input was advanced to.
......
/*
* 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.text.ttml;
import android.graphics.Color;
import android.test.InstrumentationTestCase;
/**
* Unit test for <code>TtmlColorParser</code>.
*/
public class TtmlColorParserTest extends InstrumentationTestCase {
public void testHexCodeParsing() {
assertEquals(Color.WHITE, TtmlColorParser.parseColor("#ffffff"));
assertEquals(Color.WHITE, TtmlColorParser.parseColor("#ffffffff"));
assertEquals(Color.parseColor("#00ffffff"), TtmlColorParser.parseColor("#00ffffff"));
assertEquals(Color.parseColor("#12341234"), TtmlColorParser.parseColor("#12341234"));
}
public void testColorNameParsing() {
assertEquals(TtmlColorParser.TRANSPARENT, TtmlColorParser.parseColor("transparent"));
assertEquals(TtmlColorParser.BLACK, TtmlColorParser.parseColor("black"));
assertEquals(TtmlColorParser.GRAY, TtmlColorParser.parseColor("gray"));
assertEquals(TtmlColorParser.SILVER, TtmlColorParser.parseColor("silver"));
assertEquals(TtmlColorParser.WHITE, TtmlColorParser.parseColor("white"));
assertEquals(TtmlColorParser.MAROON, TtmlColorParser.parseColor("maroon"));
assertEquals(TtmlColorParser.RED, TtmlColorParser.parseColor("red"));
assertEquals(TtmlColorParser.PURPLE, TtmlColorParser.parseColor("purple"));
assertEquals(TtmlColorParser.FUCHSIA, TtmlColorParser.parseColor("fuchsia"));
assertEquals(TtmlColorParser.MAGENTA, TtmlColorParser.parseColor("magenta"));
assertEquals(TtmlColorParser.GREEN, TtmlColorParser.parseColor("green"));
assertEquals(TtmlColorParser.LIME, TtmlColorParser.parseColor("lime"));
assertEquals(TtmlColorParser.OLIVE, TtmlColorParser.parseColor("olive"));
assertEquals(TtmlColorParser.YELLOW, TtmlColorParser.parseColor("yellow"));
assertEquals(TtmlColorParser.NAVY, TtmlColorParser.parseColor("navy"));
assertEquals(TtmlColorParser.BLUE, TtmlColorParser.parseColor("blue"));
assertEquals(TtmlColorParser.TEAL, TtmlColorParser.parseColor("teal"));
assertEquals(TtmlColorParser.AQUA, TtmlColorParser.parseColor("aqua"));
assertEquals(TtmlColorParser.CYAN, TtmlColorParser.parseColor("cyan"));
}
public void testParseUnknownColor() {
try {
TtmlColorParser.parseColor("colorOfAnElectron");
fail();
} catch (IllegalArgumentException e) {
// expected
}
}
public void testParseNull() {
try {
TtmlColorParser.parseColor(null);
fail();
} catch (IllegalArgumentException e) {
// expected
}
}
public void testParseEmpty() {
try {
TtmlColorParser.parseColor("");
fail();
} catch (IllegalArgumentException e) {
// expected
}
}
public void testRgbColorParsing() {
assertEquals(Color.WHITE, TtmlColorParser.parseColor("rgb(255,255,255)"));
// spaces do not matter
assertEquals(Color.WHITE, TtmlColorParser.parseColor(" rgb ( 255, 255, 255)"));
}
public void testRgbColorParsing_rgbValuesOutOfBounds() {
int outOfBounds = TtmlColorParser.parseColor("rgb(999, 999, 999)");
int color = Color.rgb(999, 999, 999);
// behave like framework Color behaves
assertEquals(color, outOfBounds);
}
public void testRgbColorParsing_rgbValuesNegative() {
try {
TtmlColorParser.parseColor("rgb(-4, 55, 209)");
fail();
} catch (IllegalArgumentException e) {
// expected
}
}
public void testRgbaColorParsing() {
assertEquals(Color.WHITE, TtmlColorParser.parseColor("rgba(255,255,255,0)"));
assertEquals(Color.argb(0, 255, 255, 255), TtmlColorParser.parseColor("rgba(255,255,255,255)"));
assertEquals(Color.BLACK, TtmlColorParser.parseColor("rgba(0, 0, 0, 0)"));
assertEquals(Color.argb(0, 0, 0, 0), TtmlColorParser.parseColor("rgba(0, 0, 0, 255)"));
assertEquals(Color.RED, TtmlColorParser.parseColor("rgba(255, 0, 0, 0)"));
assertEquals(Color.argb(0, 255, 0, 0), TtmlColorParser.parseColor("rgba(255, 0, 0, 255)"));
assertEquals(Color.argb(205, 255, 0, 0), TtmlColorParser.parseColor("rgba(255, 0, 0, 50)"));
}
}
......@@ -17,7 +17,6 @@ package com.google.android.exoplayer.text.ttml;
import com.google.android.exoplayer.text.Cue;
import android.graphics.Color;
import android.test.InstrumentationTestCase;
import android.text.Layout;
import android.text.SpannableStringBuilder;
......@@ -67,8 +66,8 @@ public final class TtmlParserTest extends InstrumentationTestCase {
TtmlNode body = queryChildrenForTag(root, TtmlNode.TAG_BODY, 0);
TtmlNode firstDiv = queryChildrenForTag(body, TtmlNode.TAG_DIV, 0);
TtmlStyle firstPStyle = queryChildrenForTag(firstDiv, TtmlNode.TAG_P, 0).style;
assertEquals(Color.parseColor("yellow"), firstPStyle.getColor());
assertEquals(Color.parseColor("blue"), firstPStyle.getBackgroundColor());
assertEquals(TtmlColorParser.parseColor("yellow"), firstPStyle.getColor());
assertEquals(TtmlColorParser.parseColor("blue"), firstPStyle.getBackgroundColor());
assertEquals("serif", firstPStyle.getFontFamily());
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, firstPStyle.getStyle());
assertTrue(firstPStyle.isUnderline());
......@@ -78,24 +77,43 @@ public final class TtmlParserTest extends InstrumentationTestCase {
TtmlSubtitle subtitle = getSubtitle(INLINE_ATTRIBUTES_TTML_FILE);
assertEquals(4, subtitle.getEventTimeCount());
assertSpans(subtitle, 20, "text 2", "sansSerif", TtmlStyle.STYLE_ITALIC,
Color.CYAN, Color.parseColor("lime"), false, true, null);
TtmlColorParser.CYAN, TtmlColorParser.parseColor("lime"), false, true, null);
}
/**
* regression test for devices on JellyBean where some named colors are not correctly defined
* on framework level. Tests that <i>lime</i> resolves to <code>#FF00FF00</code> not
* <code>#00FF00</code>.
*
* See: https://github.com/android/platform_frameworks_base/blob/jb-mr2-release/
* graphics/java/android/graphics/Color.java#L414
* https://github.com/android/platform_frameworks_base/blob/kitkat-mr2.2-release/
* graphics/java/android/graphics/Color.java#L414
*
* @throws IOException thrown if reading subtitle file fails.
*/
public void testLime() throws IOException {
TtmlSubtitle subtitle = getSubtitle(INLINE_ATTRIBUTES_TTML_FILE);
assertEquals(4, subtitle.getEventTimeCount());
assertSpans(subtitle, 20, "text 2", "sansSerif", TtmlStyle.STYLE_ITALIC,
TtmlColorParser.CYAN, TtmlColorParser.LIME, false, true, null);
}
public void testInheritGlobalStyle() throws IOException {
TtmlSubtitle subtitle = getSubtitle(INHERIT_STYLE_TTML_FILE);
assertEquals(2, subtitle.getEventTimeCount());
assertSpans(subtitle, 10, "text 1", "serif", TtmlStyle.STYLE_BOLD_ITALIC,
Color.BLUE, Color.YELLOW, true, false, null);
TtmlColorParser.BLUE, TtmlColorParser.YELLOW, true, false, null);
}
public void testInheritGlobalStyleOverriddenByInlineAttributes() throws IOException {
TtmlSubtitle subtitle = getSubtitle(INHERIT_STYLE_OVERRIDE_TTML_FILE);
assertEquals(4, subtitle.getEventTimeCount());
assertSpans(subtitle, 10, "text 1", "serif", TtmlStyle.STYLE_BOLD_ITALIC, Color.BLUE,
Color.YELLOW, true, false, null);
assertSpans(subtitle, 20, "text 2", "sansSerif", TtmlStyle.STYLE_ITALIC, Color.RED,
Color.YELLOW, true, false, null);
assertSpans(subtitle, 10, "text 1", "serif", TtmlStyle.STYLE_BOLD_ITALIC, TtmlColorParser.BLUE,
TtmlColorParser.YELLOW, true, false, null);
assertSpans(subtitle, 20, "text 2", "sansSerif", TtmlStyle.STYLE_ITALIC, TtmlColorParser.RED,
TtmlColorParser.YELLOW, true, false, null);
}
public void testInheritGlobalAndParent() throws IOException {
......@@ -103,9 +121,10 @@ public final class TtmlParserTest extends InstrumentationTestCase {
assertEquals(4, subtitle.getEventTimeCount());
assertSpans(subtitle, 10, "text 1", "sansSerif", TtmlStyle.STYLE_NORMAL,
Color.RED, Color.parseColor("lime"), false, true, Layout.Alignment.ALIGN_CENTER);
TtmlColorParser.RED, TtmlColorParser.parseColor("lime"), false, true,
Layout.Alignment.ALIGN_CENTER);
assertSpans(subtitle, 20, "text 2", "serif", TtmlStyle.STYLE_BOLD_ITALIC,
Color.BLUE, Color.YELLOW, true, true, Layout.Alignment.ALIGN_CENTER);
TtmlColorParser.BLUE, TtmlColorParser.YELLOW, true, true, Layout.Alignment.ALIGN_CENTER);
}
public void testInheritMultipleStyles() throws IOException {
......@@ -113,7 +132,7 @@ public final class TtmlParserTest extends InstrumentationTestCase {
assertEquals(12, subtitle.getEventTimeCount());
assertSpans(subtitle, 10, "text 1", "sansSerif", TtmlStyle.STYLE_BOLD_ITALIC,
Color.BLUE, Color.YELLOW, false, true, null);
TtmlColorParser.BLUE, TtmlColorParser.YELLOW, false, true, null);
}
public void testInheritMultipleStylesWithoutLocalAttributes() throws IOException {
......@@ -121,7 +140,7 @@ public final class TtmlParserTest extends InstrumentationTestCase {
assertEquals(12, subtitle.getEventTimeCount());
assertSpans(subtitle, 20, "text 2", "sansSerif", TtmlStyle.STYLE_BOLD_ITALIC,
Color.BLUE, Color.BLACK, false, true, null);
TtmlColorParser.BLUE, TtmlColorParser.BLACK, false, true, null);
}
......@@ -130,7 +149,7 @@ public final class TtmlParserTest extends InstrumentationTestCase {
assertEquals(12, subtitle.getEventTimeCount());
assertSpans(subtitle, 30, "text 2.5", "sansSerifInline", TtmlStyle.STYLE_ITALIC,
Color.RED, Color.YELLOW, true, true, null);
TtmlColorParser.RED, TtmlColorParser.YELLOW, true, true, null);
}
public void testEmptyStyleAttribute() throws IOException {
......@@ -175,16 +194,16 @@ public final class TtmlParserTest extends InstrumentationTestCase {
TtmlStyle style = globalStyles.get("s2");
assertEquals("serif", style.getFontFamily());
assertEquals(Color.RED, style.getBackgroundColor());
assertEquals(Color.BLACK, style.getColor());
assertEquals(TtmlColorParser.RED, style.getBackgroundColor());
assertEquals(TtmlColorParser.BLACK, style.getColor());
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, style.getStyle());
assertTrue(style.isLinethrough());
style = globalStyles.get("s3");
// only difference: color must be RED
assertEquals(Color.RED, style.getColor());
assertEquals(TtmlColorParser.RED, style.getColor());
assertEquals("serif", style.getFontFamily());
assertEquals(Color.RED, style.getBackgroundColor());
assertEquals(TtmlColorParser.RED, style.getBackgroundColor());
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, style.getStyle());
assertTrue(style.isLinethrough());
}
......@@ -224,8 +243,8 @@ public final class TtmlParserTest extends InstrumentationTestCase {
TtmlStyle style = queryChildrenForTag(div, TtmlNode.TAG_P, 0).style;
assertNotNull(style);
assertEquals(Color.BLACK, style.getBackgroundColor());
assertEquals(Color.YELLOW, style.getColor());
assertEquals(TtmlColorParser.BLACK, style.getBackgroundColor());
assertEquals(TtmlColorParser.YELLOW, style.getColor());
assertEquals(TtmlStyle.STYLE_ITALIC, style.getStyle());
assertEquals("sansSerif", style.getFontFamily());
assertFalse(style.isUnderline());
......@@ -243,8 +262,8 @@ public final class TtmlParserTest extends InstrumentationTestCase {
TtmlStyle style = queryChildrenForTag(div, TtmlNode.TAG_P, 0).style;
assertNotNull(style);
assertEquals(Color.BLACK, style.getBackgroundColor());
assertEquals(Color.YELLOW, style.getColor());
assertEquals(TtmlColorParser.BLACK, style.getBackgroundColor());
assertEquals(TtmlColorParser.YELLOW, style.getColor());
assertEquals(TtmlStyle.STYLE_ITALIC, style.getStyle());
assertEquals("sansSerif", style.getFontFamily());
assertFalse(style.isUnderline());
......
......@@ -68,6 +68,11 @@ public final class C {
@SuppressWarnings("InlinedApi")
public static final int ENCODING_E_AC3 = AudioFormat.ENCODING_E_AC3;
// TODO: Switch these constants to use AudioFormat fields when the target API version is >= 23.
// The inlined values here are for NVIDIA Shield devices which support DTS on earlier versions.
public static final int ENCODING_DTS = 7;
public static final int ENCODING_DTS_HD = 8;
/**
* @see MediaExtractor#SAMPLE_FLAG_SYNC
*/
......@@ -90,6 +95,11 @@ public final class C {
*/
public static final int RESULT_END_OF_INPUT = -1;
/**
* A return value for methods where the length of parsed data exceeds the maximum length allowed.
*/
public static final int RESULT_MAX_LENGTH_EXCEEDED = -2;
private C() {}
}
......@@ -23,7 +23,7 @@ public final class ExoPlayerLibraryInfo {
/**
* The version of the library, expressed as a string.
*/
public static final String VERSION = "1.5.0";
public static final String VERSION = "1.5.1";
/**
* The version of the library, expressed as an integer.
......@@ -31,7 +31,7 @@ public final class ExoPlayerLibraryInfo {
* Three digits are used for each component of {@link #VERSION}. For example "1.2.3" has the
* corresponding integer version 001002003.
*/
public static final int VERSION_INT = 001005000;
public static final int VERSION_INT = 001005001;
/**
* Whether the library was compiled with {@link com.google.android.exoplayer.util.Assertions}
......
......@@ -643,7 +643,7 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
adaptiveReconfigurationBytes);
codec.queueSecureInputBuffer(inputIndex, 0, cryptoInfo, presentationTimeUs, 0);
} else {
codec.queueInputBuffer(inputIndex, 0 , bufferSize, presentationTimeUs, 0);
codec.queueInputBuffer(inputIndex, 0, bufferSize, presentationTimeUs, 0);
}
inputIndex = -1;
codecHasQueuedBuffers = true;
......@@ -922,11 +922,7 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
* propagation incorrectly on the host device. False otherwise.
*/
private static boolean codecNeedsEosPropagationWorkaround(String name) {
return Util.SDK_INT <= 17
&& "OMX.rk.video_decoder.avc".equals(name)
&& ("ht7s3".equals(Util.DEVICE) // Tesco HUDL
|| "rk30sdk".equals(Util.DEVICE) // Rockchip rk30
|| "rk31sdk".equals(Util.DEVICE)); // Rockchip rk31
return Util.SDK_INT <= 17 && "OMX.rk.video_decoder.avc".equals(name);
}
/**
......
......@@ -94,8 +94,14 @@ public final class MediaCodecUtil {
/**
* Returns the name of the best decoder and its capabilities for the given mimeType.
*
* @param mimeType The mime type.
* @param secure Whether the decoder is required to support secure decryption. Always pass false
* unless secure decryption really is required.
* @return The name of the best decoder and its capabilities for the given mimeType, or null if
* no decoder exists.
*/
private static synchronized Pair<String, CodecCapabilities> getMediaCodecInfo(
public static synchronized Pair<String, CodecCapabilities> getMediaCodecInfo(
String mimeType, boolean secure) throws DecoderQueryException {
CodecKey key = new CodecKey(mimeType, secure);
if (codecs.containsKey(key)) {
......@@ -202,8 +208,10 @@ public final class MediaCodecUtil {
return false;
}
// Work around an issue where the VP8 decoder on Samsung Galaxy S4 Mini does not render video.
if (Util.SDK_INT <= 19 && Util.DEVICE != null && Util.DEVICE.startsWith("serrano")
// Work around an issue where the VP8 decoder on Samsung Galaxy S3/S4 Mini does not render
// video.
if (Util.SDK_INT <= 19 && Util.DEVICE != null
&& (Util.DEVICE.startsWith("d2") || Util.DEVICE.startsWith("serrano"))
&& "samsung".equals(Util.MANUFACTURER) && name.equals("OMX.SEC.vp8.dec")) {
return false;
}
......
......@@ -209,6 +209,12 @@ public final class MediaFormat {
this.maxHeight = maxHeight;
}
public MediaFormat copyWithMaxInputSize(int maxInputSize) {
return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, durationUs, width, height,
rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate, language,
subsampleOffsetUs, initializationData, adaptive, maxWidth, maxHeight);
}
public MediaFormat copyWithMaxVideoDimensions(int maxWidth, int maxHeight) {
return new MediaFormat(id, trackId, mimeType, bitrate, maxInputSize, durationUs, width, height,
rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate, language,
......
......@@ -15,17 +15,17 @@
*/
package com.google.android.exoplayer;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer.FrameReleaseTimeHelper;
import android.annotation.TargetApi;
import android.content.Context;
import android.view.Choreographer;
import android.view.Choreographer.FrameCallback;
import android.view.WindowManager;
/**
* Makes a best effort to adjust frame release timestamps for a smoother visual result.
*/
@TargetApi(16)
public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelper, FrameCallback {
public final class VideoFrameReleaseTimeHelper implements FrameCallback {
private static final long CHOREOGRAPHER_SAMPLE_DELAY_MILLIS = 500;
private static final long MAX_ALLOWED_DRIFT_NS = 20000000;
......@@ -33,32 +33,45 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe
private static final long VSYNC_OFFSET_PERCENTAGE = 80;
private static final int MIN_FRAMES_FOR_ADJUSTMENT = 6;
private final boolean usePrimaryDisplayVsync;
private final boolean useDefaultDisplayVsync;
private final long vsyncDurationNs;
private final long vsyncOffsetNs;
private Choreographer choreographer;
private long sampledVsyncTimeNs;
private long lastUnadjustedFrameTimeUs;
private long lastFramePresentationTimeUs;
private long adjustedLastFrameTimeNs;
private long pendingAdjustedFrameTimeNs;
private boolean haveSync;
private long syncReleaseTimeNs;
private long syncFrameTimeNs;
private int frameCount;
private long syncUnadjustedReleaseTimeNs;
private long syncFramePresentationTimeNs;
private long frameCount;
/**
* Constructs an instance that smoothes frame release but does not snap release to the default
* display's vsync signal.
*/
public VideoFrameReleaseTimeHelper() {
this(-1, false);
}
/**
* @param primaryDisplayRefreshRate The refresh rate of the default display.
* @param usePrimaryDisplayVsync Whether to snap to the primary display vsync. May not be
* suitable when rendering to secondary displays.
* Constructs an instance that smoothes frame release and snaps release to the default display's
* vsync signal.
*
* @param context A context from which information about the default display can be retrieved.
*/
public SmoothFrameReleaseTimeHelper(
float primaryDisplayRefreshRate, boolean usePrimaryDisplayVsync) {
this.usePrimaryDisplayVsync = usePrimaryDisplayVsync;
if (usePrimaryDisplayVsync) {
vsyncDurationNs = (long) (1000000000d / primaryDisplayRefreshRate);
public VideoFrameReleaseTimeHelper(Context context) {
this(getDefaultDisplayRefreshRate(context), true);
}
private VideoFrameReleaseTimeHelper(float defaultDisplayRefreshRate,
boolean useDefaultDisplayVsync) {
this.useDefaultDisplayVsync = useDefaultDisplayVsync;
if (useDefaultDisplayVsync) {
vsyncDurationNs = (long) (1000000000d / defaultDisplayRefreshRate);
vsyncOffsetNs = (vsyncDurationNs * VSYNC_OFFSET_PERCENTAGE) / 100;
} else {
vsyncDurationNs = -1;
......@@ -66,19 +79,23 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe
}
}
@Override
/**
* Enables the helper.
*/
public void enable() {
haveSync = false;
if (usePrimaryDisplayVsync) {
if (useDefaultDisplayVsync) {
sampledVsyncTimeNs = 0;
choreographer = Choreographer.getInstance();
choreographer.postFrameCallback(this);
}
}
@Override
/**
* Disables the helper.
*/
public void disable() {
if (usePrimaryDisplayVsync) {
if (useDefaultDisplayVsync) {
choreographer.removeFrameCallback(this);
choreographer = null;
}
......@@ -90,17 +107,25 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe
choreographer.postFrameCallbackDelayed(this, CHOREOGRAPHER_SAMPLE_DELAY_MILLIS);
}
@Override
public long adjustReleaseTime(long unadjustedFrameTimeUs, long unadjustedReleaseTimeNs) {
long unadjustedFrameTimeNs = unadjustedFrameTimeUs * 1000;
/**
* Called to make a fine-grained adjustment to a frame release time.
*
* @param framePresentationTimeUs The frame's media presentation time, in microseconds.
* @param unadjustedReleaseTimeNs The frame's unadjusted release time, in nanoseconds and in
* the same time base as {@link System#nanoTime()}.
* @return An adjusted release time for the frame, in nanoseconds and in the same time base as
* {@link System#nanoTime()}.
*/
public long adjustReleaseTime(long framePresentationTimeUs, long unadjustedReleaseTimeNs) {
long framePresentationTimeNs = framePresentationTimeUs * 1000;
// Until we know better, the adjustment will be a no-op.
long adjustedFrameTimeNs = unadjustedFrameTimeNs;
long adjustedFrameTimeNs = framePresentationTimeNs;
long adjustedReleaseTimeNs = unadjustedReleaseTimeNs;
if (haveSync) {
// See if we've advanced to the next frame.
if (unadjustedFrameTimeUs != lastUnadjustedFrameTimeUs) {
if (framePresentationTimeUs != lastFramePresentationTimeUs) {
frameCount++;
adjustedLastFrameTimeNs = pendingAdjustedFrameTimeNs;
}
......@@ -109,20 +134,22 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe
// Calculate the average frame time across all the frames we've seen since the last sync.
// This will typically give us a frame rate at a finer granularity than the frame times
// themselves (which often only have millisecond granularity).
long averageFrameTimeNs = (unadjustedFrameTimeNs - syncFrameTimeNs) / frameCount;
long averageFrameDurationNs = (framePresentationTimeNs - syncFramePresentationTimeNs)
/ frameCount;
// Project the adjusted frame time forward using the average.
long candidateAdjustedFrameTimeNs = adjustedLastFrameTimeNs + averageFrameTimeNs;
long candidateAdjustedFrameTimeNs = adjustedLastFrameTimeNs + averageFrameDurationNs;
if (isDriftTooLarge(candidateAdjustedFrameTimeNs, unadjustedReleaseTimeNs)) {
haveSync = false;
} else {
adjustedFrameTimeNs = candidateAdjustedFrameTimeNs;
adjustedReleaseTimeNs = syncReleaseTimeNs + adjustedFrameTimeNs - syncFrameTimeNs;
adjustedReleaseTimeNs = syncUnadjustedReleaseTimeNs + adjustedFrameTimeNs
- syncFramePresentationTimeNs;
}
} else {
// We're synced but haven't waited the required number of frames to apply an adjustment.
// Check drift anyway.
if (isDriftTooLarge(unadjustedFrameTimeNs, unadjustedReleaseTimeNs)) {
if (isDriftTooLarge(framePresentationTimeNs, unadjustedReleaseTimeNs)) {
haveSync = false;
}
}
......@@ -130,14 +157,14 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe
// If we need to sync, do so now.
if (!haveSync) {
syncFrameTimeNs = unadjustedFrameTimeNs;
syncReleaseTimeNs = unadjustedReleaseTimeNs;
syncFramePresentationTimeNs = framePresentationTimeNs;
syncUnadjustedReleaseTimeNs = unadjustedReleaseTimeNs;
frameCount = 0;
haveSync = true;
onSynced();
}
lastUnadjustedFrameTimeUs = unadjustedFrameTimeUs;
lastFramePresentationTimeUs = framePresentationTimeUs;
pendingAdjustedFrameTimeNs = adjustedFrameTimeNs;
if (sampledVsyncTimeNs == 0) {
......@@ -155,8 +182,8 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe
}
private boolean isDriftTooLarge(long frameTimeNs, long releaseTimeNs) {
long elapsedFrameTimeNs = frameTimeNs - syncFrameTimeNs;
long elapsedReleaseTimeNs = releaseTimeNs - syncReleaseTimeNs;
long elapsedFrameTimeNs = frameTimeNs - syncFramePresentationTimeNs;
long elapsedReleaseTimeNs = releaseTimeNs - syncUnadjustedReleaseTimeNs;
return Math.abs(elapsedReleaseTimeNs - elapsedFrameTimeNs) > MAX_ALLOWED_DRIFT_NS;
}
......@@ -177,4 +204,9 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe
return snappedAfterDiff < snappedBeforeDiff ? snappedAfterNs : snappedBeforeNs;
}
private static float getDefaultDisplayRefreshRate(Context context) {
WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
return manager.getDefaultDisplay().getRefreshRate();
}
}
......@@ -202,7 +202,7 @@ public final class AudioTrack {
private int temporaryBufferSize;
/**
* Bitrate measured in kilobits per second, if {@link #isPassthrough()} returns true.
* Bitrate measured in kilobits per second, if using passthrough.
*/
private int passthroughBitrate;
......@@ -359,7 +359,7 @@ public final class AudioTrack {
}
}
audioTrackUtil.reconfigure(audioTrack, isPassthrough());
audioTrackUtil.reconfigure(audioTrack, needsPassthroughWorkarounds());
setAudioTrackVolume();
return sessionId;
......@@ -472,8 +472,7 @@ public final class AudioTrack {
return RESULT_BUFFER_CONSUMED;
}
// Workarounds for issues with AC-3 passthrough AudioTracks on API versions 21/22:
if (Util.SDK_INT <= 22 && isPassthrough()) {
if (needsPassthroughWorkarounds()) {
// An AC-3 audio track continues to play data written while it is paused. Stop writing so its
// buffer empties. See [Internal: b/18899620].
if (audioTrack.getPlayState() == android.media.AudioTrack.PLAYSTATE_PAUSED) {
......@@ -491,8 +490,14 @@ public final class AudioTrack {
int result = 0;
if (temporaryBufferSize == 0) {
if (isPassthrough() && passthroughBitrate == UNKNOWN_BITRATE) {
passthroughBitrate = Ac3Util.getBitrate(size, sampleRate);
if (passthroughBitrate == UNKNOWN_BITRATE) {
if (isAc3Passthrough()) {
passthroughBitrate = Ac3Util.getBitrate(size, sampleRate);
} else if (isDtsPassthrough()) {
int unscaledBitrate = size * 8 * sampleRate;
int divisor = 1000 * 512;
passthroughBitrate = (unscaledBitrate + divisor / 2) / divisor;
}
}
// This is the first time we've seen this {@code buffer}.
......@@ -583,7 +588,7 @@ public final class AudioTrack {
public boolean hasPendingData() {
return isInitialized()
&& (bytesToFrames(submittedBytes) > audioTrackUtil.getPlaybackHeadPosition()
|| audioTrackUtil.overrideHasPendingData());
|| overrideHasPendingData());
}
/** Sets the playback volume. */
......@@ -709,10 +714,13 @@ public final class AudioTrack {
}
}
// Don't sample the timestamp and latency if this is a passthrough AudioTrack, as the returned
// values cause audio/video synchronization to be incorrect.
if (!isPassthrough()
&& systemClockUs - lastTimestampSampleTimeUs >= MIN_TIMESTAMP_SAMPLE_INTERVAL_US) {
if (needsPassthroughWorkarounds()) {
// Don't sample the timestamp and latency if this is an AC-3 passthrough AudioTrack on
// platform API versions 21/22, as incorrect values are returned. See [Internal: b/21145353].
return;
}
if (systemClockUs - lastTimestampSampleTimeUs >= MIN_TIMESTAMP_SAMPLE_INTERVAL_US) {
audioTimestampSet = audioTrackUtil.updateTimestamp();
if (audioTimestampSet) {
// Perform sanity checks on the timestamp.
......@@ -818,17 +826,50 @@ public final class AudioTrack {
}
private boolean isPassthrough() {
return isAc3Passthrough() || isDtsPassthrough();
}
private boolean isAc3Passthrough() {
return encoding == C.ENCODING_AC3 || encoding == C.ENCODING_E_AC3;
}
private boolean isDtsPassthrough() {
return encoding == C.ENCODING_DTS || encoding == C.ENCODING_DTS_HD;
}
/**
* Returns whether to work around problems with passthrough audio tracks.
* See [Internal: b/18899620, b/19187573, b/21145353].
*/
private boolean needsPassthroughWorkarounds() {
return Util.SDK_INT < 23 && isAc3Passthrough();
}
/**
* Returns whether the audio track should behave as though it has pending data. This is to work
* around an issue on platform API versions 21/22 where AC-3 audio tracks can't be paused, so we
* empty their buffers when paused. In this case, they should still behave as if they have
* pending data, otherwise writing will never resume.
*/
private boolean overrideHasPendingData() {
return needsPassthroughWorkarounds()
&& audioTrack.getPlayState() == android.media.AudioTrack.PLAYSTATE_PAUSED
&& audioTrack.getPlaybackHeadPosition() == 0;
}
private static int getEncodingForMimeType(String mimeType) {
if (MimeTypes.AUDIO_AC3.equals(mimeType)) {
return C.ENCODING_AC3;
}
if (MimeTypes.AUDIO_EC3.equals(mimeType)) {
return C.ENCODING_E_AC3;
switch (mimeType) {
case MimeTypes.AUDIO_AC3:
return C.ENCODING_AC3;
case MimeTypes.AUDIO_EC3:
return C.ENCODING_E_AC3;
case MimeTypes.AUDIO_DTS:
return C.ENCODING_DTS;
case MimeTypes.AUDIO_DTS_HD:
return C.ENCODING_DTS_HD;
default:
return AudioFormat.ENCODING_INVALID;
}
return AudioFormat.ENCODING_INVALID;
}
/**
......@@ -837,7 +878,7 @@ public final class AudioTrack {
private static class AudioTrackUtil {
protected android.media.AudioTrack audioTrack;
private boolean isPassthrough;
private boolean needsPassthroughWorkaround;
private int sampleRate;
private long lastRawPlaybackHeadPosition;
private long rawPlaybackHeadWrapCount;
......@@ -851,11 +892,13 @@ public final class AudioTrack {
* Reconfigures the audio track utility helper to use the specified {@code audioTrack}.
*
* @param audioTrack The audio track to wrap.
* @param isPassthrough Whether the audio track is used for passthrough (e.g. AC-3) playback.
* @param needsPassthroughWorkaround Whether to workaround issues with pausing AC-3 passthrough
* audio tracks on platform API version 21/22.
*/
public void reconfigure(android.media.AudioTrack audioTrack, boolean isPassthrough) {
public void reconfigure(android.media.AudioTrack audioTrack,
boolean needsPassthroughWorkaround) {
this.audioTrack = audioTrack;
this.isPassthrough = isPassthrough;
this.needsPassthroughWorkaround = needsPassthroughWorkaround;
stopTimestampUs = -1;
lastRawPlaybackHeadPosition = 0;
rawPlaybackHeadWrapCount = 0;
......@@ -866,20 +909,6 @@ public final class AudioTrack {
}
/**
* Returns whether the audio track should behave as though it has pending data. This is to work
* around an issue on platform API versions 21/22 where AC-3 audio tracks can't be paused, so we
* empty their buffers when paused. In this case, they should still behave as if they have
* pending data, otherwise writing will never resume.
*
* @see #handleBuffer
*/
public boolean overrideHasPendingData() {
return Util.SDK_INT <= 22 && isPassthrough
&& audioTrack.getPlayState() == android.media.AudioTrack.PLAYSTATE_PAUSED
&& audioTrack.getPlaybackHeadPosition() == 0;
}
/**
* Stops the audio track in a way that ensures media written to it is played out in full, and
* that {@link #getPlaybackHeadPosition()} and {@link #getPlaybackHeadPositionUs()} continue to
* increment as the remaining media is played out.
......@@ -929,7 +958,7 @@ public final class AudioTrack {
}
long rawPlaybackHeadPosition = 0xFFFFFFFFL & audioTrack.getPlaybackHeadPosition();
if (Util.SDK_INT <= 22 && isPassthrough) {
if (needsPassthroughWorkaround) {
// Work around an issue with passthrough/direct AudioTracks on platform API versions 21/22
// where the playback head position jumps back to zero on paused passthrough/direct audio
// tracks. See [Internal: b/19187573].
......@@ -1009,8 +1038,9 @@ public final class AudioTrack {
}
@Override
public void reconfigure(android.media.AudioTrack audioTrack, boolean isPassthrough) {
super.reconfigure(audioTrack, isPassthrough);
public void reconfigure(android.media.AudioTrack audioTrack,
boolean needsPassthroughWorkaround) {
super.reconfigure(audioTrack, needsPassthroughWorkaround);
rawTimestampFramePositionWrapCount = 0;
lastRawTimestampFramePosition = 0;
lastTimestampFramePosition = 0;
......
......@@ -55,11 +55,9 @@ public final class VideoFormatSelectorUtil {
public static int[] selectVideoFormatsForDefaultDisplay(Context context,
List<? extends FormatWrapper> formatWrappers, String[] allowedContainerMimeTypes,
boolean filterHdFormats) throws DecoderQueryException {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
Point displaySize = getDisplaySize(display);
Point viewportSize = getViewportSize(context);
return selectVideoFormats(formatWrappers, allowedContainerMimeTypes, filterHdFormats, true,
displaySize.x, displaySize.y);
viewportSize.x, viewportSize.y);
}
/**
......@@ -184,6 +182,19 @@ public final class VideoFormatSelectorUtil {
}
}
private static Point getViewportSize(Context context) {
// Before API 23 the platform Display object does not provide a way to identify Android TVs that
// can show 4k resolution in a SurfaceView, so check for supported devices here.
// See also https://developer.sony.com/develop/tvs/android-tv/design-guide/.
if (Util.MODEL != null && Util.MODEL.startsWith("BRAVIA")
&& context.getPackageManager().hasSystemFeature("com.sony.dtv.hardware.panel.qfhd")) {
return new Point(3840, 2160);
}
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
return getDisplaySize(windowManager.getDefaultDisplay());
}
private static Point getDisplaySize(Display display) {
Point displaySize = new Point();
if (Util.SDK_INT >= 17) {
......
......@@ -92,6 +92,7 @@ import java.util.List;
public static final int TYPE_enca = Util.getIntegerCodeForString("enca");
public static final int TYPE_frma = Util.getIntegerCodeForString("frma");
public static final int TYPE_saiz = Util.getIntegerCodeForString("saiz");
public static final int TYPE_saio = Util.getIntegerCodeForString("saio");
public static final int TYPE_uuid = Util.getIntegerCodeForString("uuid");
public static final int TYPE_senc = Util.getIntegerCodeForString("senc");
public static final int TYPE_pasp = Util.getIntegerCodeForString("pasp");
......@@ -219,6 +220,31 @@ import java.util.List;
return null;
}
/**
* Returns the total number of leaf/container children of this atom with the given type.
*
* @param type The type of child atoms to count.
* @return The total number of leaf/container children of this atom with the given type.
*/
public int getChildAtomOfTypeCount(int type) {
int count = 0;
int size = leafChildren.size();
for (int i = 0; i < size; i++) {
LeafAtom atom = leafChildren.get(i);
if (atom.type == type) {
count++;
}
}
size = containerChildren.size();
for (int i = 0; i < size; i++) {
ContainerAtom atom = containerChildren.get(i);
if (atom.type == type) {
count++;
}
}
return count;
}
@Override
public String toString() {
return getAtomTypeString(type)
......
......@@ -106,10 +106,11 @@ import java.util.List;
long[] offsets = new long[sampleCount];
int[] sizes = new int[sampleCount];
int maximumSize = 0;
long[] timestamps = new long[sampleCount];
int[] flags = new int[sampleCount];
if (sampleCount == 0) {
return new TrackSampleTable(offsets, sizes, timestamps, flags);
return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags);
}
// Prepare to read chunk offsets.
......@@ -172,6 +173,9 @@ import java.util.List;
for (int i = 0; i < sampleCount; i++) {
offsets[i] = offsetBytes;
sizes[i] = fixedSampleSize == 0 ? stsz.readUnsignedIntToInt() : fixedSampleSize;
if (sizes[i] > maximumSize) {
maximumSize = sizes[i];
}
timestamps[i] = timestampTimeUnits + timestampOffset;
// All samples are synchronization samples if the stss is not present.
......@@ -244,7 +248,7 @@ import java.util.List;
Assertions.checkArgument(remainingSamplesInChunk == 0);
Assertions.checkArgument(remainingTimestampDeltaChanges == 0);
Assertions.checkArgument(remainingTimestampOffsetChanges == 0);
return new TrackSampleTable(offsets, sizes, timestamps, flags);
return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags);
}
/**
......
......@@ -262,7 +262,10 @@ public final class Mp4Extractor implements Extractor, SeekMap {
}
Mp4Track mp4Track = new Mp4Track(track, trackSampleTable, extractorOutput.track(i));
mp4Track.trackOutput.format(track.mediaFormat);
// Each sample has up to three bytes of overhead for the start code that replaces its length.
// Allow ten source samples per output sample, like the platform extractor.
int maxInputSize = trackSampleTable.maximumSize + 3 * 10;
mp4Track.trackOutput.format(track.mediaFormat.copyWithMaxInputSize(maxInputSize));
tracks.add(mp4Track);
long firstSampleOffset = trackSampleTable.offsets[0];
......
......@@ -25,8 +25,18 @@ import java.io.IOException;
*/
/* package */ final class TrackFragment {
public int sampleDescriptionIndex;
/**
* The default values for samples from the track fragment header.
*/
public DefaultSampleValues header;
/**
* The position (byte offset) of the start of sample data.
*/
public long dataPosition;
/**
* The position (byte offset) of the start of auxiliary data.
*/
public long auxiliaryDataPosition;
/**
* The number of samples contained by the fragment.
*/
......
......@@ -19,31 +19,49 @@ import com.google.android.exoplayer.C;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.Util;
/** Sample table for a track in an MP4 file. */
/**
* Sample table for a track in an MP4 file.
*/
/* package */ final class TrackSampleTable {
/** Sample index when no sample is available. */
/**
* Sample index when no sample is available.
*/
public static final int NO_SAMPLE = -1;
/** Number of samples. */
/**
* Number of samples.
*/
public final int sampleCount;
/** Sample offsets in bytes. */
/**
* Sample offsets in bytes.
*/
public final long[] offsets;
/** Sample sizes in bytes. */
/**
* Sample sizes in bytes.
*/
public final int[] sizes;
/** Sample timestamps in microseconds. */
/**
* Maximum sample size in {@link #sizes}.
*/
public final int maximumSize;
/**
* Sample timestamps in microseconds.
*/
public final long[] timestampsUs;
/** Sample flags. */
/**
* Sample flags.
*/
public final int[] flags;
TrackSampleTable(
long[] offsets, int[] sizes, long[] timestampsUs, int[] flags) {
TrackSampleTable(long[] offsets, int[] sizes, int maximumSize, long[] timestampsUs, int[] flags) {
Assertions.checkArgument(sizes.length == timestampsUs.length);
Assertions.checkArgument(offsets.length == timestampsUs.length);
Assertions.checkArgument(flags.length == timestampsUs.length);
this.offsets = offsets;
this.sizes = sizes;
this.maximumSize = maximumSize;
this.timestampsUs = timestampsUs;
this.flags = flags;
sampleCount = offsets.length;
......
......@@ -19,6 +19,7 @@ import com.google.android.exoplayer.C;
import com.google.android.exoplayer.extractor.ExtractorInput;
import com.google.android.exoplayer.util.Assertions;
import java.io.EOFException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Stack;
......@@ -32,6 +33,9 @@ import java.util.Stack;
private static final int ELEMENT_STATE_READ_CONTENT_SIZE = 1;
private static final int ELEMENT_STATE_READ_CONTENT = 2;
private static final int MAX_ID_BYTES = 4;
private static final int MAX_LENGTH_BYTES = 8;
private static final int MAX_INTEGER_ELEMENT_SIZE_BYTES = 8;
private static final int VALID_FLOAT32_ELEMENT_SIZE_BYTES = 4;
private static final int VALID_FLOAT64_ELEMENT_SIZE_BYTES = 8;
......@@ -68,8 +72,11 @@ import java.util.Stack;
}
if (elementState == ELEMENT_STATE_READ_ID) {
long result = varintReader.readUnsignedVarint(input, true, false);
if (result == -1) {
long result = varintReader.readUnsignedVarint(input, true, false, MAX_ID_BYTES);
if (result == C.RESULT_MAX_LENGTH_EXCEEDED) {
result = maybeResyncToNextLevel1Element(input);
}
if (result == C.RESULT_END_OF_INPUT) {
return false;
}
// Element IDs are at most 4 bytes, so we can cast to integers.
......@@ -78,7 +85,7 @@ import java.util.Stack;
}
if (elementState == ELEMENT_STATE_READ_CONTENT_SIZE) {
elementContentSize = varintReader.readUnsignedVarint(input, false, true);
elementContentSize = varintReader.readUnsignedVarint(input, false, true, MAX_LENGTH_BYTES);
elementState = ELEMENT_STATE_READ_CONTENT;
}
......@@ -128,6 +135,35 @@ import java.util.Stack;
}
/**
* Does a byte by byte search to try and find the next level 1 element. This method is called if
* some invalid data is encountered in the parser.
*
* @param input The {@link ExtractorInput} from which data has to be read.
* @return id of the next level 1 element that has been found.
* @throws EOFException If the end of input was encountered when searching for the next level 1
* element.
* @throws IOException If an error occurs reading from the input.
* @throws InterruptedException If the thread is interrupted.
*/
private long maybeResyncToNextLevel1Element(ExtractorInput input) throws EOFException,
IOException, InterruptedException {
while (true) {
input.resetPeekPosition();
input.peekFully(scratch, 0, MAX_ID_BYTES);
int varintLength = VarintReader.parseUnsignedVarintLength(scratch[0]);
if (varintLength != -1 && varintLength <= MAX_ID_BYTES) {
int potentialId = (int) VarintReader.assembleVarint(scratch, varintLength, false);
if (output.isLevel1Element(potentialId)) {
input.skipFully(varintLength);
input.resetPeekPosition();
return potentialId;
}
}
input.skipFully(1);
}
}
/**
* Reads and returns an integer of length {@code byteLength} from the {@link ExtractorInput}.
*
* @param input The {@link ExtractorInput} from which to read.
......
......@@ -37,6 +37,14 @@ import java.io.IOException;
int getElementType(int id);
/**
* Checks if the given id is that of a level 1 element.
*
* @param id The element ID.
* @return True the given id is that of a level 1 element. false otherwise.
*/
boolean isLevel1Element(int id);
/**
* Called when the start of a master element is encountered.
* <p>
* Following events should be considered as taking place within this element until a matching call
......
package com.google.android.exoplayer.extractor.webm;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.extractor.ExtractorInput;
import java.io.EOFException;
......@@ -19,8 +20,8 @@ import java.io.IOException;
*
* <p>{@code 0x80} is a one-byte integer, {@code 0x40} is two bytes, and so on up to eight bytes.
*/
private static final int[] VARINT_LENGTH_MASKS = new int[] {
0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01
private static final long[] VARINT_LENGTH_MASKS = new long[] {
0x80L, 0x40L, 0x20L, 0x10L, 0x08L, 0x04L, 0x02L, 0x01L
};
private final byte[] scratch;
......@@ -53,48 +54,41 @@ import java.io.IOException;
*
* @param input The {@link ExtractorInput} from which the integer should be read.
* @param allowEndOfInput True if encountering the end of the input having read no data is
* allowed, and should result in {@code -1} being returned. False if it should be
* considered an error, causing an {@link EOFException} to be thrown.
* @param removeLengthMask Removes the variable-length integer length mask from the value
* @return The read value, or -1 if {@code allowEndOfStream} is true and the end of the input was
* encountered.
* allowed, and should result in {@link C#RESULT_END_OF_INPUT} being returned. False if it
* should be considered an error, causing an {@link EOFException} to be thrown.
* @param removeLengthMask Removes the variable-length integer length mask from the value.
* @param maximumAllowedLength Maximum allowed length of the variable integer to be read.
* @return The read value, or {@link C#RESULT_END_OF_INPUT} if {@code allowEndOfStream} is true
* and the end of the input was encountered, or {@link C#RESULT_MAX_LENGTH_EXCEEDED} if the
* length of the varint exceeded maximumAllowedLength.
* @throws IOException If an error occurs reading from the input.
* @throws InterruptedException If the thread is interrupted.
*/
public long readUnsignedVarint(ExtractorInput input, boolean allowEndOfInput,
boolean removeLengthMask) throws IOException, InterruptedException {
boolean removeLengthMask, int maximumAllowedLength) throws IOException, InterruptedException {
if (state == STATE_BEGIN_READING) {
// Read the first byte to establish the length.
if (!input.readFully(scratch, 0, 1, allowEndOfInput)) {
return -1;
return C.RESULT_END_OF_INPUT;
}
int firstByte = scratch[0] & 0xFF;
length = -1;
for (int i = 0; i < VARINT_LENGTH_MASKS.length; i++) {
if ((VARINT_LENGTH_MASKS[i] & firstByte) != 0) {
length = i + 1;
break;
}
}
length = parseUnsignedVarintLength(firstByte);
if (length == -1) {
throw new IllegalStateException("No valid varint length mask found");
}
state = STATE_READ_CONTENTS;
}
if (length > maximumAllowedLength) {
state = STATE_BEGIN_READING;
return C.RESULT_MAX_LENGTH_EXCEEDED;
}
// Read the remaining bytes.
input.readFully(scratch, 1, length - 1);
// Parse the value.
if (removeLengthMask) {
scratch[0] &= ~VARINT_LENGTH_MASKS[length - 1];
}
long varint = 0;
for (int i = 0; i < length; i++) {
varint = (varint << 8) | (scratch[i] & 0xFF);
}
state = STATE_BEGIN_READING;
return varint;
return assembleVarint(scratch, length, removeLengthMask);
}
/**
......@@ -104,4 +98,41 @@ import java.io.IOException;
return length;
}
/**
* Parses and the length of the varint given the first byte.
*
* @param firstByte First byte of the varint.
* @return Length of the varint beginning with the given byte if it was valid, -1 otherwise.
*/
public static int parseUnsignedVarintLength(int firstByte) {
int varIntLength = -1;
for (int i = 0; i < VARINT_LENGTH_MASKS.length; i++) {
if ((VARINT_LENGTH_MASKS[i] & firstByte) != 0) {
varIntLength = i + 1;
break;
}
}
return varIntLength;
}
/**
* Assemble a varint from the given byte array.
*
* @param varintBytes Bytes that make up the varint.
* @param varintLength Length of the varint to assemble.
* @param removeLengthMask Removes the variable-length integer length mask from the value.
* @return Parsed and assembled varint.
*/
public static long assembleVarint(byte[] varintBytes, int varintLength,
boolean removeLengthMask) {
long varint = varintBytes[0] & 0xFFL;
if (removeLengthMask) {
varint &= ~VARINT_LENGTH_MASKS[varintLength - 1];
}
for (int i = 1; i < varintLength; i++) {
varint = (varint << 8) | (varintBytes[i] & 0xFFL);
}
return varint;
}
}
......@@ -64,6 +64,7 @@ public final class WebmExtractor implements Extractor {
private static final String DOC_TYPE_MATROSKA = "matroska";
private static final String CODEC_ID_VP8 = "V_VP8";
private static final String CODEC_ID_VP9 = "V_VP9";
private static final String CODEC_ID_MPEG2 = "V_MPEG2";
private static final String CODEC_ID_MPEG4_SP = "V_MPEG4/ISO/SP";
private static final String CODEC_ID_MPEG4_ASP = "V_MPEG4/ISO/ASP";
private static final String CODEC_ID_MPEG4_AP = "V_MPEG4/ISO/AP";
......@@ -74,6 +75,7 @@ public final class WebmExtractor implements Extractor {
private static final String CODEC_ID_AAC = "A_AAC";
private static final String CODEC_ID_MP3 = "A_MPEG/L3";
private static final String CODEC_ID_AC3 = "A_AC3";
private static final String CODEC_ID_TRUEHD = "A_TRUEHD";
private static final String CODEC_ID_DTS = "A_DTS";
private static final String CODEC_ID_DTS_EXPRESS = "A_DTS/EXPRESS";
private static final String CODEC_ID_DTS_LOSSLESS = "A_DTS/LOSSLESS";
......@@ -347,6 +349,10 @@ public final class WebmExtractor implements Extractor {
}
}
/* package */ boolean isLevel1Element(int id) {
return id == ID_SEGMENT_INFO || id == ID_CLUSTER || id == ID_CUES || id == ID_TRACKS;
}
/* package */ void startMasterElement(int id, long contentPosition, long contentSize)
throws ParserException {
switch (id) {
......@@ -639,7 +645,7 @@ public final class WebmExtractor implements Extractor {
// differ only in the way flags are specified.
if (blockState == BLOCK_STATE_START) {
blockTrackNumber = (int) varintReader.readUnsignedVarint(input, false, true);
blockTrackNumber = (int) varintReader.readUnsignedVarint(input, false, true, 8);
blockTrackNumberLength = varintReader.getLastLength();
blockDurationUs = UNKNOWN;
blockState = BLOCK_STATE_HEADER;
......@@ -1028,6 +1034,7 @@ public final class WebmExtractor implements Extractor {
private static boolean isCodecSupported(String codecId) {
return CODEC_ID_VP8.equals(codecId)
|| CODEC_ID_VP9.equals(codecId)
|| CODEC_ID_MPEG2.equals(codecId)
|| CODEC_ID_MPEG4_SP.equals(codecId)
|| CODEC_ID_MPEG4_ASP.equals(codecId)
|| CODEC_ID_MPEG4_AP.equals(codecId)
......@@ -1038,6 +1045,7 @@ public final class WebmExtractor implements Extractor {
|| CODEC_ID_AAC.equals(codecId)
|| CODEC_ID_MP3.equals(codecId)
|| CODEC_ID_AC3.equals(codecId)
|| CODEC_ID_TRUEHD.equals(codecId)
|| CODEC_ID_DTS.equals(codecId)
|| CODEC_ID_DTS_EXPRESS.equals(codecId)
|| CODEC_ID_DTS_LOSSLESS.equals(codecId)
......@@ -1070,6 +1078,11 @@ public final class WebmExtractor implements Extractor {
}
@Override
public boolean isLevel1Element(int id) {
return WebmExtractor.this.isLevel1Element(id);
}
@Override
public void startMasterElement(int id, long contentPosition, long contentSize)
throws ParserException {
WebmExtractor.this.startMasterElement(id, contentPosition, contentSize);
......@@ -1147,6 +1160,9 @@ public final class WebmExtractor implements Extractor {
case CODEC_ID_VP9:
mimeType = MimeTypes.VIDEO_VP9;
break;
case CODEC_ID_MPEG2:
mimeType = MimeTypes.VIDEO_MPEG2;
break;
case CODEC_ID_MPEG4_SP:
case CODEC_ID_MPEG4_ASP:
case CODEC_ID_MPEG4_AP:
......@@ -1194,6 +1210,9 @@ public final class WebmExtractor implements Extractor {
case CODEC_ID_AC3:
mimeType = MimeTypes.AUDIO_AC3;
break;
case CODEC_ID_TRUEHD:
mimeType = MimeTypes.AUDIO_TRUEHD;
break;
case CODEC_ID_DTS:
case CODEC_ID_DTS_EXPRESS:
mimeType = MimeTypes.AUDIO_DTS;
......
......@@ -427,7 +427,8 @@ public class SmoothStreamingChunkSource implements ChunkSource,
// Build the extractor.
FragmentedMp4Extractor mp4Extractor = new FragmentedMp4Extractor(
FragmentedMp4Extractor.WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME);
FragmentedMp4Extractor.WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME
| FragmentedMp4Extractor.WORKAROUND_IGNORE_TFDT_BOX);
Track mp4Track = new Track(trackIndex, mp4TrackType, element.timescale, durationUs, mediaFormat,
trackEncryptionBoxes, mp4TrackType == Track.TYPE_vide ? 4 : -1);
mp4Extractor.setTrack(mp4Track);
......
/*
* 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.text.ttml;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.Util;
import android.text.TextUtils;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Parser to parse ttml color value expression
* (http://www.w3.org/TR/ttml1/#style-value-color)
*/
/*package*/ final class TtmlColorParser {
private static final String RGB = "rgb";
private static final String RGBA = "rgba";
private static final Pattern RGB_PATTERN = Pattern.compile(
"^rgb\\((\\d{1,3}),(\\d{1,3}),(\\d{1,3})\\)$");
private static final Pattern RGBA_PATTERN = Pattern.compile(
"^rgba\\((\\d{1,3}),(\\d{1,3}),(\\d{1,3}),(\\d{1,3})\\)$");
static final int TRANSPARENT = 0x00000000;
static final int BLACK = 0xFF000000;
static final int SILVER = 0xFFC0C0C0;
static final int GRAY = 0xFF808080;
static final int WHITE = 0xFFFFFFFF;
static final int MAROON = 0xFF800000;
static final int RED = 0xFFFF0000;
static final int PURPLE = 0xFF800080;
static final int FUCHSIA = 0xFFFF00FF;
static final int MAGENTA = FUCHSIA;
static final int GREEN = 0xFF008000;
static final int LIME = 0xFF00FF00;
static final int OLIVE = 0xFF808000;
static final int YELLOW = 0xFFFFFF00;
static final int NAVY = 0xFF000080;
static final int BLUE = 0xFF0000FF;
static final int TEAL = 0xFF008080;
static final int AQUA = 0x00FFFFFF;
static final int CYAN = 0xFF00FFFF;
private static final Map<String, Integer> COLOR_NAME_MAP;
static {
COLOR_NAME_MAP = new HashMap<>();
COLOR_NAME_MAP.put("transparent", TRANSPARENT);
COLOR_NAME_MAP.put("black", BLACK);
COLOR_NAME_MAP.put("silver", SILVER);
COLOR_NAME_MAP.put("gray", GRAY);
COLOR_NAME_MAP.put("white", WHITE);
COLOR_NAME_MAP.put("maroon", MAROON);
COLOR_NAME_MAP.put("red", RED);
COLOR_NAME_MAP.put("purple", PURPLE);
COLOR_NAME_MAP.put("fuchsia", FUCHSIA);
COLOR_NAME_MAP.put("magenta", MAGENTA);
COLOR_NAME_MAP.put("green", GREEN);
COLOR_NAME_MAP.put("lime", LIME);
COLOR_NAME_MAP.put("olive", OLIVE);
COLOR_NAME_MAP.put("yellow", YELLOW);
COLOR_NAME_MAP.put("navy", NAVY);
COLOR_NAME_MAP.put("blue", BLUE);
COLOR_NAME_MAP.put("teal", TEAL);
COLOR_NAME_MAP.put("aqua", AQUA);
COLOR_NAME_MAP.put("cyan", CYAN);
}
public static int parseColor(String colorExpression) {
Assertions.checkArgument(!TextUtils.isEmpty(colorExpression));
colorExpression = colorExpression.replace(" ", "");
if (colorExpression.charAt(0) == '#') {
// Use a long to avoid rollovers on #ffXXXXXX
long color = Long.parseLong(colorExpression.substring(1), 16);
if (colorExpression.length() == 7) {
// Set the alpha value
color |= 0x00000000ff000000;
} else if (colorExpression.length() != 9) {
throw new IllegalArgumentException();
}
return (int) color;
} else if (colorExpression.startsWith(RGBA)) {
Matcher matcher = RGBA_PATTERN.matcher(colorExpression);
if (matcher.matches()) {
return argb(
255 - Integer.parseInt(matcher.group(4), 10),
Integer.parseInt(matcher.group(1), 10),
Integer.parseInt(matcher.group(2), 10),
Integer.parseInt(matcher.group(3), 10)
);
}
} else if (colorExpression.startsWith(RGB)) {
Matcher matcher = RGB_PATTERN.matcher(colorExpression);
if (matcher.matches()) {
return rgb(
Integer.parseInt(matcher.group(1), 10),
Integer.parseInt(matcher.group(2), 10),
Integer.parseInt(matcher.group(3), 10)
);
}
} else {
// we use our own color map
Integer color = COLOR_NAME_MAP.get(Util.toLowerInvariant(colorExpression));
if (color != null) {
return color;
}
}
throw new IllegalArgumentException();
}
private static int argb(int alpha, int red, int green, int blue) {
return (alpha << 24) | (red << 16) | (green << 8) | blue;
}
private static int rgb(int red, int green, int blue) {
return argb(0xFF, red, green, blue);
}
}
......@@ -23,7 +23,6 @@ import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.ParserUtil;
import com.google.android.exoplayer.util.Util;
import android.graphics.Color;
import android.text.Layout;
import android.util.Log;
......@@ -207,7 +206,7 @@ public final class TtmlParser implements SubtitleParser {
case TtmlNode.ATTR_TTS_BACKGROUND_COLOR:
style = createIfNull(style);
try {
style.setBackgroundColor(Color.parseColor(attributeValue));
style.setBackgroundColor(TtmlColorParser.parseColor(attributeValue));
} catch (IllegalArgumentException e) {
Log.w(TAG, "failed parsing background value: '" + attributeValue + "'");
}
......@@ -215,7 +214,7 @@ public final class TtmlParser implements SubtitleParser {
case TtmlNode.ATTR_TTS_COLOR:
style = createIfNull(style);
try {
style.setColor(Color.parseColor(attributeValue));
style.setColor(TtmlColorParser.parseColor(attributeValue));
} catch (IllegalArgumentException e) {
Log.w(TAG, "failed parsing color value: '" + attributeValue + "'");
}
......
......@@ -34,6 +34,7 @@ public final class MimeTypes {
public static final String VIDEO_VP8 = BASE_TYPE_VIDEO + "/x-vnd.on2.vp8";
public static final String VIDEO_VP9 = BASE_TYPE_VIDEO + "/x-vnd.on2.vp9";
public static final String VIDEO_MP4V = BASE_TYPE_VIDEO + "/mp4v-es";
public static final String VIDEO_MPEG2 = BASE_TYPE_VIDEO + "/mpeg2";
public static final String AUDIO_UNKNOWN = BASE_TYPE_AUDIO + "/x-unknown";
public static final String AUDIO_MP4 = BASE_TYPE_AUDIO + "/mp4";
......@@ -45,6 +46,7 @@ public final class MimeTypes {
public static final String AUDIO_RAW = BASE_TYPE_AUDIO + "/raw";
public static final String AUDIO_AC3 = BASE_TYPE_AUDIO + "/ac3";
public static final String AUDIO_EC3 = BASE_TYPE_AUDIO + "/eac3";
public static final String AUDIO_TRUEHD = BASE_TYPE_AUDIO + "/true-hd";
public static final String AUDIO_DTS = BASE_TYPE_AUDIO + "/vnd.dts";
public static final String AUDIO_DTS_HD = BASE_TYPE_AUDIO + "/vnd.dts.hd";
public static final String AUDIO_VORBIS = BASE_TYPE_AUDIO + "/vorbis";
......
......@@ -75,6 +75,12 @@ public final class Util {
*/
public static final String MANUFACTURER = android.os.Build.MANUFACTURER;
/**
* Like {@link android.os.Build#MODEL}, but in a place where it can be conveniently overridden for
* local testing.
*/
public static final String MODEL = android.os.Build.MODEL;
private static final Pattern XS_DATE_TIME_PATTERN = Pattern.compile(
"(\\d\\d\\d\\d)\\-(\\d\\d)\\-(\\d\\d)[Tt]"
+ "(\\d\\d):(\\d\\d):(\\d\\d)(\\.(\\d+))?"
......
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
<classpathentry combineaccessrules="false" kind="src" path="/ExoPlayerLib"/>
<classpathentry kind="src" path="java"/>
<classpathentry kind="src" path="gen"/>
<classpathentry kind="output" path="bin/classes"/>
</classpath>
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>ExoPlayerPlaybackTests</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
<filteredResources>
<filter>
<id>1363908154650</id>
<name></name>
<type>22</type>
<matcher>
<id>org.eclipse.ui.ide.multiFilter</id>
<arguments>1.0-name-matches-false-false-BUILD</arguments>
</matcher>
</filter>
<filter>
<id>1363908154652</id>
<name></name>
<type>10</type>
<matcher>
<id>org.eclipse.ui.ide.multiFilter</id>
<arguments>1.0-name-matches-true-false-build</arguments>
</matcher>
</filter>
</filteredResources>
</projectDescription>
......@@ -17,8 +17,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.google.android.exoplayer.playbacktests"
android:versionCode="1500"
android:versionName="1.5.0">
android:versionCode="1501"
android:versionName="1.5.1">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
......
......@@ -217,7 +217,7 @@ public final class H264DashTest extends ActivityInstrumentationTestCase2<HostAct
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, handler, logger, VIDEO_EVENT_ID,
MIN_LOADABLE_RETRY_COUNT);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(host,
videoSampleSource, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, handler, logger, 50);
videoCounters = videoRenderer.codecCounters;
player.sendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment