Merge branch 'dev-v2' into rtsp-socket-factory

This commit is contained in:
claincly 2021-11-15 17:54:51 +00:00 committed by GitHub
commit cdcf57374a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 754 additions and 277 deletions

View file

@ -2,7 +2,12 @@
### dev-v2 (not yet released)
### 2.16.1 (2021-11-11)
* Core Library:
* Fix track selection issue where overriding one track group did not
disable other track groups of the same type
([#9675](https://github.com/google/ExoPlayer/issues/9675)).
* Fix track selection issue where a mixture of non-empty and empty track
overrides is not applied correctly
([#9649](https://github.com/google/ExoPlayer/issues/9649).
@ -14,6 +19,9 @@
* Extractors:
* WAV: Add support for RF64 streams
([#9543](https://github.com/google/ExoPlayer/issues/9543).
* DASH:
* Add parsed essential and supplemental properties to the `Representation`
([#9579](https://github.com/google/ExoPlayer/issues/9579)).
* RTSP
* Provide a client API to override the `SocketFactory` used for any server
connection ([#9606](https://github.com/google/ExoPlayer/pull/9606)).

View file

@ -13,8 +13,8 @@
// limitations under the License.
project.ext {
// ExoPlayer version and version code.
releaseVersion = '2.16.0'
releaseVersionCode = 2016000
releaseVersion = '2.16.1'
releaseVersionCode = 2016001
minSdkVersion = 16
appTargetSdkVersion = 29
// Upgrading this requires [Internal ref: b/193254928] to be fixed, or some
@ -26,33 +26,32 @@ project.ext {
// Use the same Guava version as the Android repo:
// https://cs.android.com/android/platform/superproject/+/master:external/guava/METADATA
guavaVersion = '27.1-android'
mockitoVersion = '3.4.0'
mockWebServerVersion = '3.12.0'
mockitoVersion = '3.12.4'
robolectricVersion = '4.6.1'
// Keep this in sync with Google's internal Checker Framework version.
checkerframeworkVersion = '3.5.0'
checkerframeworkCompatVersion = '2.5.0'
errorProneVersion = '2.9.0'
checkerframeworkVersion = '3.13.0'
checkerframeworkCompatVersion = '2.5.5'
errorProneVersion = '2.10.0'
jsr305Version = '3.0.2'
kotlinAnnotationsVersion = '1.5.20'
androidxAnnotationVersion = '1.2.0'
androidxAppCompatVersion = '1.3.0'
kotlinAnnotationsVersion = '1.5.31'
androidxAnnotationVersion = '1.3.0'
androidxAppCompatVersion = '1.3.1'
androidxCollectionVersion = '1.1.0'
androidxCoreVersion = '1.6.0'
androidxCoreVersion = '1.7.0'
androidxFuturesVersion = '1.1.0'
androidxMediaVersion = '1.4.3'
androidxMedia2Version = '1.1.3'
androidxMedia2Version = '1.2.0'
androidxMultidexVersion = '2.0.1'
androidxRecyclerViewVersion = '1.2.1'
androidxMaterialVersion = '1.3.0'
androidxTestCoreVersion = '1.3.0'
androidxTestJUnitVersion = '1.1.2'
androidxTestRunnerVersion = '1.3.0'
androidxTestRulesVersion = '1.3.0'
androidxTestServicesStorageVersion = '1.3.0'
androidxTestTruthVersion = '1.3.0'
androidxMaterialVersion = '1.4.0'
androidxTestCoreVersion = '1.4.0'
androidxTestJUnitVersion = '1.1.3'
androidxTestRunnerVersion = '1.4.0'
androidxTestRulesVersion = '1.4.0'
androidxTestServicesStorageVersion = '1.4.0'
androidxTestTruthVersion = '1.4.0'
truthVersion = '1.1.3'
okhttpVersion = '4.9.1'
okhttpVersion = '4.9.2'
modulePrefix = ':'
if (gradle.ext.has('exoplayerModulePrefix')) {
modulePrefix += gradle.ext.exoplayerModulePrefix

View file

@ -126,7 +126,7 @@ may result in unexpected or obscure errors. It will be removed in ExoPlayer
2.16.
{:.info}
For more information about ExoPlayer's treading model, see the
For more information about ExoPlayer's threading model, see the
["Threading model" section of the ExoPlayer Javadoc][].
## Attaching the player to a view ##

View file

@ -31,7 +31,8 @@ Changes in player state can be received by implementing
`Player.Listener`. The player can be in one of four playback states:
* `Player.STATE_IDLE`: This is the initial state, the state when the player is
stopped, and when playback failed.
stopped, and when playback failed. The player will hold only limited resources
in this state.
* `Player.STATE_BUFFERING`: The player is not able to immediately play from its
current position. This mostly happens because more data needs to be loaded.
* `Player.STATE_READY`: The player is able to immediately play from its current

View file

@ -392,7 +392,6 @@ public class CastPlayerTest {
assertThat(mediaQueueItems[1].getMedia().getContentId()).isEqualTo(uri2);
}
@SuppressWarnings("deprecation") // Verifies deprecated callback being called correctly.
@Test
public void setMediaItems_replaceExistingPlaylist_notifiesMediaItemTransition() {
List<MediaItem> firstPlaylist = new ArrayList<>();

View file

@ -32,7 +32,7 @@ dependencies {
androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito:' + dexmakerVersion
// Instrumentation tests assume that an app-packaged version of cronet is
// available.
androidTestImplementation 'org.chromium.net:cronet-embedded:94.4606.61'
androidTestImplementation 'org.chromium.net:cronet-embedded:95.4638.50'
androidTestImplementation project(modulePrefix + 'testutils')
testImplementation project(modulePrefix + 'testutils')
testImplementation 'com.squareup.okhttp3:mockwebserver:' + okhttpVersion

View file

@ -25,7 +25,7 @@ android {
}
dependencies {
api 'com.google.ads.interactivemedia.v3:interactivemedia:3.24.0'
api 'com.google.ads.interactivemedia.v3:interactivemedia:3.25.1'
implementation project(modulePrefix + 'library-core')
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion

View file

@ -134,8 +134,6 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter implements Runnab
return player.getPlaybackState() == Player.STATE_IDLE ? -1 : player.getCurrentPosition();
}
// Calls deprecated method to provide backwards compatibility.
@SuppressWarnings("deprecation")
@Override
public void play() {
if (player.getPlaybackState() == Player.STATE_IDLE) {

View file

@ -27,11 +27,11 @@ public final class ExoPlayerLibraryInfo {
/** The version of the library expressed as a string, for example "1.2.3". */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
public static final String VERSION = "2.16.0";
public static final String VERSION = "2.16.1";
/** The version of the library expressed as {@code TAG + "/" + VERSION}. */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final String VERSION_SLASHY = "ExoPlayerLib/2.16.0";
public static final String VERSION_SLASHY = "ExoPlayerLib/2.16.1";
/**
* The version of the library expressed as an integer, for example 1002003.
@ -41,7 +41,7 @@ public final class ExoPlayerLibraryInfo {
* integer version 123045006 (123-045-006).
*/
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final int VERSION_INT = 2016000;
public static final int VERSION_INT = 2016001;
/** Whether the library was compiled with {@link Assertions} checks enabled. */
public static final boolean ASSERTIONS_ENABLED = true;

View file

@ -1087,7 +1087,10 @@ public interface Player {
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
@IntDef({STATE_IDLE, STATE_BUFFERING, STATE_READY, STATE_ENDED})
@interface State {}
/** The player is idle, and must be {@link #prepare() prepared} before it will play the media. */
/**
* The player is idle, meaning it holds only limited resources. The player must be {@link
* #prepare() prepared} before it will play the media.
*/
int STATE_IDLE = 1;
/**
* The player is not able to immediately play the media, but is doing work toward being able to do
@ -1669,7 +1672,12 @@ public interface Player {
*/
Commands getAvailableCommands();
/** Prepares the player. */
/**
* Prepares the player.
*
* <p>This will move the player out of {@link #STATE_IDLE idle state} and the player will start
* loading media and acquire resources needed for playback.
*/
void prepare();
/**
@ -2001,12 +2009,13 @@ public interface Player {
PlaybackParameters getPlaybackParameters();
/**
* Stops playback without resetting the player. Use {@link #pause()} rather than this method if
* Stops playback without resetting the playlist. Use {@link #pause()} rather than this method if
* the intention is to pause playback.
*
* <p>Calling this method will cause the playback state to transition to {@link #STATE_IDLE}. The
* player instance can still be used, and {@link #release()} must still be called on the player if
* it's no longer required.
* <p>Calling this method will cause the playback state to transition to {@link #STATE_IDLE} and
* the player will release the loaded media and resources required for playback. The player
* instance can still be used by calling {@link #prepare()} again, and {@link #release()} must
* still be called on the player if it's no longer required.
*
* <p>Calling this method does not clear the playlist, reset the playback position or the playback
* error.

View file

@ -362,7 +362,6 @@ public final class Cue implements Bundleable {
* @param textSize See {@link #textSize}.
* @deprecated Use {@link Builder}.
*/
@SuppressWarnings("deprecation")
@Deprecated
public Cue(
CharSequence text,

View file

@ -262,7 +262,6 @@ public class DefaultLoadControl implements LoadControl {
private boolean isLoading;
/** Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class. */
@SuppressWarnings("deprecation")
public DefaultLoadControl() {
this(
new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE),

View file

@ -396,7 +396,6 @@ public class SimpleExoPlayer extends BasePlayer
/** @deprecated Use the {@link ExoPlayer.Builder}. */
@Deprecated
@SuppressWarnings("deprecation")
protected SimpleExoPlayer(
Context context,
RenderersFactory renderersFactory,
@ -428,7 +427,6 @@ public class SimpleExoPlayer extends BasePlayer
}
/** @param builder The {@link ExoPlayer.Builder} to obtain all construction parameters. */
@SuppressWarnings("deprecation")
/* package */ SimpleExoPlayer(ExoPlayer.Builder builder) {
constructorFinished = new ConditionVariable();
try {

View file

@ -753,6 +753,8 @@ public class DashManifestParser extends DefaultHandler
drmSchemeType,
drmSchemeDatas,
inbandEventStreams,
essentialProperties,
supplementalProperties,
Representation.REVISION_ID_DEFAULT);
}
@ -841,7 +843,10 @@ public class DashManifestParser extends DefaultHandler
formatBuilder.build(),
representationInfo.baseUrls,
representationInfo.segmentBase,
inbandEventStreams);
inbandEventStreams,
representationInfo.essentialProperties,
representationInfo.supplementalProperties,
/* cacheKey= */ null);
}
// SegmentBase, SegmentList and SegmentTemplate parsing.
@ -1910,6 +1915,8 @@ public class DashManifestParser extends DefaultHandler
public final ArrayList<SchemeData> drmSchemeDatas;
public final ArrayList<Descriptor> inbandEventStreams;
public final long revisionId;
public final List<Descriptor> essentialProperties;
public final List<Descriptor> supplementalProperties;
public RepresentationInfo(
Format format,
@ -1918,6 +1925,8 @@ public class DashManifestParser extends DefaultHandler
@Nullable String drmSchemeType,
ArrayList<SchemeData> drmSchemeDatas,
ArrayList<Descriptor> inbandEventStreams,
List<Descriptor> essentialProperties,
List<Descriptor> supplementalProperties,
long revisionId) {
this.format = format;
this.baseUrls = ImmutableList.copyOf(baseUrls);
@ -1925,6 +1934,8 @@ public class DashManifestParser extends DefaultHandler
this.drmSchemeType = drmSchemeType;
this.drmSchemeDatas = drmSchemeDatas;
this.inbandEventStreams = inbandEventStreams;
this.essentialProperties = essentialProperties;
this.supplementalProperties = supplementalProperties;
this.revisionId = revisionId;
}
}

View file

@ -50,6 +50,10 @@ public abstract class Representation {
public final long presentationTimeOffsetUs;
/** The in-band event streams in the representation. May be empty. */
public final List<Descriptor> inbandEventStreams;
/** Essential properties in the representation. May be empty. */
public final List<Descriptor> essentialProperties;
/** Supplemental properties in the adaptation set. May be empty. */
public final List<Descriptor> supplementalProperties;
private final RangedUri initializationUri;
@ -64,27 +68,15 @@ public abstract class Representation {
*/
public static Representation newInstance(
long revisionId, Format format, List<BaseUrl> baseUrls, SegmentBase segmentBase) {
return newInstance(revisionId, format, baseUrls, segmentBase, /* inbandEventStreams= */ null);
}
/**
* Constructs a new instance.
*
* @param revisionId Identifies the revision of the content.
* @param format The format of the representation.
* @param baseUrls The list of base URLs of the representation.
* @param segmentBase A segment base element for the representation.
* @param inbandEventStreams The in-band event streams in the representation. May be null.
* @return The constructed instance.
*/
public static Representation newInstance(
long revisionId,
Format format,
List<BaseUrl> baseUrls,
SegmentBase segmentBase,
@Nullable List<Descriptor> inbandEventStreams) {
return newInstance(
revisionId, format, baseUrls, segmentBase, inbandEventStreams, /* cacheKey= */ null);
revisionId,
format,
baseUrls,
segmentBase,
/* inbandEventStreams= */ null,
/* essentialProperties= */ ImmutableList.of(),
/* supplementalProperties= */ ImmutableList.of(),
/* cacheKey= */ null);
}
/**
@ -95,6 +87,8 @@ public abstract class Representation {
* @param baseUrls The list of base URLs of the representation.
* @param segmentBase A segment base element for the representation.
* @param inbandEventStreams The in-band event streams in the representation. May be null.
* @param essentialProperties Essential properties in the representation. May be empty.
* @param supplementalProperties Supplemental properties in the representation. May be empty.
* @param cacheKey An optional key to be returned from {@link #getCacheKey()}, or null. This
* parameter is ignored if {@code segmentBase} consists of multiple segments.
* @return The constructed instance.
@ -105,6 +99,8 @@ public abstract class Representation {
List<BaseUrl> baseUrls,
SegmentBase segmentBase,
@Nullable List<Descriptor> inbandEventStreams,
List<Descriptor> essentialProperties,
List<Descriptor> supplementalProperties,
@Nullable String cacheKey) {
if (segmentBase instanceof SingleSegmentBase) {
return new SingleSegmentRepresentation(
@ -113,11 +109,19 @@ public abstract class Representation {
baseUrls,
(SingleSegmentBase) segmentBase,
inbandEventStreams,
essentialProperties,
supplementalProperties,
cacheKey,
C.LENGTH_UNSET);
/* contentLength= */ C.LENGTH_UNSET);
} else if (segmentBase instanceof MultiSegmentBase) {
return new MultiSegmentRepresentation(
revisionId, format, baseUrls, (MultiSegmentBase) segmentBase, inbandEventStreams);
revisionId,
format,
baseUrls,
(MultiSegmentBase) segmentBase,
inbandEventStreams,
essentialProperties,
supplementalProperties);
} else {
throw new IllegalArgumentException(
"segmentBase must be of type SingleSegmentBase or " + "MultiSegmentBase");
@ -129,7 +133,9 @@ public abstract class Representation {
Format format,
List<BaseUrl> baseUrls,
SegmentBase segmentBase,
@Nullable List<Descriptor> inbandEventStreams) {
@Nullable List<Descriptor> inbandEventStreams,
List<Descriptor> essentialProperties,
List<Descriptor> supplementalProperties) {
checkArgument(!baseUrls.isEmpty());
this.revisionId = revisionId;
this.format = format;
@ -138,6 +144,8 @@ public abstract class Representation {
inbandEventStreams == null
? Collections.emptyList()
: Collections.unmodifiableList(inbandEventStreams);
this.essentialProperties = essentialProperties;
this.supplementalProperties = supplementalProperties;
initializationUri = segmentBase.getInitialization(this);
presentationTimeOffsetUs = segmentBase.getPresentationTimeOffsetUs();
}
@ -207,7 +215,15 @@ public abstract class Representation {
new SingleSegmentBase(rangedUri, 1, 0, indexStart, indexEnd - indexStart + 1);
List<BaseUrl> baseUrls = ImmutableList.of(new BaseUrl(uri));
return new SingleSegmentRepresentation(
revisionId, format, baseUrls, segmentBase, inbandEventStreams, cacheKey, contentLength);
revisionId,
format,
baseUrls,
segmentBase,
inbandEventStreams,
/* essentialProperties= */ ImmutableList.of(),
/* supplementalProperties= */ ImmutableList.of(),
cacheKey,
contentLength);
}
/**
@ -216,6 +232,8 @@ public abstract class Representation {
* @param baseUrls The base urls of the representation.
* @param segmentBase The segment base underlying the representation.
* @param inbandEventStreams The in-band event streams in the representation. May be null.
* @param essentialProperties Essential properties in the representation. May be empty.
* @param supplementalProperties Supplemental properties in the representation. May be empty.
* @param cacheKey An optional key to be returned from {@link #getCacheKey()}, or null.
* @param contentLength The content length, or {@link C#LENGTH_UNSET} if unknown.
*/
@ -225,9 +243,18 @@ public abstract class Representation {
List<BaseUrl> baseUrls,
SingleSegmentBase segmentBase,
@Nullable List<Descriptor> inbandEventStreams,
List<Descriptor> essentialProperties,
List<Descriptor> supplementalProperties,
@Nullable String cacheKey,
long contentLength) {
super(revisionId, format, baseUrls, segmentBase, inbandEventStreams);
super(
revisionId,
format,
baseUrls,
segmentBase,
inbandEventStreams,
essentialProperties,
supplementalProperties);
this.uri = Uri.parse(baseUrls.get(0).url);
this.indexUri = segmentBase.getIndex();
this.cacheKey = cacheKey;
@ -271,14 +298,25 @@ public abstract class Representation {
* @param baseUrls The base URLs of the representation.
* @param segmentBase The segment base underlying the representation.
* @param inbandEventStreams The in-band event streams in the representation. May be null.
* @param essentialProperties Essential properties in the representation. May be empty.
* @param supplementalProperties Supplemental properties in the representation. May be empty.
*/
public MultiSegmentRepresentation(
long revisionId,
Format format,
List<BaseUrl> baseUrls,
MultiSegmentBase segmentBase,
@Nullable List<Descriptor> inbandEventStreams) {
super(revisionId, format, baseUrls, segmentBase, inbandEventStreams);
@Nullable List<Descriptor> inbandEventStreams,
List<Descriptor> essentialProperties,
List<Descriptor> supplementalProperties) {
super(
revisionId,
format,
baseUrls,
segmentBase,
inbandEventStreams,
essentialProperties,
supplementalProperties);
this.segmentBase = segmentBase;
}

View file

@ -79,6 +79,8 @@ public final class DashUtilTest {
baseUrls,
new SingleSegmentBase(),
/* inbandEventStreams= */ null,
/* essentialProperties= */ ImmutableList.of(),
/* supplementalProperties= */ ImmutableList.of(),
/* cacheKey= */ null,
/* contentLength= */ 1);
RangedUri rangedUri = new RangedUri("path/to/resource", /* start= */ 0, /* length= */ 1);
@ -99,6 +101,8 @@ public final class DashUtilTest {
baseUrls,
new SingleSegmentBase(),
/* inbandEventStreams= */ null,
/* essentialProperties= */ ImmutableList.of(),
/* supplementalProperties= */ ImmutableList.of(),
"cacheKey",
/* contentLength= */ 1);
RangedUri rangedUri = new RangedUri("path/to/resource", /* start= */ 0, /* length= */ 1);

View file

@ -24,6 +24,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.metadata.emsg.EventMessage;
import com.google.android.exoplayer2.source.dash.manifest.Representation.MultiSegmentRepresentation;
import com.google.android.exoplayer2.source.dash.manifest.Representation.SingleSegmentRepresentation;
import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SegmentTimelineElement;
import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.util.MimeTypes;
@ -53,6 +55,8 @@ public class DashManifestParserTest {
private static final String SAMPLE_MPD_ASSET_IDENTIFIER = "media/mpd/sample_mpd_asset_identifier";
private static final String SAMPLE_MPD_TEXT = "media/mpd/sample_mpd_text";
private static final String SAMPLE_MPD_TRICK_PLAY = "media/mpd/sample_mpd_trick_play";
private static final String SAMPLE_MPD_ESSENTIAL_SUPPLEMENTAL_PROPERTIES =
"media/mpd/sample_mpd_essential_supplemental_properties";
private static final String SAMPLE_MPD_AVAILABILITY_TIME_OFFSET_BASE_URL =
"media/mpd/sample_mpd_availabilityTimeOffset_baseUrl";
private static final String SAMPLE_MPD_MULTIPLE_BASE_URLS =
@ -504,6 +508,74 @@ public class DashManifestParserTest {
assertThat(assetIdentifier.id).isEqualTo("uniqueId");
}
@Test
public void parseEssentialAndSupplementalProperties() throws IOException {
DashManifestParser parser = new DashManifestParser();
DashManifest manifest =
parser.parse(
Uri.parse("https://example.com/test.mpd"),
TestUtil.getInputStream(
ApplicationProvider.getApplicationContext(),
SAMPLE_MPD_ESSENTIAL_SUPPLEMENTAL_PROPERTIES));
// Verify test setup.
assertThat(manifest.getPeriodCount()).isEqualTo(1);
assertThat(manifest.getPeriod(0).adaptationSets).hasSize(1);
AdaptationSet adaptationSet = manifest.getPeriod(0).adaptationSets.get(0);
assertThat(adaptationSet.representations).hasSize(2);
Representation representation0 = adaptationSet.representations.get(0);
Representation representation1 = adaptationSet.representations.get(1);
assertThat(representation0).isInstanceOf(SingleSegmentRepresentation.class);
assertThat(representation1).isInstanceOf(MultiSegmentRepresentation.class);
// Verify parsed properties.
assertThat(adaptationSet.essentialProperties).hasSize(1);
assertThat(adaptationSet.essentialProperties.get(0).schemeIdUri)
.isEqualTo("urn:mpeg:dash:essential-scheme:2050");
assertThat(adaptationSet.essentialProperties.get(0).value).isEqualTo("adaptationEssential");
assertThat(adaptationSet.supplementalProperties).hasSize(1);
assertThat(adaptationSet.supplementalProperties.get(0).schemeIdUri)
.isEqualTo("urn:mpeg:dash:supplemental-scheme:2050");
assertThat(adaptationSet.supplementalProperties.get(0).value)
.isEqualTo("adaptationSupplemental");
assertThat(representation0.essentialProperties).hasSize(2);
assertThat(representation0.essentialProperties.get(0).schemeIdUri)
.isEqualTo("urn:mpeg:dash:essential-scheme:2050");
assertThat(representation0.essentialProperties.get(0).value).isEqualTo("adaptationEssential");
assertThat(representation0.essentialProperties.get(1).schemeIdUri)
.isEqualTo("urn:mpeg:dash:essential-scheme:2050");
assertThat(representation0.essentialProperties.get(1).value)
.isEqualTo("representationEssential");
assertThat(representation0.supplementalProperties).hasSize(2);
assertThat(representation0.supplementalProperties.get(0).schemeIdUri)
.isEqualTo("urn:mpeg:dash:supplemental-scheme:2050");
assertThat(representation0.supplementalProperties.get(0).value)
.isEqualTo("adaptationSupplemental");
assertThat(representation0.supplementalProperties.get(1).schemeIdUri)
.isEqualTo("urn:mpeg:dash:supplemental-scheme:2050");
assertThat(representation0.supplementalProperties.get(1).value)
.isEqualTo("representationSupplemental");
assertThat(representation1.essentialProperties).hasSize(2);
assertThat(representation0.essentialProperties.get(0).schemeIdUri)
.isEqualTo("urn:mpeg:dash:essential-scheme:2050");
assertThat(representation0.essentialProperties.get(0).value).isEqualTo("adaptationEssential");
assertThat(representation1.essentialProperties.get(1).schemeIdUri)
.isEqualTo("urn:mpeg:dash:essential-scheme:2050");
assertThat(representation1.essentialProperties.get(1).value)
.isEqualTo("representationEssential");
assertThat(representation1.supplementalProperties).hasSize(2);
assertThat(representation0.supplementalProperties.get(0).schemeIdUri)
.isEqualTo("urn:mpeg:dash:supplemental-scheme:2050");
assertThat(representation0.supplementalProperties.get(0).value)
.isEqualTo("adaptationSupplemental");
assertThat(representation1.supplementalProperties.get(1).schemeIdUri)
.isEqualTo("urn:mpeg:dash:supplemental-scheme:2050");
assertThat(representation1.supplementalProperties.get(1).value)
.isEqualTo("representationSupplemental");
}
@Test
public void availabilityTimeOffset_staticManifest_setToTimeUnset() throws IOException {
DashManifestParser parser = new DashManifestParser();

View file

@ -12,15 +12,27 @@
// See the License for the specific language governing permissions and
// limitations under the License.
apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle"
android {
defaultConfig {
// The following argument makes the Android Test Orchestrator run its
// "pm clear" command after each test invocation. This command ensures
// that the app's state is completely cleared between tests.
testInstrumentationRunnerArguments clearPackageData: 'true'
multiDexEnabled true
}
buildTypes {
debug {
testCoverageEnabled = true
}
}
sourceSets.test.assets.srcDir '../../testdata/src/test/assets/'
sourceSets {
androidTest.assets.srcDir '../test_data/src/test/assets/' //copybara:media3-only
androidTest.assets.srcDir '../../testdata/src/test/assets/'
test.assets.srcDir '../../testdata/src/test/assets/'
}
}
dependencies {
@ -33,6 +45,10 @@ dependencies {
testImplementation project(modulePrefix + 'testutils')
testImplementation project(modulePrefix + 'testdata')
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
testImplementation 'com.google.truth:truth:' + truthVersion
androidTestImplementation 'junit:junit:' + junitVersion
androidTestImplementation 'androidx.test:runner:' + androidxTestRunnerVersion
androidTestImplementation project(modulePrefix + 'testutils')
}
ext {

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2016 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.google.android.exoplayer2.transformer">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-sdk/>
<application
android:allowBackup="false"
tools:ignore="MissingApplicationIcon,HardcodedDebugMode"
android:usesCleartextTraffic="true"/>
<instrumentation
android:targetPackage="com.google.android.exoplayer2.transformer"
android:name="androidx.test.runner.AndroidJUnitRunner"/>
</manifest>

View file

@ -0,0 +1,91 @@
/*
* Copyright 2021 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.exoplayer2.transformer;
import android.content.Context;
import android.net.Uri;
import androidx.annotation.Nullable;
import androidx.test.platform.app.InstrumentationRegistry;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.util.Assertions;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import org.checkerframework.checker.nullness.compatqual.NullableType;
/** Utility methods for instrumentation tests. */
/* package */ final class AndroidTestUtil {
public static final String MP4_ASSET_URI = "asset:///media/mp4/sample.mp4";
public static final String SEF_ASSET_URI = "asset:///media/mp4/sample_sef_slow_motion.mp4";
/** Transforms the {@code uriString} with the {@link Transformer}. */
public static void runTransformer(Context context, Transformer transformer, String uriString)
throws Exception {
AtomicReference<@NullableType Exception> exceptionReference = new AtomicReference<>();
CountDownLatch countDownLatch = new CountDownLatch(1);
Transformer testTransformer =
transformer
.buildUpon()
.setListener(
new Transformer.Listener() {
@Override
public void onTransformationCompleted(MediaItem inputMediaItem) {
countDownLatch.countDown();
}
@Override
public void onTransformationError(MediaItem inputMediaItem, Exception exception) {
exceptionReference.set(exception);
countDownLatch.countDown();
}
})
.build();
Uri uri = Uri.parse(uriString);
File externalCacheFile = createExternalCacheFile(uri, context);
try {
InstrumentationRegistry.getInstrumentation()
.runOnMainSync(
() -> {
try {
testTransformer.startTransformation(
MediaItem.fromUri(uri), externalCacheFile.getAbsolutePath());
} catch (IOException e) {
exceptionReference.set(e);
}
});
countDownLatch.await();
@Nullable Exception exception = exceptionReference.get();
if (exception != null) {
throw exception;
}
} finally {
externalCacheFile.delete();
}
}
private static File createExternalCacheFile(Uri uri, Context context) throws IOException {
File file = new File(context.getExternalCacheDir(), "transformer-" + uri.hashCode());
Assertions.checkState(
!file.exists() || file.delete(), "Could not delete the previous transformer output file");
Assertions.checkState(file.createNewFile(), "Could not create the transformer output file");
return file;
}
private AndroidTestUtil() {}
}

View file

@ -0,0 +1,37 @@
/*
* Copyright 2021 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.exoplayer2.transformer;
import static com.google.android.exoplayer2.transformer.AndroidTestUtil.MP4_ASSET_URI;
import static com.google.android.exoplayer2.transformer.AndroidTestUtil.runTransformer;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
/** {@link Transformer} instrumentation test for removing audio. */
@RunWith(AndroidJUnit4.class)
public class RemoveAudioTransformationTest {
@Test
public void removeAudioTransform() throws Exception {
Context context = ApplicationProvider.getApplicationContext();
Transformer transformer =
new Transformer.Builder().setContext(context).setRemoveAudio(true).build();
runTransformer(context, transformer, MP4_ASSET_URI);
}
}

View file

@ -0,0 +1,37 @@
/*
* Copyright 2021 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.exoplayer2.transformer;
import static com.google.android.exoplayer2.transformer.AndroidTestUtil.MP4_ASSET_URI;
import static com.google.android.exoplayer2.transformer.AndroidTestUtil.runTransformer;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
/** {@link Transformer} instrumentation test for removing video. */
@RunWith(AndroidJUnit4.class)
public class RemoveVideoTransformationTest {
@Test
public void removeVideoTransform() throws Exception {
Context context = ApplicationProvider.getApplicationContext();
Transformer transformer =
new Transformer.Builder().setContext(context).setRemoveVideo(true).build();
runTransformer(context, transformer, MP4_ASSET_URI);
}
}

View file

@ -0,0 +1,37 @@
/*
* Copyright 2021 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.exoplayer2.transformer;
import static com.google.android.exoplayer2.transformer.AndroidTestUtil.SEF_ASSET_URI;
import static com.google.android.exoplayer2.transformer.AndroidTestUtil.runTransformer;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
/** {@link Transformer} instrumentation test for SEF. */
@RunWith(AndroidJUnit4.class)
public class SefTransformationTest {
@Test
public void sefTransform() throws Exception {
Context context = ApplicationProvider.getApplicationContext();
Transformer transformer =
new Transformer.Builder().setContext(context).setFlattenForSlowMotion(true).build();
runTransformer(context, transformer, SEF_ASSET_URI);
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright 2021 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.exoplayer2.transformer;
import static com.google.android.exoplayer2.transformer.AndroidTestUtil.MP4_ASSET_URI;
import static com.google.android.exoplayer2.transformer.AndroidTestUtil.runTransformer;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
/** {@link Transformer} instrumentation test. */
@RunWith(AndroidJUnit4.class)
public class TransformationTest {
@Test
public void transform() throws Exception {
Context context = ApplicationProvider.getApplicationContext();
Transformer transformer = new Transformer.Builder().setContext(context).build();
runTransformer(context, transformer, MP4_ASSET_URI);
}
}

View file

@ -0,0 +1,180 @@
/*
* Copyright 2021 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.exoplayer2.transformer;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkState;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.opengl.EGL14;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLExt;
import android.opengl.EGLSurface;
import android.opengl.GLES20;
import android.view.Surface;
import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.util.GlUtil;
import java.io.IOException;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** Applies OpenGL transformations to video frames. */
@RequiresApi(18)
/* package */ final class OpenGlFrameEditor {
static {
GlUtil.glAssertionsEnabled = true;
}
public static OpenGlFrameEditor create(
Context context, Format inputFormat, Surface outputSurface) {
EGLDisplay eglDisplay = GlUtil.createEglDisplay();
EGLContext eglContext;
try {
eglContext = GlUtil.createEglContext(eglDisplay);
} catch (GlUtil.UnsupportedEglVersionException e) {
throw new IllegalStateException("EGL version is unsupported", e);
}
EGLSurface eglSurface = GlUtil.getEglSurface(eglDisplay, outputSurface);
GlUtil.focusSurface(eglDisplay, eglContext, eglSurface, inputFormat.width, inputFormat.height);
int textureId = GlUtil.createExternalTexture();
GlUtil.Program copyProgram;
try {
copyProgram = new GlUtil.Program(context, VERTEX_SHADER_FILE_PATH, FRAGMENT_SHADER_FILE_PATH);
} catch (IOException e) {
throw new IllegalStateException(e);
}
copyProgram.use();
GlUtil.Attribute[] copyAttributes = copyProgram.getAttributes();
checkState(
copyAttributes.length == EXPECTED_NUMBER_OF_ATTRIBUTES,
"Expected program to have " + EXPECTED_NUMBER_OF_ATTRIBUTES + " vertex attributes.");
for (GlUtil.Attribute copyAttribute : copyAttributes) {
if (copyAttribute.name.equals("a_position")) {
copyAttribute.setBuffer(
new float[] {
-1.0f, -1.0f, 0.0f, 1.0f,
1.0f, -1.0f, 0.0f, 1.0f,
-1.0f, 1.0f, 0.0f, 1.0f,
1.0f, 1.0f, 0.0f, 1.0f,
},
/* size= */ 4);
} else if (copyAttribute.name.equals("a_texcoord")) {
copyAttribute.setBuffer(
new float[] {
0.0f, 0.0f, 0.0f, 1.0f,
1.0f, 0.0f, 0.0f, 1.0f,
0.0f, 1.0f, 0.0f, 1.0f,
1.0f, 1.0f, 0.0f, 1.0f,
},
/* size= */ 4);
} else {
throw new IllegalStateException("Unexpected attribute name.");
}
copyAttribute.bind();
}
GlUtil.Uniform[] copyUniforms = copyProgram.getUniforms();
checkState(
copyUniforms.length == EXPECTED_NUMBER_OF_UNIFORMS,
"Expected program to have " + EXPECTED_NUMBER_OF_UNIFORMS + " uniforms.");
GlUtil.@MonotonicNonNull Uniform textureTransformUniform = null;
for (GlUtil.Uniform copyUniform : copyUniforms) {
if (copyUniform.name.equals("tex_sampler")) {
copyUniform.setSamplerTexId(textureId, 0);
copyUniform.bind();
} else if (copyUniform.name.equals("tex_transform")) {
textureTransformUniform = copyUniform;
} else {
throw new IllegalStateException("Unexpected uniform name.");
}
}
return new OpenGlFrameEditor(
eglDisplay, eglContext, eglSurface, textureId, checkNotNull(textureTransformUniform));
}
// Predefined shader values.
private static final String VERTEX_SHADER_FILE_PATH = "shaders/blit_vertex_shader.glsl";
private static final String FRAGMENT_SHADER_FILE_PATH =
"shaders/copy_external_fragment_shader.glsl";
private static final int EXPECTED_NUMBER_OF_ATTRIBUTES = 2;
private static final int EXPECTED_NUMBER_OF_UNIFORMS = 2;
private final float[] textureTransformMatrix;
private final EGLDisplay eglDisplay;
private final EGLContext eglContext;
private final EGLSurface eglSurface;
private final int textureId;
private final SurfaceTexture inputSurfaceTexture;
private final Surface inputSurface;
private final GlUtil.Uniform textureTransformUniform;
private volatile boolean hasInputData;
private OpenGlFrameEditor(
EGLDisplay eglDisplay,
EGLContext eglContext,
EGLSurface eglSurface,
int textureId,
GlUtil.Uniform textureTransformUniform) {
this.eglDisplay = eglDisplay;
this.eglContext = eglContext;
this.eglSurface = eglSurface;
this.textureId = textureId;
this.textureTransformUniform = textureTransformUniform;
textureTransformMatrix = new float[16];
inputSurfaceTexture = new SurfaceTexture(textureId);
inputSurfaceTexture.setOnFrameAvailableListener(surfaceTexture -> hasInputData = true);
inputSurface = new Surface(inputSurfaceTexture);
}
/** Releases all resources. */
public void release() {
GlUtil.destroyEglContext(eglDisplay, eglContext);
GlUtil.deleteTexture(textureId);
inputSurfaceTexture.release();
inputSurface.release();
}
/** Informs the editor that there is new input data available for it to process asynchronously. */
public void processData() {
inputSurfaceTexture.updateTexImage();
inputSurfaceTexture.getTransformMatrix(textureTransformMatrix);
textureTransformUniform.setFloats(textureTransformMatrix);
textureTransformUniform.bind();
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
long surfaceTextureTimestampNs = inputSurfaceTexture.getTimestamp();
EGLExt.eglPresentationTimeANDROID(eglDisplay, eglSurface, surfaceTextureTimestampNs);
EGL14.eglSwapBuffers(eglDisplay, eglSurface);
hasInputData = false;
}
/**
* Returns the input {@link Surface} after configuring the editor if it has not previously been
* configured.
*/
public Surface getInputSurface() {
return inputSurface;
}
public boolean hasInputData() {
return hasInputData;
}
}

View file

@ -17,19 +17,9 @@
package com.google.android.exoplayer2.transformer;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkState;
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.media.MediaCodec;
import android.opengl.EGL14;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLExt;
import android.opengl.EGLSurface;
import android.opengl.GLES20;
import android.view.Surface;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.C;
@ -37,13 +27,8 @@ import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.PlaybackException;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.util.GlUtil;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/**
* Pipeline to decode video samples, apply transformations on the raw samples, and re-encode them.
@ -51,52 +36,24 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@RequiresApi(18)
/* package */ final class VideoSamplePipeline implements SamplePipeline {
static {
GlUtil.glAssertionsEnabled = true;
}
private static final String TAG = "VideoSamplePipeline";
// Predefined shader values.
private static final String VERTEX_SHADER_FILE_PATH = "shaders/blit_vertex_shader.glsl";
private static final String FRAGMENT_SHADER_FILE_PATH =
"shaders/copy_external_fragment_shader.glsl";
private static final int EXPECTED_NUMBER_OF_ATTRIBUTES = 2;
private static final int EXPECTED_NUMBER_OF_UNIFORMS = 2;
private final Context context;
private final int rendererIndex;
private final MediaCodecAdapterWrapper encoder;
private final DecoderInputBuffer encoderOutputBuffer;
private final DecoderInputBuffer decoderInputBuffer;
private final float[] decoderTextureTransformMatrix;
private final Format decoderInputFormat;
private final MediaCodecAdapterWrapper decoder;
private @MonotonicNonNull EGLDisplay eglDisplay;
private @MonotonicNonNull EGLContext eglContext;
private @MonotonicNonNull EGLSurface eglSurface;
private final OpenGlFrameEditor openGlFrameEditor;
private int decoderTextureId;
private @MonotonicNonNull SurfaceTexture decoderSurfaceTexture;
private @MonotonicNonNull Surface decoderSurface;
private @MonotonicNonNull MediaCodecAdapterWrapper decoder;
private volatile boolean isDecoderSurfacePopulated;
private boolean waitingForPopulatedDecoderSurface;
private GlUtil.@MonotonicNonNull Uniform decoderTextureTransformUniform;
public VideoSamplePipeline(
Context context, Format decoderInputFormat, Transformation transformation, int rendererIndex)
throws ExoPlaybackException {
this.decoderInputFormat = decoderInputFormat;
this.rendererIndex = rendererIndex;
this.context = context;
decoderInputBuffer =
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
decoderTextureTransformMatrix = new float[16];
decoderTextureId = GlUtil.TEXTURE_ID_UNSET;
encoderOutputBuffer =
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
@ -114,34 +71,57 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
ImmutableMap.of());
} catch (IOException e) {
// TODO (internal b/184262323): Assign an adequate error code.
throw ExoPlaybackException.createForRenderer(
e,
TAG,
rendererIndex,
decoderInputFormat,
/* rendererFormatSupport= */ C.FORMAT_HANDLED,
/* isRecoverable= */ false,
PlaybackException.ERROR_CODE_UNSPECIFIED);
throw createRendererException(
e, rendererIndex, decoderInputFormat, PlaybackException.ERROR_CODE_UNSPECIFIED);
}
openGlFrameEditor =
OpenGlFrameEditor.create(
context,
decoderInputFormat,
/* outputSurface= */ checkNotNull(encoder.getInputSurface()));
try {
decoder =
MediaCodecAdapterWrapper.createForVideoDecoding(
decoderInputFormat, openGlFrameEditor.getInputSurface());
} catch (IOException e) {
throw createRendererException(
e, rendererIndex, decoderInputFormat, PlaybackException.ERROR_CODE_DECODER_INIT_FAILED);
}
}
@Override
public boolean processData() throws ExoPlaybackException {
ensureOpenGlConfigured();
return !ensureDecoderConfigured() || feedEncoderFromDecoder();
public boolean processData() {
if (decoder.isEnded()) {
return false;
}
if (!openGlFrameEditor.hasInputData()) {
if (!waitingForPopulatedDecoderSurface) {
if (decoder.getOutputBufferInfo() != null) {
decoder.releaseOutputBuffer(/* render= */ true);
waitingForPopulatedDecoderSurface = true;
}
if (decoder.isEnded()) {
encoder.signalEndOfInputStream();
}
}
return false;
}
waitingForPopulatedDecoderSurface = false;
openGlFrameEditor.processData();
return true;
}
@Override
@Nullable
public DecoderInputBuffer dequeueInputBuffer() {
return decoder != null && decoder.maybeDequeueInputBuffer(decoderInputBuffer)
? decoderInputBuffer
: null;
return decoder.maybeDequeueInputBuffer(decoderInputBuffer) ? decoderInputBuffer : null;
}
@Override
public void queueInputBuffer() {
checkStateNotNull(decoder).queueInputBuffer(decoderInputBuffer);
decoder.queueInputBuffer(decoderInputBuffer);
}
@Override
@ -175,154 +155,13 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@Override
public void release() {
GlUtil.destroyEglContext(eglDisplay, eglContext);
if (decoderTextureId != GlUtil.TEXTURE_ID_UNSET) {
GlUtil.deleteTexture(decoderTextureId);
}
if (decoderSurfaceTexture != null) {
decoderSurfaceTexture.release();
}
if (decoderSurface != null) {
decoderSurface.release();
}
if (decoder != null) {
decoder.release();
}
openGlFrameEditor.release();
decoder.release();
encoder.release();
}
@EnsuresNonNull({"eglDisplay", "eglContext", "eglSurface", "decoderTextureTransformUniform"})
private void ensureOpenGlConfigured() {
if (eglDisplay != null
&& eglContext != null
&& eglSurface != null
&& decoderTextureTransformUniform != null) {
return;
}
eglDisplay = GlUtil.createEglDisplay();
try {
eglContext = GlUtil.createEglContext(eglDisplay);
} catch (GlUtil.UnsupportedEglVersionException e) {
throw new IllegalStateException("EGL version is unsupported", e);
}
eglSurface = GlUtil.getEglSurface(eglDisplay, checkNotNull(encoder.getInputSurface()));
GlUtil.focusSurface(
eglDisplay, eglContext, eglSurface, decoderInputFormat.width, decoderInputFormat.height);
decoderTextureId = GlUtil.createExternalTexture();
GlUtil.Program copyProgram;
try {
copyProgram = new GlUtil.Program(context, VERTEX_SHADER_FILE_PATH, FRAGMENT_SHADER_FILE_PATH);
} catch (IOException e) {
throw new IllegalStateException(e);
}
copyProgram.use();
GlUtil.Attribute[] copyAttributes = copyProgram.getAttributes();
checkState(
copyAttributes.length == EXPECTED_NUMBER_OF_ATTRIBUTES,
"Expected program to have " + EXPECTED_NUMBER_OF_ATTRIBUTES + " vertex attributes.");
for (GlUtil.Attribute copyAttribute : copyAttributes) {
if (copyAttribute.name.equals("a_position")) {
copyAttribute.setBuffer(
new float[] {
-1.0f, -1.0f, 0.0f, 1.0f,
1.0f, -1.0f, 0.0f, 1.0f,
-1.0f, 1.0f, 0.0f, 1.0f,
1.0f, 1.0f, 0.0f, 1.0f,
},
/* size= */ 4);
} else if (copyAttribute.name.equals("a_texcoord")) {
copyAttribute.setBuffer(
new float[] {
0.0f, 0.0f, 0.0f, 1.0f,
1.0f, 0.0f, 0.0f, 1.0f,
0.0f, 1.0f, 0.0f, 1.0f,
1.0f, 1.0f, 0.0f, 1.0f,
},
/* size= */ 4);
} else {
throw new IllegalStateException("Unexpected attribute name.");
}
copyAttribute.bind();
}
GlUtil.Uniform[] copyUniforms = copyProgram.getUniforms();
checkState(
copyUniforms.length == EXPECTED_NUMBER_OF_UNIFORMS,
"Expected program to have " + EXPECTED_NUMBER_OF_UNIFORMS + " uniforms.");
for (GlUtil.Uniform copyUniform : copyUniforms) {
if (copyUniform.name.equals("tex_sampler")) {
copyUniform.setSamplerTexId(decoderTextureId, 0);
copyUniform.bind();
} else if (copyUniform.name.equals("tex_transform")) {
decoderTextureTransformUniform = copyUniform;
} else {
throw new IllegalStateException("Unexpected uniform name.");
}
}
checkNotNull(decoderTextureTransformUniform);
}
@EnsuresNonNullIf(
expression = {"decoder", "decoderSurfaceTexture"},
result = true)
private boolean ensureDecoderConfigured() throws ExoPlaybackException {
if (decoder != null && decoderSurfaceTexture != null) {
return true;
}
checkState(decoderTextureId != GlUtil.TEXTURE_ID_UNSET);
decoderSurfaceTexture = new SurfaceTexture(decoderTextureId);
decoderSurfaceTexture.setOnFrameAvailableListener(
surfaceTexture -> isDecoderSurfacePopulated = true);
decoderSurface = new Surface(decoderSurfaceTexture);
try {
decoder = MediaCodecAdapterWrapper.createForVideoDecoding(decoderInputFormat, decoderSurface);
} catch (IOException e) {
throw createRendererException(e, PlaybackException.ERROR_CODE_DECODER_INIT_FAILED);
}
return true;
}
@RequiresNonNull({
"decoder",
"decoderSurfaceTexture",
"decoderTextureTransformUniform",
"eglDisplay",
"eglSurface"
})
private boolean feedEncoderFromDecoder() {
if (decoder.isEnded()) {
return false;
}
if (!isDecoderSurfacePopulated) {
if (!waitingForPopulatedDecoderSurface) {
if (decoder.getOutputBufferInfo() != null) {
decoder.releaseOutputBuffer(/* render= */ true);
waitingForPopulatedDecoderSurface = true;
}
if (decoder.isEnded()) {
encoder.signalEndOfInputStream();
}
}
return false;
}
waitingForPopulatedDecoderSurface = false;
decoderSurfaceTexture.updateTexImage();
decoderSurfaceTexture.getTransformMatrix(decoderTextureTransformMatrix);
decoderTextureTransformUniform.setFloats(decoderTextureTransformMatrix);
decoderTextureTransformUniform.bind();
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
long decoderSurfaceTextureTimestampNs = decoderSurfaceTexture.getTimestamp();
EGLExt.eglPresentationTimeANDROID(eglDisplay, eglSurface, decoderSurfaceTextureTimestampNs);
EGL14.eglSwapBuffers(eglDisplay, eglSurface);
isDecoderSurfacePopulated = false;
return true;
}
private ExoPlaybackException createRendererException(Throwable cause, int errorCode) {
private static ExoPlaybackException createRendererException(
Throwable cause, int rendererIndex, Format decoderInputFormat, int errorCode) {
return ExoPlaybackException.createForRenderer(
cause,
TAG,

View file

@ -1222,7 +1222,6 @@ public class PlayerControlView extends FrameLayout {
}
}
@SuppressWarnings("deprecation")
private void dispatchPlay(Player player) {
@State int state = player.getPlaybackState();
if (state == Player.STATE_IDLE) {

View file

@ -2165,7 +2165,9 @@ public class StyledPlayerControlView extends FrameLayout {
TrackSelectionParameters trackSelectionParameters =
player.getTrackSelectionParameters();
TrackSelectionOverrides overrides =
new TrackSelectionOverrides.Builder()
trackSelectionParameters
.trackSelectionOverrides
.buildUpon()
.setOverrideForType(
new TrackSelectionOverride(
track.trackGroup, ImmutableList.of(track.trackIndex)))

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD availabilityStartTime="2014-06-19T23:07:42" type="dynamic">
<Period start="PT7462826.784S" id="0">
<AdaptationSet id="0" mimeType="video/mp4">
<SupplementalProperty
schemeIdUri="urn:mpeg:dash:supplemental-scheme:2050"
value="adaptationSupplemental"/>
<EssentialProperty
schemeIdUri="urn:mpeg:dash:essential-scheme:2050"
value="adaptationEssential"/>
<Representation id="singleSegment">
<SegmentBase/>
<SupplementalProperty
schemeIdUri="urn:mpeg:dash:supplemental-scheme:2050"
value="representationSupplemental"/>
<EssentialProperty
schemeIdUri="urn:mpeg:dash:essential-scheme:2050"
value="representationEssential"/>
</Representation>
<Representation id="multiSegment">
<SegmentList/>
<SupplementalProperty
schemeIdUri="urn:mpeg:dash:supplemental-scheme:2050"
value="representationSupplemental"/>
<EssentialProperty
schemeIdUri="urn:mpeg:dash:essential-scheme:2050"
value="representationEssential"/>
</Representation>
</AdaptationSet>
</Period>
</MPD>