Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
SDK
/
exoplayer
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
4b1e0fa9
authored
Feb 13, 2021
by
Oliver Woodman
Committed by
GitHub
Feb 13, 2021
Browse files
Options
_('Browse Files')
Download
Plain Diff
Merge pull request #8582 from google/dev-v2-r2.13.1
r2.13.1
parents
b1000940
5807d2e0
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
268 additions
and
43 deletions
RELEASENOTES.md
constants.gradle
extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java
extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java
library/common/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java
library/common/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java
library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java
library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java
library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java
library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerProvider.java
library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java
library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java
library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioSinkTest.java
library/core/src/test/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerTest.java
library/core/src/test/java/com/google/android/exoplayer2/source/DefaultDrmSessionManagerProviderTest.java
library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java
library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Period.java
library/transformer/src/main/java/com/google/android/exoplayer2/transformer/SpeedProvider.java
RELEASENOTES.md
View file @
4b1e0fa9
# Release notes
# Release notes
### 2.13.1 (2021-02-12)
*
Live streaming:
*
Fix playback issue for HLS live streams without program date time
information (
[
#8560
](
https://github.com/google/ExoPlayer/issues/8560
)
).
*
Fix playback issue for multi-period DASH live streams
(
[
#8537
](
https://github.com/google/ExoPlayer/issues/8537
)
).
*
Fix playback failures when playing live streams with video tunneling
enabled (
[
#8570
](
https://github.com/google/ExoPlayer/issues/8570
)
).
*
IMA extension:
*
Fix handling of repeated ad loads, to avoid ads being discarded if the
user seeks away and then back to a preloaded postroll (for example).
*
Fix a bug where an assertion would fail if the player started to buffer
an ad media period before the ad URI was known then an ad state update
arrived that didn't set the ad URI.
*
Add
`ImaAdsLoader.focusSkipButton`
to allow apps to request that the
skip button should receive UI focus, if shown
(
[
#8565
](
https://github.com/google/ExoPlayer/issues/8565
)
).
*
DRM:
*
Re-use the previous
`DrmSessionManager`
instance when playing a playlist
(if possible)
(
[
#8523
](
https://github.com/google/ExoPlayer/issues/8523
)
).
*
Propagate DRM configuration when creating media sources for ad content
(
[
#8568
](
https://github.com/google/ExoPlayer/issues/8568
)
).
*
Only release 'keepalive' references to
`DrmSession`
in
`DefaultDrmSessionManager#release()`
if keepalive is enabled
(
[
#8576
](
https://github.com/google/ExoPlayer/issues/8576
)
).
### 2.13.0 (2021-02-04)
### 2.13.0 (2021-02-04)
*
Core library:
*
Core library:
...
...
constants.gradle
View file @
4b1e0fa9
...
@@ -13,8 +13,8 @@
...
@@ -13,8 +13,8 @@
// limitations under the License.
// limitations under the License.
project
.
ext
{
project
.
ext
{
// ExoPlayer version and version code.
// ExoPlayer version and version code.
releaseVersion
=
'2.13.
0
'
releaseVersion
=
'2.13.
1
'
releaseVersionCode
=
201300
0
releaseVersionCode
=
201300
1
minSdkVersion
=
16
minSdkVersion
=
16
appTargetSdkVersion
=
29
appTargetSdkVersion
=
29
targetSdkVersion
=
28
// TODO: Bump once b/143232359 is resolved. Also fix TODOs in UtilTest.
targetSdkVersion
=
28
// TODO: Bump once b/143232359 is resolved. Also fix TODOs in UtilTest.
...
...
extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java
View file @
4b1e0fa9
...
@@ -282,6 +282,16 @@ import java.util.Map;
...
@@ -282,6 +282,16 @@ import java.util.Map;
}
}
/**
/**
* Moves UI focus to the skip button (or other interactive elements), if currently shown. See
* {@link AdsManager#focus()}.
*/
public
void
focusSkipButton
()
{
if
(
adsManager
!=
null
)
{
adsManager
.
focus
();
}
}
/**
* Starts passing events from this instance (including any pending ad playback state) and
* Starts passing events from this instance (including any pending ad playback state) and
* registers obstructions.
* registers obstructions.
*/
*/
...
@@ -879,7 +889,8 @@ import java.util.Map;
...
@@ -879,7 +889,8 @@ import java.util.Map;
int
adGroupIndex
=
getAdGroupIndexForAdPod
(
adPodInfo
);
int
adGroupIndex
=
getAdGroupIndexForAdPod
(
adPodInfo
);
int
adIndexInAdGroup
=
adPodInfo
.
getAdPosition
()
-
1
;
int
adIndexInAdGroup
=
adPodInfo
.
getAdPosition
()
-
1
;
AdInfo
adInfo
=
new
AdInfo
(
adGroupIndex
,
adIndexInAdGroup
);
AdInfo
adInfo
=
new
AdInfo
(
adGroupIndex
,
adIndexInAdGroup
);
adInfoByAdMediaInfo
.
put
(
adMediaInfo
,
adInfo
);
// The ad URI may already be known, so force put to update it if needed.
adInfoByAdMediaInfo
.
forcePut
(
adMediaInfo
,
adInfo
);
if
(
configuration
.
debugModeEnabled
)
{
if
(
configuration
.
debugModeEnabled
)
{
Log
.
d
(
TAG
,
"loadAd "
+
getAdMediaInfoString
(
adMediaInfo
));
Log
.
d
(
TAG
,
"loadAd "
+
getAdMediaInfoString
(
adMediaInfo
));
}
}
...
...
extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java
View file @
4b1e0fa9
...
@@ -473,6 +473,16 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
...
@@ -473,6 +473,16 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
}
}
}
}
/**
* Moves UI focus to the skip button (or other interactive elements), if currently shown. See
* {@link AdsManager#focus()}.
*/
public
void
focusSkipButton
()
{
if
(
currentAdTagLoader
!=
null
)
{
currentAdTagLoader
.
focusSkipButton
();
}
}
// AdsLoader implementation.
// AdsLoader implementation.
@Override
@Override
...
...
library/common/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java
View file @
4b1e0fa9
...
@@ -30,11 +30,11 @@ public final class ExoPlayerLibraryInfo {
...
@@ -30,11 +30,11 @@ public final class ExoPlayerLibraryInfo {
/** The version of the library expressed as a string, for example "1.2.3". */
/** 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.
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
public
static
final
String
VERSION
=
"2.13.
0
"
;
public
static
final
String
VERSION
=
"2.13.
1
"
;
/** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */
/** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public
static
final
String
VERSION_SLASHY
=
"ExoPlayerLib/2.13.
0
"
;
public
static
final
String
VERSION_SLASHY
=
"ExoPlayerLib/2.13.
1
"
;
/**
/**
* The version of the library expressed as an integer, for example 1002003.
* The version of the library expressed as an integer, for example 1002003.
...
@@ -44,7 +44,7 @@ public final class ExoPlayerLibraryInfo {
...
@@ -44,7 +44,7 @@ public final class ExoPlayerLibraryInfo {
* integer version 123045006 (123-045-006).
* integer version 123045006 (123-045-006).
*/
*/
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public
static
final
int
VERSION_INT
=
201300
0
;
public
static
final
int
VERSION_INT
=
201300
1
;
/**
/**
* The default user agent for requests made by the library.
* The default user agent for requests made by the library.
...
...
library/common/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java
View file @
4b1e0fa9
...
@@ -182,9 +182,10 @@ public final class AdPlaybackState {
...
@@ -182,9 +182,10 @@ public final class AdPlaybackState {
/** Returns a new instance with the specified ad durations, in microseconds. */
/** Returns a new instance with the specified ad durations, in microseconds. */
@CheckResult
@CheckResult
public
AdGroup
withAdDurationsUs
(
long
[]
durationsUs
)
{
public
AdGroup
withAdDurationsUs
(
long
[]
durationsUs
)
{
Assertions
.
checkArgument
(
count
==
C
.
LENGTH_UNSET
||
durationsUs
.
length
<=
this
.
uris
.
length
);
if
(
durationsUs
.
length
<
uris
.
length
)
{
if
(
durationsUs
.
length
<
this
.
uris
.
length
)
{
durationsUs
=
copyDurationsUsWithSpaceForAdCount
(
durationsUs
,
uris
.
length
);
durationsUs
=
copyDurationsUsWithSpaceForAdCount
(
durationsUs
,
uris
.
length
);
}
else
if
(
count
!=
C
.
LENGTH_UNSET
&&
durationsUs
.
length
>
uris
.
length
)
{
durationsUs
=
Arrays
.
copyOf
(
durationsUs
,
uris
.
length
);
}
}
return
new
AdGroup
(
count
,
states
,
uris
,
durationsUs
);
return
new
AdGroup
(
count
,
states
,
uris
,
durationsUs
);
}
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java
View file @
4b1e0fa9
...
@@ -880,7 +880,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
...
@@ -880,7 +880,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
// Adjust live playback speed to new position.
// Adjust live playback speed to new position.
if
(
playbackInfo
.
playWhenReady
if
(
playbackInfo
.
playWhenReady
&&
playbackInfo
.
playbackState
==
Player
.
STATE_READY
&&
playbackInfo
.
playbackState
==
Player
.
STATE_READY
&&
isCurrentPeriodInMovingLiveWindow
(
)
&&
shouldUseLivePlaybackSpeedControl
(
playbackInfo
.
timeline
,
playbackInfo
.
periodId
)
&&
playbackInfo
.
playbackParameters
.
speed
==
1
f
)
{
&&
playbackInfo
.
playbackParameters
.
speed
==
1
f
)
{
float
adjustedSpeed
=
float
adjustedSpeed
=
livePlaybackSpeedControl
.
getAdjustedPlaybackSpeed
(
livePlaybackSpeedControl
.
getAdjustedPlaybackSpeed
(
...
@@ -1051,17 +1051,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
...
@@ -1051,17 +1051,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
-
(
periodPositionUs
+
period
.
getPositionInWindowUs
());
-
(
periodPositionUs
+
period
.
getPositionInWindowUs
());
}
}
private
boolean
isCurrentPeriodInMovingLiveWindow
()
{
private
boolean
shouldUseLivePlaybackSpeedControl
(
return
isInMovingLiveWindow
(
playbackInfo
.
timeline
,
playbackInfo
.
periodId
);
Timeline
timeline
,
MediaPeriodId
mediaPeriodId
)
{
}
private
boolean
isInMovingLiveWindow
(
Timeline
timeline
,
MediaPeriodId
mediaPeriodId
)
{
if
(
mediaPeriodId
.
isAd
()
||
timeline
.
isEmpty
())
{
if
(
mediaPeriodId
.
isAd
()
||
timeline
.
isEmpty
())
{
return
false
;
return
false
;
}
}
int
windowIndex
=
timeline
.
getPeriodByUid
(
mediaPeriodId
.
periodUid
,
period
).
windowIndex
;
int
windowIndex
=
timeline
.
getPeriodByUid
(
mediaPeriodId
.
periodUid
,
period
).
windowIndex
;
timeline
.
getWindow
(
windowIndex
,
window
);
timeline
.
getWindow
(
windowIndex
,
window
);
return
window
.
isLive
()
&&
window
.
isDynamic
;
return
window
.
isLive
()
&&
window
.
isDynamic
&&
window
.
windowStartTimeMs
!=
C
.
TIME_UNSET
;
}
}
private
void
scheduleNextWork
(
long
thisOperationStartTimeMs
,
long
intervalMs
)
{
private
void
scheduleNextWork
(
long
thisOperationStartTimeMs
,
long
intervalMs
)
{
...
@@ -1725,7 +1722,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
...
@@ -1725,7 +1722,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
}
// Renderers are ready and we're loading. Ask the LoadControl whether to transition.
// Renderers are ready and we're loading. Ask the LoadControl whether to transition.
long
targetLiveOffsetUs
=
long
targetLiveOffsetUs
=
isInMovingLiveWindow
(
playbackInfo
.
timeline
,
queue
.
getPlayingPeriod
().
info
.
id
)
shouldUseLivePlaybackSpeedControl
(
playbackInfo
.
timeline
,
queue
.
getPlayingPeriod
().
info
.
id
)
?
livePlaybackSpeedControl
.
getTargetLiveOffsetUs
()
?
livePlaybackSpeedControl
.
getTargetLiveOffsetUs
()
:
C
.
TIME_UNSET
;
:
C
.
TIME_UNSET
;
MediaPeriodHolder
loadingHolder
=
queue
.
getLoadingPeriod
();
MediaPeriodHolder
loadingHolder
=
queue
.
getLoadingPeriod
();
...
@@ -1831,7 +1828,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
...
@@ -1831,7 +1828,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
Timeline
oldTimeline
,
Timeline
oldTimeline
,
MediaPeriodId
oldPeriodId
,
MediaPeriodId
oldPeriodId
,
long
positionForTargetOffsetOverrideUs
)
{
long
positionForTargetOffsetOverrideUs
)
{
if
(
newTimeline
.
isEmpty
()
||
!
isInMovingLiveWindow
(
newTimeline
,
newPeriodId
))
{
if
(
newTimeline
.
isEmpty
()
||
!
shouldUseLivePlaybackSpeedControl
(
newTimeline
,
newPeriodId
))
{
// Live playback speed control is unused.
// Live playback speed control is unused.
return
;
return
;
}
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java
View file @
4b1e0fa9
...
@@ -488,7 +488,6 @@ public final class DefaultAudioSink implements AudioSink {
...
@@ -488,7 +488,6 @@ public final class DefaultAudioSink implements AudioSink {
throws
ConfigurationException
{
throws
ConfigurationException
{
int
inputPcmFrameSize
;
int
inputPcmFrameSize
;
@Nullable
AudioProcessor
[]
availableAudioProcessors
;
@Nullable
AudioProcessor
[]
availableAudioProcessors
;
boolean
canApplyPlaybackParameters
;
@OutputMode
int
outputMode
;
@OutputMode
int
outputMode
;
@C
.
Encoding
int
outputEncoding
;
@C
.
Encoding
int
outputEncoding
;
...
@@ -500,11 +499,10 @@ public final class DefaultAudioSink implements AudioSink {
...
@@ -500,11 +499,10 @@ public final class DefaultAudioSink implements AudioSink {
Assertions
.
checkArgument
(
Util
.
isEncodingLinearPcm
(
inputFormat
.
pcmEncoding
));
Assertions
.
checkArgument
(
Util
.
isEncodingLinearPcm
(
inputFormat
.
pcmEncoding
));
inputPcmFrameSize
=
Util
.
getPcmFrameSize
(
inputFormat
.
pcmEncoding
,
inputFormat
.
channelCount
);
inputPcmFrameSize
=
Util
.
getPcmFrameSize
(
inputFormat
.
pcmEncoding
,
inputFormat
.
channelCount
);
boolean
useFloatOutput
=
enableFloatOutput
&&
Util
.
isEncodingHighResolutionPcm
(
inputFormat
.
pcmEncoding
);
availableAudioProcessors
=
availableAudioProcessors
=
useFloatOutput
?
toFloatPcmAvailableAudioProcessors
:
toIntPcmAvailableAudioProcessors
;
shouldUseFloatOutput
(
inputFormat
.
pcmEncoding
)
canApplyPlaybackParameters
=
!
useFloatOutput
;
?
toFloatPcmAvailableAudioProcessors
:
toIntPcmAvailableAudioProcessors
;
trimmingAudioProcessor
.
setTrimFrameCount
(
trimmingAudioProcessor
.
setTrimFrameCount
(
inputFormat
.
encoderDelay
,
inputFormat
.
encoderPadding
);
inputFormat
.
encoderDelay
,
inputFormat
.
encoderPadding
);
...
@@ -541,7 +539,6 @@ public final class DefaultAudioSink implements AudioSink {
...
@@ -541,7 +539,6 @@ public final class DefaultAudioSink implements AudioSink {
}
else
{
}
else
{
inputPcmFrameSize
=
C
.
LENGTH_UNSET
;
inputPcmFrameSize
=
C
.
LENGTH_UNSET
;
availableAudioProcessors
=
new
AudioProcessor
[
0
];
availableAudioProcessors
=
new
AudioProcessor
[
0
];
canApplyPlaybackParameters
=
false
;
outputSampleRate
=
inputFormat
.
sampleRate
;
outputSampleRate
=
inputFormat
.
sampleRate
;
outputPcmFrameSize
=
C
.
LENGTH_UNSET
;
outputPcmFrameSize
=
C
.
LENGTH_UNSET
;
if
(
enableOffload
&&
isOffloadedPlaybackSupported
(
inputFormat
,
audioAttributes
))
{
if
(
enableOffload
&&
isOffloadedPlaybackSupported
(
inputFormat
,
audioAttributes
))
{
...
@@ -586,7 +583,6 @@ public final class DefaultAudioSink implements AudioSink {
...
@@ -586,7 +583,6 @@ public final class DefaultAudioSink implements AudioSink {
outputEncoding
,
outputEncoding
,
specifiedBufferSize
,
specifiedBufferSize
,
enableAudioTrackPlaybackParams
,
enableAudioTrackPlaybackParams
,
canApplyPlaybackParameters
,
availableAudioProcessors
);
availableAudioProcessors
);
if
(
isAudioTrackInitialized
())
{
if
(
isAudioTrackInitialized
())
{
this
.
pendingConfiguration
=
pendingConfiguration
;
this
.
pendingConfiguration
=
pendingConfiguration
;
...
@@ -1336,11 +1332,11 @@ public final class DefaultAudioSink implements AudioSink {
...
@@ -1336,11 +1332,11 @@ public final class DefaultAudioSink implements AudioSink {
private
void
applyAudioProcessorPlaybackParametersAndSkipSilence
(
long
presentationTimeUs
)
{
private
void
applyAudioProcessorPlaybackParametersAndSkipSilence
(
long
presentationTimeUs
)
{
PlaybackParameters
playbackParameters
=
PlaybackParameters
playbackParameters
=
configuration
.
canApplyPlaybackParameters
shouldApplyAudioProcessorPlaybackParameters
()
?
audioProcessorChain
.
applyPlaybackParameters
(
getAudioProcessorPlaybackParameters
())
?
audioProcessorChain
.
applyPlaybackParameters
(
getAudioProcessorPlaybackParameters
())
:
PlaybackParameters
.
DEFAULT
;
:
PlaybackParameters
.
DEFAULT
;
boolean
skipSilenceEnabled
=
boolean
skipSilenceEnabled
=
configuration
.
canApplyPlaybackParameters
shouldApplyAudioProcessorPlaybackParameters
()
?
audioProcessorChain
.
applySkipSilenceEnabled
(
getSkipSilenceEnabled
())
?
audioProcessorChain
.
applySkipSilenceEnabled
(
getSkipSilenceEnabled
())
:
DEFAULT_SKIP_SILENCE
;
:
DEFAULT_SKIP_SILENCE
;
mediaPositionParametersCheckpoints
.
add
(
mediaPositionParametersCheckpoints
.
add
(
...
@@ -1356,6 +1352,31 @@ public final class DefaultAudioSink implements AudioSink {
...
@@ -1356,6 +1352,31 @@ public final class DefaultAudioSink implements AudioSink {
}
}
/**
/**
* Returns whether audio processor playback parameters should be applied in the current
* configuration.
*/
private
boolean
shouldApplyAudioProcessorPlaybackParameters
()
{
// We don't apply speed/pitch adjustment using an audio processor in the following cases:
// - in tunneling mode, because audio processing can change the duration of audio yet the video
// frame presentation times are currently not modified (see also
// https://github.com/google/ExoPlayer/issues/4803);
// - when playing encoded audio via passthrough/offload, because modifying the audio stream
// would require decoding/re-encoding; and
// - when outputting float PCM audio, because SonicAudioProcessor outputs 16-bit integer PCM.
return
!
tunneling
&&
MimeTypes
.
AUDIO_RAW
.
equals
(
configuration
.
inputFormat
.
sampleMimeType
)
&&
!
shouldUseFloatOutput
(
configuration
.
inputFormat
.
pcmEncoding
);
}
/**
* Returns whether audio in the specified PCM encoding should be written to the audio track as
* float PCM.
*/
private
boolean
shouldUseFloatOutput
(
@C
.
PcmEncoding
int
pcmEncoding
)
{
return
enableFloatOutput
&&
Util
.
isEncodingHighResolutionPcm
(
pcmEncoding
);
}
/**
* Applies and updates media position parameters.
* Applies and updates media position parameters.
*
*
* @param positionUs The current audio track position, in microseconds.
* @param positionUs The current audio track position, in microseconds.
...
@@ -1897,7 +1918,6 @@ public final class DefaultAudioSink implements AudioSink {
...
@@ -1897,7 +1918,6 @@ public final class DefaultAudioSink implements AudioSink {
public
final
int
outputChannelConfig
;
public
final
int
outputChannelConfig
;
@C
.
Encoding
public
final
int
outputEncoding
;
@C
.
Encoding
public
final
int
outputEncoding
;
public
final
int
bufferSize
;
public
final
int
bufferSize
;
public
final
boolean
canApplyPlaybackParameters
;
public
final
AudioProcessor
[]
availableAudioProcessors
;
public
final
AudioProcessor
[]
availableAudioProcessors
;
public
Configuration
(
public
Configuration
(
...
@@ -1910,7 +1930,6 @@ public final class DefaultAudioSink implements AudioSink {
...
@@ -1910,7 +1930,6 @@ public final class DefaultAudioSink implements AudioSink {
int
outputEncoding
,
int
outputEncoding
,
int
specifiedBufferSize
,
int
specifiedBufferSize
,
boolean
enableAudioTrackPlaybackParams
,
boolean
enableAudioTrackPlaybackParams
,
boolean
canApplyPlaybackParameters
,
AudioProcessor
[]
availableAudioProcessors
)
{
AudioProcessor
[]
availableAudioProcessors
)
{
this
.
inputFormat
=
inputFormat
;
this
.
inputFormat
=
inputFormat
;
this
.
inputPcmFrameSize
=
inputPcmFrameSize
;
this
.
inputPcmFrameSize
=
inputPcmFrameSize
;
...
@@ -1919,7 +1938,6 @@ public final class DefaultAudioSink implements AudioSink {
...
@@ -1919,7 +1938,6 @@ public final class DefaultAudioSink implements AudioSink {
this
.
outputSampleRate
=
outputSampleRate
;
this
.
outputSampleRate
=
outputSampleRate
;
this
.
outputChannelConfig
=
outputChannelConfig
;
this
.
outputChannelConfig
=
outputChannelConfig
;
this
.
outputEncoding
=
outputEncoding
;
this
.
outputEncoding
=
outputEncoding
;
this
.
canApplyPlaybackParameters
=
canApplyPlaybackParameters
;
this
.
availableAudioProcessors
=
availableAudioProcessors
;
this
.
availableAudioProcessors
=
availableAudioProcessors
;
// Call computeBufferSize() last as it depends on the other configuration values.
// Call computeBufferSize() last as it depends on the other configuration values.
...
...
library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java
View file @
4b1e0fa9
...
@@ -457,12 +457,14 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
...
@@ -457,12 +457,14 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
if
(--
prepareCallsCount
!=
0
)
{
if
(--
prepareCallsCount
!=
0
)
{
return
;
return
;
}
}
// Make a local copy, because sessions are removed from this.sessions during release (via
// Release all keepalive acquisitions if keepalive is enabled.
// callback).
if
(
sessionKeepaliveMs
!=
C
.
TIME_UNSET
)
{
List
<
DefaultDrmSession
>
sessions
=
new
ArrayList
<>(
this
.
sessions
);
// Make a local copy, because sessions are removed from this.sessions during release (via
for
(
int
i
=
0
;
i
<
sessions
.
size
();
i
++)
{
// callback).
// Release all the keepalive acquisitions.
List
<
DefaultDrmSession
>
sessions
=
new
ArrayList
<>(
this
.
sessions
);
sessions
.
get
(
i
).
release
(
/* eventDispatcher= */
null
);
for
(
int
i
=
0
;
i
<
sessions
.
size
();
i
++)
{
sessions
.
get
(
i
).
release
(
/* eventDispatcher= */
null
);
}
}
}
Assertions
.
checkNotNull
(
exoMediaDrm
).
release
();
Assertions
.
checkNotNull
(
exoMediaDrm
).
release
();
exoMediaDrm
=
null
;
exoMediaDrm
=
null
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerProvider.java
View file @
4b1e0fa9
...
@@ -16,23 +16,38 @@
...
@@ -16,23 +16,38 @@
package
com
.
google
.
android
.
exoplayer2
.
drm
;
package
com
.
google
.
android
.
exoplayer2
.
drm
;
import
static
com
.
google
.
android
.
exoplayer2
.
drm
.
DefaultDrmSessionManager
.
MODE_PLAYBACK
;
import
static
com
.
google
.
android
.
exoplayer2
.
drm
.
DefaultDrmSessionManager
.
MODE_PLAYBACK
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkNotNull
;
import
androidx.annotation.GuardedBy
;
import
androidx.annotation.Nullable
;
import
androidx.annotation.Nullable
;
import
androidx.annotation.RequiresApi
;
import
com.google.android.exoplayer2.MediaItem
;
import
com.google.android.exoplayer2.MediaItem
;
import
com.google.android.exoplayer2.upstream.DefaultHttpDataSource
;
import
com.google.android.exoplayer2.upstream.DefaultHttpDataSource
;
import
com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory
;
import
com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory
;
import
com.google.android.exoplayer2.upstream.HttpDataSource
;
import
com.google.android.exoplayer2.upstream.HttpDataSource
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.Util
;
import
com.google.android.exoplayer2.util.Util
;
import
com.google.common.primitives.Ints
;
import
com.google.common.primitives.Ints
;
import
java.util.Map
;
import
java.util.Map
;
import
org.checkerframework.checker.nullness.qual.MonotonicNonNull
;
/** Default implementation of {@link DrmSessionManagerProvider}. */
/** Default implementation of {@link DrmSessionManagerProvider}. */
public
final
class
DefaultDrmSessionManagerProvider
implements
DrmSessionManagerProvider
{
public
final
class
DefaultDrmSessionManagerProvider
implements
DrmSessionManagerProvider
{
private
final
Object
lock
;
@GuardedBy
(
"lock"
)
private
MediaItem
.
@MonotonicNonNull
DrmConfiguration
drmConfiguration
;
@GuardedBy
(
"lock"
)
private
@MonotonicNonNull
DrmSessionManager
manager
;
@Nullable
private
HttpDataSource
.
Factory
drmHttpDataSourceFactory
;
@Nullable
private
HttpDataSource
.
Factory
drmHttpDataSourceFactory
;
@Nullable
private
String
userAgent
;
@Nullable
private
String
userAgent
;
public
DefaultDrmSessionManagerProvider
()
{
lock
=
new
Object
();
}
/**
/**
* Sets the {@link HttpDataSource.Factory} to be used for creating {@link HttpMediaDrmCallback
* Sets the {@link HttpDataSource.Factory} to be used for creating {@link HttpMediaDrmCallback
* HttpMediaDrmCallbacks} which executes key and provisioning requests over HTTP. If {@code null}
* HttpMediaDrmCallbacks} which executes key and provisioning requests over HTTP. If {@code null}
...
@@ -60,12 +75,24 @@ public final class DefaultDrmSessionManagerProvider implements DrmSessionManager
...
@@ -60,12 +75,24 @@ public final class DefaultDrmSessionManagerProvider implements DrmSessionManager
@Override
@Override
public
DrmSessionManager
get
(
MediaItem
mediaItem
)
{
public
DrmSessionManager
get
(
MediaItem
mediaItem
)
{
Assertions
.
checkNotNull
(
mediaItem
.
playbackProperties
);
checkNotNull
(
mediaItem
.
playbackProperties
);
@Nullable
@Nullable
MediaItem
.
DrmConfiguration
drmConfiguration
=
mediaItem
.
playbackProperties
.
drmConfiguration
;
MediaItem
.
DrmConfiguration
drmConfiguration
=
mediaItem
.
playbackProperties
.
drmConfiguration
;
if
(
drmConfiguration
==
null
||
Util
.
SDK_INT
<
18
)
{
if
(
drmConfiguration
==
null
||
Util
.
SDK_INT
<
18
)
{
return
DrmSessionManager
.
DRM_UNSUPPORTED
;
return
DrmSessionManager
.
DRM_UNSUPPORTED
;
}
}
synchronized
(
lock
)
{
if
(!
Util
.
areEqual
(
drmConfiguration
,
this
.
drmConfiguration
))
{
this
.
drmConfiguration
=
drmConfiguration
;
this
.
manager
=
createManager
(
drmConfiguration
);
}
return
checkNotNull
(
this
.
manager
);
}
}
@RequiresApi
(
18
)
private
DrmSessionManager
createManager
(
MediaItem
.
DrmConfiguration
drmConfiguration
)
{
HttpDataSource
.
Factory
dataSourceFactory
=
HttpDataSource
.
Factory
dataSourceFactory
=
drmHttpDataSourceFactory
!=
null
drmHttpDataSourceFactory
!=
null
?
drmHttpDataSourceFactory
?
drmHttpDataSourceFactory
...
...
library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java
View file @
4b1e0fa9
...
@@ -318,8 +318,28 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
...
@@ -318,8 +318,28 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
&&
adIndexInAdGroup
<
adPlaybackState
.
adGroups
[
adGroupIndex
].
uris
.
length
)
{
&&
adIndexInAdGroup
<
adPlaybackState
.
adGroups
[
adGroupIndex
].
uris
.
length
)
{
@Nullable
Uri
adUri
=
adPlaybackState
.
adGroups
[
adGroupIndex
].
uris
[
adIndexInAdGroup
];
@Nullable
Uri
adUri
=
adPlaybackState
.
adGroups
[
adGroupIndex
].
uris
[
adIndexInAdGroup
];
if
(
adUri
!=
null
)
{
if
(
adUri
!=
null
)
{
MediaSource
adMediaSource
=
MediaItem
.
Builder
adMediaItem
=
new
MediaItem
.
Builder
().
setUri
(
adUri
);
adMediaSourceFactory
.
createMediaSource
(
MediaItem
.
fromUri
(
adUri
));
// Propagate the content's DRM config into the ad media source.
@Nullable
MediaItem
.
PlaybackProperties
contentPlaybackProperties
=
contentMediaSource
.
getMediaItem
().
playbackProperties
;
if
(
contentPlaybackProperties
!=
null
&&
contentPlaybackProperties
.
drmConfiguration
!=
null
)
{
MediaItem
.
DrmConfiguration
drmConfiguration
=
contentPlaybackProperties
.
drmConfiguration
;
// TODO(internal b/179984779): Use MediaItem.Builder#setDrmConfiguration() when it's
// available.
adMediaItem
.
setDrmUuid
(
drmConfiguration
.
uuid
);
adMediaItem
.
setDrmKeySetId
(
drmConfiguration
.
getKeySetId
());
adMediaItem
.
setDrmLicenseUri
(
drmConfiguration
.
licenseUri
);
adMediaItem
.
setDrmForceDefaultLicenseUri
(
drmConfiguration
.
forceDefaultLicenseUri
);
adMediaItem
.
setDrmLicenseRequestHeaders
(
drmConfiguration
.
requestHeaders
);
adMediaItem
.
setDrmMultiSession
(
drmConfiguration
.
multiSession
);
adMediaItem
.
setDrmPlayClearContentWithoutKey
(
drmConfiguration
.
playClearContentWithoutKey
);
adMediaItem
.
setDrmSessionForClearTypes
(
drmConfiguration
.
sessionForClearTypes
);
}
MediaSource
adMediaSource
=
adMediaSourceFactory
.
createMediaSource
(
adMediaItem
.
build
());
adMediaSourceHolder
.
initializeWithMediaSource
(
adMediaSource
,
adUri
);
adMediaSourceHolder
.
initializeWithMediaSource
(
adMediaSource
,
adUri
);
}
}
}
}
...
...
library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java
View file @
4b1e0fa9
...
@@ -71,6 +71,7 @@ import com.google.android.exoplayer2.source.MediaSource;
...
@@ -71,6 +71,7 @@ import com.google.android.exoplayer2.source.MediaSource;
import
com.google.android.exoplayer2.source.MediaSource.MediaPeriodId
;
import
com.google.android.exoplayer2.source.MediaSource.MediaPeriodId
;
import
com.google.android.exoplayer2.source.MediaSourceEventListener
;
import
com.google.android.exoplayer2.source.MediaSourceEventListener
;
import
com.google.android.exoplayer2.source.SilenceMediaSource
;
import
com.google.android.exoplayer2.source.SilenceMediaSource
;
import
com.google.android.exoplayer2.source.SinglePeriodTimeline
;
import
com.google.android.exoplayer2.source.TrackGroup
;
import
com.google.android.exoplayer2.source.TrackGroup
;
import
com.google.android.exoplayer2.source.TrackGroupArray
;
import
com.google.android.exoplayer2.source.TrackGroupArray
;
import
com.google.android.exoplayer2.source.ads.AdPlaybackState
;
import
com.google.android.exoplayer2.source.ads.AdPlaybackState
;
...
@@ -83,6 +84,7 @@ import com.google.android.exoplayer2.testutil.ExoPlayerTestRunner;
...
@@ -83,6 +84,7 @@ import com.google.android.exoplayer2.testutil.ExoPlayerTestRunner;
import
com.google.android.exoplayer2.testutil.FakeAdaptiveDataSet
;
import
com.google.android.exoplayer2.testutil.FakeAdaptiveDataSet
;
import
com.google.android.exoplayer2.testutil.FakeAdaptiveMediaSource
;
import
com.google.android.exoplayer2.testutil.FakeAdaptiveMediaSource
;
import
com.google.android.exoplayer2.testutil.FakeChunkSource
;
import
com.google.android.exoplayer2.testutil.FakeChunkSource
;
import
com.google.android.exoplayer2.testutil.FakeClock
;
import
com.google.android.exoplayer2.testutil.FakeDataSource
;
import
com.google.android.exoplayer2.testutil.FakeDataSource
;
import
com.google.android.exoplayer2.testutil.FakeMediaClockRenderer
;
import
com.google.android.exoplayer2.testutil.FakeMediaClockRenderer
;
import
com.google.android.exoplayer2.testutil.FakeMediaPeriod
;
import
com.google.android.exoplayer2.testutil.FakeMediaPeriod
;
...
@@ -8834,6 +8836,42 @@ public final class ExoPlayerTest {
...
@@ -8834,6 +8836,42 @@ public final class ExoPlayerTest {
}
}
@Test
@Test
public
void
targetLiveOffsetInMedia_unknownWindowStartTime_doesNotAdjustLiveOffset
()
throws
Exception
{
FakeClock
fakeClock
=
new
AutoAdvancingFakeClock
(
/* initialTimeMs= */
987_654_321L
);
ExoPlayer
player
=
new
TestExoPlayerBuilder
(
context
).
setClock
(
fakeClock
).
build
();
MediaItem
mediaItem
=
new
MediaItem
.
Builder
().
setUri
(
Uri
.
EMPTY
).
setLiveTargetOffsetMs
(
4_000
).
build
();
Timeline
liveTimeline
=
new
SinglePeriodTimeline
(
/* presentationStartTimeMs= */
C
.
TIME_UNSET
,
/* windowStartTimeMs= */
C
.
TIME_UNSET
,
/* elapsedRealtimeEpochOffsetMs= */
C
.
TIME_UNSET
,
/* periodDurationUs= */
1000
*
C
.
MICROS_PER_SECOND
,
/* windowDurationUs= */
1000
*
C
.
MICROS_PER_SECOND
,
/* windowPositionInPeriodUs= */
0
,
/* windowDefaultStartPositionUs= */
0
,
/* isSeekable= */
true
,
/* isDynamic= */
true
,
/* manifest= */
null
,
mediaItem
,
mediaItem
.
liveConfiguration
);
player
.
pause
();
player
.
setMediaSource
(
new
FakeMediaSource
(
liveTimeline
));
player
.
prepare
();
TestPlayerRunHelper
.
runUntilPlaybackState
(
player
,
Player
.
STATE_READY
);
long
playbackStartTimeMs
=
fakeClock
.
elapsedRealtime
();
TestPlayerRunHelper
.
playUntilPosition
(
player
,
/* windowIndex= */
0
,
/* positionMs= */
999_000
);
long
playbackEndTimeMs
=
fakeClock
.
elapsedRealtime
();
player
.
release
();
// Assert that the time it took to play 999 seconds of media is 999 seconds (asserting that no
// playback speed adjustment was used).
assertThat
(
playbackEndTimeMs
-
playbackStartTimeMs
).
isEqualTo
(
999_000
);
}
@Test
public
void
noTargetLiveOffsetInMedia_doesNotAdjustLiveOffset
()
throws
Exception
{
public
void
noTargetLiveOffsetInMedia_doesNotAdjustLiveOffset
()
throws
Exception
{
long
windowStartUnixTimeMs
=
987_654_321_000L
;
long
windowStartUnixTimeMs
=
987_654_321_000L
;
long
nowUnixTimeMs
=
windowStartUnixTimeMs
+
20_000
;
long
nowUnixTimeMs
=
windowStartUnixTimeMs
+
20_000
;
...
...
library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioSinkTest.java
View file @
4b1e0fa9
...
@@ -320,6 +320,20 @@ public final class DefaultAudioSinkTest {
...
@@ -320,6 +320,20 @@ public final class DefaultAudioSinkTest {
assertThat
(
thrown
.
format
).
isEqualTo
(
format
);
assertThat
(
thrown
.
format
).
isEqualTo
(
format
);
}
}
@Test
public
void
setPlaybackParameters_doesNothingWhenTunnelingIsEnabled
()
throws
Exception
{
defaultAudioSink
.
setAudioSessionId
(
1
);
defaultAudioSink
.
enableTunnelingV21
();
defaultAudioSink
.
setPlaybackParameters
(
new
PlaybackParameters
(
2
));
configureDefaultAudioSink
(
/* channelCount= */
2
);
defaultAudioSink
.
handleBuffer
(
createDefaultSilenceBuffer
(),
/* presentationTimeUs= */
5
*
C
.
MICROS_PER_SECOND
,
/* encodedAccessUnitCount= */
1
);
assertThat
(
defaultAudioSink
.
getPlaybackParameters
().
speed
).
isEqualTo
(
1
);
}
private
void
configureDefaultAudioSink
(
int
channelCount
)
throws
AudioSink
.
ConfigurationException
{
private
void
configureDefaultAudioSink
(
int
channelCount
)
throws
AudioSink
.
ConfigurationException
{
configureDefaultAudioSink
(
channelCount
,
/* trimStartFrames= */
0
,
/* trimEndFrames= */
0
);
configureDefaultAudioSink
(
channelCount
,
/* trimStartFrames= */
0
,
/* trimEndFrames= */
0
);
}
}
...
...
library/core/src/test/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerTest.java
View file @
4b1e0fa9
...
@@ -148,6 +148,32 @@ public class DefaultDrmSessionManagerTest {
...
@@ -148,6 +148,32 @@ public class DefaultDrmSessionManagerTest {
}
}
@Test
(
timeout
=
10_000
)
@Test
(
timeout
=
10_000
)
public
void
managerRelease_keepaliveDisabled_doesntReleaseAnySessions
()
throws
Exception
{
FakeExoMediaDrm
.
LicenseServer
licenseServer
=
FakeExoMediaDrm
.
LicenseServer
.
allowingSchemeDatas
(
DRM_SCHEME_DATAS
);
DrmSessionManager
drmSessionManager
=
new
DefaultDrmSessionManager
.
Builder
()
.
setUuidAndExoMediaDrmProvider
(
DRM_SCHEME_UUID
,
uuid
->
new
FakeExoMediaDrm
())
.
setSessionKeepaliveMs
(
C
.
TIME_UNSET
)
.
build
(
/* mediaDrmCallback= */
licenseServer
);
drmSessionManager
.
prepare
();
DrmSession
drmSession
=
checkNotNull
(
drmSessionManager
.
acquireSession
(
/* playbackLooper= */
checkNotNull
(
Looper
.
myLooper
()),
/* eventDispatcher= */
null
,
FORMAT_WITH_DRM_INIT_DATA
));
waitForOpenedWithKeys
(
drmSession
);
assertThat
(
drmSession
.
getState
()).
isEqualTo
(
DrmSession
.
STATE_OPENED_WITH_KEYS
);
// Release the manager, the session should still be open (though it's unusable because
// the underlying ExoMediaDrm is released).
drmSessionManager
.
release
();
assertThat
(
drmSession
.
getState
()).
isEqualTo
(
DrmSession
.
STATE_OPENED_WITH_KEYS
);
}
@Test
(
timeout
=
10_000
)
public
void
maxConcurrentSessionsExceeded_allKeepAliveSessionsEagerlyReleased
()
throws
Exception
{
public
void
maxConcurrentSessionsExceeded_allKeepAliveSessionsEagerlyReleased
()
throws
Exception
{
ImmutableList
<
DrmInitData
.
SchemeData
>
secondSchemeDatas
=
ImmutableList
<
DrmInitData
.
SchemeData
>
secondSchemeDatas
=
ImmutableList
.
of
(
DRM_SCHEME_DATAS
.
get
(
0
).
copyWithData
(
TestUtil
.
createByteArray
(
4
,
5
,
6
)));
ImmutableList
.
of
(
DRM_SCHEME_DATAS
.
get
(
0
).
copyWithData
(
TestUtil
.
createByteArray
(
4
,
5
,
6
)));
...
...
library/core/src/test/java/com/google/android/exoplayer2/source/DefaultDrmSessionManagerProviderTest.java
View file @
4b1e0fa9
...
@@ -51,4 +51,39 @@ public class DefaultDrmSessionManagerProviderTest {
...
@@ -51,4 +51,39 @@ public class DefaultDrmSessionManagerProviderTest {
assertThat
(
drmSessionManager
).
isNotEqualTo
(
DrmSessionManager
.
DRM_UNSUPPORTED
);
assertThat
(
drmSessionManager
).
isNotEqualTo
(
DrmSessionManager
.
DRM_UNSUPPORTED
);
}
}
@Test
public
void
create_reusesCachedInstanceWherePossible
()
{
MediaItem
mediaItem1
=
new
MediaItem
.
Builder
()
.
setUri
(
"https://example.test/content-1"
)
.
setDrmUuid
(
C
.
WIDEVINE_UUID
)
.
build
();
// Same DRM info as item1, but different URL to check it doesn't prevent re-using a manager.
MediaItem
mediaItem2
=
new
MediaItem
.
Builder
()
.
setUri
(
"https://example.test/content-2"
)
.
setDrmUuid
(
C
.
WIDEVINE_UUID
)
.
build
();
// Different DRM info to 1 and 2, needs a different manager instance.
MediaItem
mediaItem3
=
new
MediaItem
.
Builder
()
.
setUri
(
"https://example.test/content-3"
)
.
setDrmUuid
(
C
.
WIDEVINE_UUID
)
.
setDrmLicenseUri
(
"https://example.test/license"
)
.
build
();
DefaultDrmSessionManagerProvider
provider
=
new
DefaultDrmSessionManagerProvider
();
DrmSessionManager
drmSessionManager1
=
provider
.
get
(
mediaItem1
);
DrmSessionManager
drmSessionManager2
=
provider
.
get
(
mediaItem2
);
DrmSessionManager
drmSessionManager3
=
provider
.
get
(
mediaItem3
);
// Get a manager for the first item again - expect it to be a different instance to last time
// since we only cache one.
DrmSessionManager
drmSessionManager4
=
provider
.
get
(
mediaItem1
);
assertThat
(
drmSessionManager1
).
isSameInstanceAs
(
drmSessionManager2
);
assertThat
(
drmSessionManager1
).
isNotSameInstanceAs
(
drmSessionManager3
);
assertThat
(
drmSessionManager1
).
isNotSameInstanceAs
(
drmSessionManager4
);
}
}
}
library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java
View file @
4b1e0fa9
This diff is collapsed.
Click to expand it.
library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Period.java
View file @
4b1e0fa9
...
@@ -30,9 +30,7 @@ public class Period {
...
@@ -30,9 +30,7 @@ public class Period {
*/
*/
@Nullable
public
final
String
id
;
@Nullable
public
final
String
id
;
/**
/** The start time of the period in milliseconds, relative to the start of the manifest. */
* The start time of the period in milliseconds.
*/
public
final
long
startMs
;
public
final
long
startMs
;
/**
/**
...
...
library/transformer/src/main/java/com/google/android/exoplayer2/transformer/SpeedProvider.java
View file @
4b1e0fa9
...
@@ -16,7 +16,7 @@
...
@@ -16,7 +16,7 @@
package
com
.
google
.
android
.
exoplayer2
.
transformer
;
package
com
.
google
.
android
.
exoplayer2
.
transformer
;
/** A custom interface that determines the speed for media at specific timestamps. */
/** A custom interface that determines the speed for media at specific timestamps. */
public
interface
SpeedProvider
{
/* package */
interface
SpeedProvider
{
/**
/**
* Provides the speed that the media should be played at, based on the timeUs.
* Provides the speed that the media should be played at, based on the timeUs.
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment