mirror of
https://github.com/samsonjs/media.git
synced 2026-03-26 09:35:47 +00:00
Merge branch 'dev-v2' into rtsp-socket-factory
This commit is contained in:
commit
cdcf57374a
29 changed files with 754 additions and 277 deletions
|
|
@ -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)).
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ##
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<>();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
37
library/transformer/src/androidTest/AndroidManifest.xml
Normal file
37
library/transformer/src/androidTest/AndroidManifest.xml
Normal 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>
|
||||
|
|
@ -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() {}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
|
|
|||
31
testdata/src/test/assets/media/mpd/sample_mpd_essential_supplemental_properties
vendored
Normal file
31
testdata/src/test/assets/media/mpd/sample_mpd_essential_supplemental_properties
vendored
Normal 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>
|
||||
Loading…
Reference in a new issue