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
f6297f4f
authored
Jun 23, 2019
by
Oliver Woodman
Committed by
GitHub
Jun 23, 2019
Browse files
Options
_('Browse Files')
Download
Plain Diff
Merge pull request #5986 from google/dev-v2-r2.10.2
r2.10.2
parents
7e407089
b9c8861b
Hide whitespace changes
Inline
Side-by-side
Showing
65 changed files
with
1427 additions
and
487 deletions
RELEASENOTES.md
build.gradle
constants.gradle
demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DefaultReceiverPlayerManager.java
extensions/cast/build.gradle
extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java
extensions/ima/build.gradle
extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java
extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java
extensions/vp9/README.md
extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxOutputBuffer.java
extensions/vp9/src/main/jni/Application.mk
extensions/vp9/src/main/jni/generate_libvpx_android_configs.sh
extensions/vp9/src/main/jni/vpx_jni.cc
library/core/src/main/java/com/google/android/exoplayer2/C.java
library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java
library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java
library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java
library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java
library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java
library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java
library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java
library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java
library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java
library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java
library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java
library/core/src/main/java/com/google/android/exoplayer2/scheduler/Requirements.java
library/core/src/main/java/com/google/android/exoplayer2/scheduler/RequirementsWatcher.java
library/core/src/main/java/com/google/android/exoplayer2/source/SilenceMediaSource.java
library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java
library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java
library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java
library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRegion.java
library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/CssParser.java
library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoder.java
library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java
library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java
library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java
library/core/src/main/java/com/google/android/exoplayer2/upstream/ResolvingDataSource.java
library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java
library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java
library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java
library/core/src/main/java/com/google/android/exoplayer2/util/Util.java
library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java
library/core/src/test/assets/webvtt/with_css_styles
library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java
library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/CssParserTest.java
library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java
library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java
library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadManagerDashTest.java
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java
library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java
library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java
library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java
library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java
library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java
library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java
library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalSurfaceView.java
library/ui/src/main/res/layout/exo_playback_control_view.xml
library/ui/src/main/res/values/attrs.xml
library/ui/src/main/res/values/ids.xml
testutils/src/main/java/com/google/android/exoplayer2/testutil/DebugRenderersFactory.java
testutils/src/main/java/com/google/android/exoplayer2/testutil/HostActivity.java
testutils/src/main/res/layout/host_activity.xml → testutils/src/main/res/layout/exo_testutils_host_activity.xml
testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/CacheAsserts.java
RELEASENOTES.md
View file @
f6297f4f
# Release notes #
### 2.10.2 ###
*
Add
`ResolvingDataSource`
for just-in-time resolution of
`DataSpec`
s
(
[
#5779
](
https://github.com/google/ExoPlayer/issues/5779
)
).
*
Add
`SilenceMediaSource`
that can be used to play silence of a given
duration (
[
#5735
](
https://github.com/google/ExoPlayer/issues/5735
)
).
*
Offline:
*
Prevent unexpected
`DownloadHelper.Callback.onPrepared`
callbacks after
preparation of a
`DownloadHelper`
fails
(
[
#5915
](
https://github.com/google/ExoPlayer/issues/5915
)
).
*
Fix
`CacheUtil.cache()`
downloading too much data
(
[
#5927
](
https://github.com/google/ExoPlayer/issues/5927
)
).
*
Fix misreporting cached bytes when caching is paused
(
[
#5573
](
https://github.com/google/ExoPlayer/issues/5573
)
).
*
UI:
*
Allow setting
`DefaultTimeBar`
attributes on
`PlayerView`
and
`PlayerControlView`
.
*
Change playback controls toggle from touch down to touch up events
(
[
#5784
](
https://github.com/google/ExoPlayer/issues/5784
)
).
*
Fix issue where playback controls were not kept visible on key presses
(
[
#5963
](
https://github.com/google/ExoPlayer/issues/5963
)
).
*
Subtitles:
*
CEA-608: Handle XDS and TEXT modes
(
[
#5807
](
https://github.com/google/ExoPlayer/pull/5807
)
).
*
TTML: Fix bitmap rendering
(
[
#5633
](
https://github.com/google/ExoPlayer/pull/5633
)
).
*
IMA: Fix ad pod index offset calculation without preroll
(
[
#5928
](
https://github.com/google/ExoPlayer/issues/5928
)
).
*
Add a
`playWhenReady`
flag to MediaSessionConnector.PlaybackPreparer methods
to indicate whether a controller sent a play or only a prepare command. This
allows to take advantage of decoder reuse with the MediaSessionConnector
(
[
#5891
](
https://github.com/google/ExoPlayer/issues/5891
)
).
*
Add
`ProgressUpdateListener`
to
`PlayerControlView`
(
[
#5834
](
https://github.com/google/ExoPlayer/issues/5834
)
).
*
Add support for auto-detecting UDP streams in
`DefaultDataSource`
(
[
#6036
](
https://github.com/google/ExoPlayer/pull/6036
)
).
*
Allow enabling decoder fallback with
`DefaultRenderersFactory`
(
[
#5942
](
https://github.com/google/ExoPlayer/issues/5942
)
).
*
Gracefully handle revoked
`ACCESS_NETWORK_STATE`
permission
(
[
#6019
](
https://github.com/google/ExoPlayer/issues/6019
)
).
*
Fix decoding problems when seeking back after seeking beyond a mid-roll ad
(
[
#6009
](
https://github.com/google/ExoPlayer/issues/6009
)
).
*
Fix application of
`maxAudioBitrate`
for adaptive audio track groups
(
[
#6006
](
https://github.com/google/ExoPlayer/issues/6006
)
).
*
Fix bug caused by parallel adaptive track selection using
`Format`
s without
bitrate information
(
[
#5971
](
https://github.com/google/ExoPlayer/issues/5971
)
).
*
Fix bug in
`CastPlayer.getCurrentWindowIndex()`
(
[
#5955
](
https://github.com/google/ExoPlayer/issues/5955
)
).
### 2.10.1 ###
*
Offline: Add option to remove all downloads.
...
...
build.gradle
View file @
f6297f4f
...
...
@@ -36,7 +36,7 @@ allprojects {
jcenter
()
}
project
.
ext
{
exoplayerPublishEnabled
=
tru
e
exoplayerPublishEnabled
=
fals
e
}
if
(
it
.
hasProperty
(
'externalBuildDir'
))
{
if
(!
new
File
(
externalBuildDir
).
isAbsolute
())
{
...
...
constants.gradle
View file @
f6297f4f
...
...
@@ -13,8 +13,8 @@
// limitations under the License.
project
.
ext
{
// ExoPlayer version and version code.
releaseVersion
=
'2.10.
1
'
releaseVersionCode
=
201000
1
releaseVersion
=
'2.10.
2
'
releaseVersionCode
=
201000
2
minSdkVersion
=
16
targetSdkVersion
=
28
compileSdkVersion
=
28
...
...
demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DefaultReceiverPlayerManager.java
View file @
f6297f4f
...
...
@@ -66,7 +66,6 @@ import java.util.ArrayList;
private
final
Listener
listener
;
private
final
ConcatenatingMediaSource
concatenatingMediaSource
;
private
boolean
castMediaQueueCreationPending
;
private
int
currentItemIndex
;
private
Player
currentPlayer
;
...
...
@@ -268,9 +267,6 @@ import java.util.ArrayList;
public
void
onTimelineChanged
(
Timeline
timeline
,
@Nullable
Object
manifest
,
@TimelineChangeReason
int
reason
)
{
updateCurrentItemIndex
();
if
(
currentPlayer
==
castPlayer
&&
timeline
.
isEmpty
())
{
castMediaQueueCreationPending
=
true
;
}
}
// CastPlayer.SessionAvailabilityListener implementation.
...
...
@@ -332,7 +328,6 @@ import java.util.ArrayList;
this
.
currentPlayer
=
currentPlayer
;
// Media queue management.
castMediaQueueCreationPending
=
currentPlayer
==
castPlayer
;
if
(
currentPlayer
==
exoPlayer
)
{
exoPlayer
.
prepare
(
concatenatingMediaSource
);
}
...
...
@@ -352,12 +347,11 @@ import java.util.ArrayList;
*/
private
void
setCurrentItem
(
int
itemIndex
,
long
positionMs
,
boolean
playWhenReady
)
{
maybeSetCurrentItemAndNotify
(
itemIndex
);
if
(
c
astMediaQueueCreationPending
)
{
if
(
c
urrentPlayer
==
castPlayer
&&
castPlayer
.
getCurrentTimeline
().
isEmpty
()
)
{
MediaQueueItem
[]
items
=
new
MediaQueueItem
[
mediaQueue
.
size
()];
for
(
int
i
=
0
;
i
<
items
.
length
;
i
++)
{
items
[
i
]
=
buildMediaQueueItem
(
mediaQueue
.
get
(
i
));
}
castMediaQueueCreationPending
=
false
;
castPlayer
.
loadItems
(
items
,
itemIndex
,
positionMs
,
Player
.
REPEAT_MODE_OFF
);
}
else
{
currentPlayer
.
seekTo
(
itemIndex
,
positionMs
);
...
...
extensions/cast/build.gradle
View file @
f6297f4f
...
...
@@ -31,7 +31,7 @@ android {
}
dependencies
{
api
'com.google.android.gms:play-services-cast-framework:16.
1.2
'
api
'com.google.android.gms:play-services-cast-framework:16.
2.0
'
implementation
'androidx.annotation:annotation:1.0.2'
implementation
project
(
modulePrefix
+
'library-core'
)
implementation
project
(
modulePrefix
+
'library-ui'
)
...
...
extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java
View file @
f6297f4f
...
...
@@ -45,8 +45,11 @@ import com.google.android.gms.cast.framework.media.RemoteMediaClient;
import
com.google.android.gms.cast.framework.media.RemoteMediaClient.MediaChannelResult
;
import
com.google.android.gms.common.api.PendingResult
;
import
com.google.android.gms.common.api.ResultCallback
;
import
java.util.ArrayDeque
;
import
java.util.ArrayList
;
import
java.util.Iterator
;
import
java.util.List
;
import
java.util.concurrent.CopyOnWriteArray
Se
t
;
import
java.util.concurrent.CopyOnWriteArray
Lis
t
;
/**
* {@link Player} implementation that communicates with a Cast receiver app.
...
...
@@ -86,8 +89,10 @@ public final class CastPlayer extends BasePlayer {
private
final
StatusListener
statusListener
;
private
final
SeekResultCallback
seekResultCallback
;
// Listeners.
private
final
CopyOnWriteArraySet
<
EventListener
>
listeners
;
// Listeners and notification.
private
final
CopyOnWriteArrayList
<
ListenerHolder
>
listeners
;
private
final
ArrayList
<
ListenerNotificationTask
>
notificationsBatch
;
private
final
ArrayDeque
<
ListenerNotificationTask
>
ongoingNotificationsTasks
;
private
SessionAvailabilityListener
sessionAvailabilityListener
;
// Internal state.
...
...
@@ -113,7 +118,9 @@ public final class CastPlayer extends BasePlayer {
period
=
new
Timeline
.
Period
();
statusListener
=
new
StatusListener
();
seekResultCallback
=
new
SeekResultCallback
();
listeners
=
new
CopyOnWriteArraySet
<>();
listeners
=
new
CopyOnWriteArrayList
<>();
notificationsBatch
=
new
ArrayList
<>();
ongoingNotificationsTasks
=
new
ArrayDeque
<>();
SessionManager
sessionManager
=
castContext
.
getSessionManager
();
sessionManager
.
addSessionManagerListener
(
statusListener
,
CastSession
.
class
);
...
...
@@ -296,12 +303,17 @@ public final class CastPlayer extends BasePlayer {
@Override
public
void
addListener
(
EventListener
listener
)
{
listeners
.
add
(
listener
);
listeners
.
add
IfAbsent
(
new
ListenerHolder
(
listener
)
);
}
@Override
public
void
removeListener
(
EventListener
listener
)
{
listeners
.
remove
(
listener
);
for
(
ListenerHolder
listenerHolder
:
listeners
)
{
if
(
listenerHolder
.
listener
.
equals
(
listener
))
{
listenerHolder
.
release
();
listeners
.
remove
(
listenerHolder
);
}
}
}
@Override
...
...
@@ -347,14 +359,13 @@ public final class CastPlayer extends BasePlayer {
pendingSeekCount
++;
pendingSeekWindowIndex
=
windowIndex
;
pendingSeekPositionMs
=
positionMs
;
for
(
EventListener
listener
:
listeners
)
{
listener
.
onPositionDiscontinuity
(
Player
.
DISCONTINUITY_REASON_SEEK
);
}
notificationsBatch
.
add
(
new
ListenerNotificationTask
(
listener
->
listener
.
onPositionDiscontinuity
(
DISCONTINUITY_REASON_SEEK
)));
}
else
if
(
pendingSeekCount
==
0
)
{
for
(
EventListener
listener
:
listeners
)
{
listener
.
onSeekProcessed
();
}
notificationsBatch
.
add
(
new
ListenerNotificationTask
(
EventListener:
:
onSeekProcessed
));
}
flushNotifications
();
}
@Override
...
...
@@ -530,30 +541,40 @@ public final class CastPlayer extends BasePlayer {
||
this
.
playWhenReady
!=
playWhenReady
)
{
this
.
playbackState
=
playbackState
;
this
.
playWhenReady
=
playWhenReady
;
for
(
EventListener
listener
:
listeners
)
{
listener
.
onPlayerStateChanged
(
this
.
playWhenReady
,
this
.
playbackState
);
}
notificationsBatch
.
add
(
new
ListenerNotificationTask
(
listener
->
listener
.
onPlayerStateChanged
(
this
.
playWhenReady
,
this
.
playbackState
)));
}
@RepeatMode
int
repeatMode
=
fetchRepeatMode
(
remoteMediaClient
);
if
(
this
.
repeatMode
!=
repeatMode
)
{
this
.
repeatMode
=
repeatMode
;
for
(
EventListener
listener
:
listeners
)
{
listener
.
onRepeatModeChanged
(
repeatMode
);
}
notificationsBatch
.
add
(
new
ListenerNotificationTask
(
listener
->
listener
.
onRepeatModeChanged
(
this
.
repeatMode
)));
}
maybeUpdateTimelineAndNotify
();
int
currentWindowIndex
=
C
.
INDEX_UNSET
;
MediaQueueItem
currentItem
=
remoteMediaClient
.
getCurrentItem
();
if
(
currentItem
!=
null
)
{
currentWindowIndex
=
currentTimeline
.
getIndexOfPeriod
(
currentItem
.
getItemId
());
}
if
(
currentWindowIndex
==
C
.
INDEX_UNSET
)
{
// The timeline is empty. Fall back to index 0, which is what ExoPlayer would do.
currentWindowIndex
=
0
;
}
int
currentWindowIndex
=
fetchCurrentWindowIndex
(
getMediaStatus
());
if
(
this
.
currentWindowIndex
!=
currentWindowIndex
&&
pendingSeekCount
==
0
)
{
this
.
currentWindowIndex
=
currentWindowIndex
;
for
(
EventListener
listener
:
listeners
)
{
listener
.
onPositionDiscontinuity
(
DISCONTINUITY_REASON_PERIOD_TRANSITION
);
}
notificationsBatch
.
add
(
new
ListenerNotificationTask
(
listener
->
listener
.
onPositionDiscontinuity
(
DISCONTINUITY_REASON_PERIOD_TRANSITION
)));
}
if
(
updateTracksAndSelections
())
{
for
(
EventListener
listener
:
listeners
)
{
listener
.
onTracksChanged
(
currentTrackGroups
,
currentTrackSelection
);
}
notificationsBatch
.
add
(
new
ListenerNotificationTask
(
listener
->
listener
.
onTracksChanged
(
currentTrackGroups
,
currentTrackSelection
)));
}
maybeUpdateTimelineAndNotify
();
flushNotifications
();
}
private
void
maybeUpdateTimelineAndNotify
()
{
...
...
@@ -561,9 +582,10 @@ public final class CastPlayer extends BasePlayer {
@Player
.
TimelineChangeReason
int
reason
=
waitingForInitialTimeline
?
Player
.
TIMELINE_CHANGE_REASON_PREPARED
:
Player
.
TIMELINE_CHANGE_REASON_DYNAMIC
;
waitingForInitialTimeline
=
false
;
for
(
EventListener
listener
:
listeners
)
{
listener
.
onTimelineChanged
(
currentTimeline
,
null
,
reason
);
}
notificationsBatch
.
add
(
new
ListenerNotificationTask
(
listener
->
listener
.
onTimelineChanged
(
currentTimeline
,
/* manifest= */
null
,
reason
)));
}
}
...
...
@@ -701,16 +723,6 @@ public final class CastPlayer extends BasePlayer {
}
}
/**
* Retrieves the current item index from {@code mediaStatus} and maps it into a window index. If
* there is no media session, returns 0.
*/
private
static
int
fetchCurrentWindowIndex
(
@Nullable
MediaStatus
mediaStatus
)
{
Integer
currentItemId
=
mediaStatus
!=
null
?
mediaStatus
.
getIndexById
(
mediaStatus
.
getCurrentItemId
())
:
null
;
return
currentItemId
!=
null
?
currentItemId
:
0
;
}
private
static
boolean
isTrackActive
(
long
id
,
long
[]
activeTrackIds
)
{
for
(
long
activeTrackId
:
activeTrackIds
)
{
if
(
activeTrackId
==
id
)
{
...
...
@@ -826,7 +838,23 @@ public final class CastPlayer extends BasePlayer {
}
// Result callbacks hooks.
// Internal methods.
private
void
flushNotifications
()
{
boolean
recursiveNotification
=
!
ongoingNotificationsTasks
.
isEmpty
();
ongoingNotificationsTasks
.
addAll
(
notificationsBatch
);
notificationsBatch
.
clear
();
if
(
recursiveNotification
)
{
// This will be handled once the current notification task is finished.
return
;
}
while
(!
ongoingNotificationsTasks
.
isEmpty
())
{
ongoingNotificationsTasks
.
peekFirst
().
execute
();
ongoingNotificationsTasks
.
removeFirst
();
}
}
// Internal classes.
private
final
class
SeekResultCallback
implements
ResultCallback
<
MediaChannelResult
>
{
...
...
@@ -840,9 +868,25 @@ public final class CastPlayer extends BasePlayer {
if
(--
pendingSeekCount
==
0
)
{
pendingSeekWindowIndex
=
C
.
INDEX_UNSET
;
pendingSeekPositionMs
=
C
.
TIME_UNSET
;
for
(
EventListener
listener
:
listeners
)
{
listener
.
onSeekProcessed
();
}
notificationsBatch
.
add
(
new
ListenerNotificationTask
(
EventListener:
:
onSeekProcessed
));
flushNotifications
();
}
}
}
private
final
class
ListenerNotificationTask
{
private
final
Iterator
<
ListenerHolder
>
listenersSnapshot
;
private
final
ListenerInvocation
listenerInvocation
;
private
ListenerNotificationTask
(
ListenerInvocation
listenerInvocation
)
{
this
.
listenersSnapshot
=
listeners
.
iterator
();
this
.
listenerInvocation
=
listenerInvocation
;
}
public
void
execute
()
{
while
(
listenersSnapshot
.
hasNext
())
{
listenersSnapshot
.
next
().
invoke
(
listenerInvocation
);
}
}
}
...
...
extensions/ima/build.gradle
View file @
f6297f4f
...
...
@@ -34,6 +34,7 @@ android {
dependencies
{
api
'com.google.ads.interactivemedia.v3:interactivemedia:3.11.2'
implementation
project
(
modulePrefix
+
'library-core'
)
implementation
'androidx.annotation:annotation:1.0.2'
implementation
'com.google.android.gms:play-services-ads-identifier:16.0.0'
testImplementation
project
(
modulePrefix
+
'testutils-robolectric'
)
}
...
...
extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java
View file @
f6297f4f
...
...
@@ -1054,13 +1054,8 @@ public final class ImaAdsLoader
long
contentPositionMs
=
player
.
getCurrentPosition
();
int
adGroupIndexForPosition
=
adPlaybackState
.
getAdGroupIndexForPositionUs
(
C
.
msToUs
(
contentPositionMs
));
if
(
adGroupIndexForPosition
==
0
)
{
podIndexOffset
=
0
;
}
else
if
(
adGroupIndexForPosition
==
C
.
INDEX_UNSET
)
{
// There is no preroll and midroll pod indices start at 1.
podIndexOffset
=
-
1
;
}
else
/* adGroupIndexForPosition > 0 */
{
// Skip ad groups before the one at or immediately before the playback position.
if
(
adGroupIndexForPosition
>
0
&&
adGroupIndexForPosition
!=
C
.
INDEX_UNSET
)
{
// Skip any ad groups before the one at or immediately before the playback position.
for
(
int
i
=
0
;
i
<
adGroupIndexForPosition
;
i
++)
{
adPlaybackState
=
adPlaybackState
.
withSkippedAdGroup
(
i
);
}
...
...
@@ -1070,9 +1065,18 @@ public final class ImaAdsLoader
long
adGroupBeforeTimeUs
=
adGroupTimesUs
[
adGroupIndexForPosition
-
1
];
double
midpointTimeUs
=
(
adGroupForPositionTimeUs
+
adGroupBeforeTimeUs
)
/
2
d
;
adsRenderingSettings
.
setPlayAdsAfterTime
(
midpointTimeUs
/
C
.
MICROS_PER_SECOND
);
}
// We're removing one or more ads, which means that the earliest ad (if any) will be a
// midroll/postroll. Midroll pod indices start at 1.
// IMA indexes any remaining midroll ad pods from 1. A preroll (if present) has index 0.
// Store an index offset as we want to index all ads (including skipped ones) from 0.
if
(
adGroupIndexForPosition
==
0
&&
adGroupTimesUs
[
0
]
==
0
)
{
// We are playing a preroll.
podIndexOffset
=
0
;
}
else
if
(
adGroupIndexForPosition
==
C
.
INDEX_UNSET
)
{
// There's no ad to play which means there's no preroll.
podIndexOffset
=
-
1
;
}
else
{
// We are playing a midroll and any ads before it were skipped.
podIndexOffset
=
adGroupIndexForPosition
-
1
;
}
...
...
extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java
View file @
f6297f4f
...
...
@@ -172,7 +172,7 @@ public final class MediaSessionConnector {
ResultReceiver
cb
);
}
/** Interface to which playback preparation actions are delegated. */
/** Interface to which playback preparation a
nd play a
ctions are delegated. */
public
interface
PlaybackPreparer
extends
CommandReceiver
{
long
ACTIONS
=
...
...
@@ -197,14 +197,36 @@ public final class MediaSessionConnector {
* @return The bitmask of the supported media actions.
*/
long
getSupportedPrepareActions
();
/** See {@link MediaSessionCompat.Callback#onPrepare()}. */
void
onPrepare
();
/** See {@link MediaSessionCompat.Callback#onPrepareFromMediaId(String, Bundle)}. */
void
onPrepareFromMediaId
(
String
mediaId
,
Bundle
extras
);
/** See {@link MediaSessionCompat.Callback#onPrepareFromSearch(String, Bundle)}. */
void
onPrepareFromSearch
(
String
query
,
Bundle
extras
);
/** See {@link MediaSessionCompat.Callback#onPrepareFromUri(Uri, Bundle)}. */
void
onPrepareFromUri
(
Uri
uri
,
Bundle
extras
);
/**
* See {@link MediaSessionCompat.Callback#onPrepare()}.
*
* @param playWhenReady Whether playback should be started after preparation.
*/
void
onPrepare
(
boolean
playWhenReady
);
/**
* See {@link MediaSessionCompat.Callback#onPrepareFromMediaId(String, Bundle)}.
*
* @param mediaId The media id of the media item to be prepared.
* @param playWhenReady Whether playback should be started after preparation.
* @param extras A {@link Bundle} of extras passed by the media controller.
*/
void
onPrepareFromMediaId
(
String
mediaId
,
boolean
playWhenReady
,
Bundle
extras
);
/**
* See {@link MediaSessionCompat.Callback#onPrepareFromSearch(String, Bundle)}.
*
* @param query The search query.
* @param playWhenReady Whether playback should be started after preparation.
* @param extras A {@link Bundle} of extras passed by the media controller.
*/
void
onPrepareFromSearch
(
String
query
,
boolean
playWhenReady
,
Bundle
extras
);
/**
* See {@link MediaSessionCompat.Callback#onPrepareFromUri(Uri, Bundle)}.
*
* @param uri The {@link Uri} of the media item to be prepared.
* @param playWhenReady Whether playback should be started after preparation.
* @param extras A {@link Bundle} of extras passed by the media controller.
*/
void
onPrepareFromUri
(
Uri
uri
,
boolean
playWhenReady
,
Bundle
extras
);
}
/**
...
...
@@ -834,13 +856,6 @@ public final class MediaSessionConnector {
return
player
!=
null
&&
mediaButtonEventHandler
!=
null
;
}
private
void
stopPlayerForPrepare
(
boolean
playWhenReady
)
{
if
(
player
!=
null
)
{
player
.
stop
();
player
.
setPlayWhenReady
(
playWhenReady
);
}
}
private
void
rewind
(
Player
player
)
{
if
(
player
.
isCurrentWindowSeekable
()
&&
rewindMs
>
0
)
{
seekTo
(
player
,
player
.
getCurrentPosition
()
-
rewindMs
);
...
...
@@ -1047,12 +1062,12 @@ public final class MediaSessionConnector {
if
(
canDispatchPlaybackAction
(
PlaybackStateCompat
.
ACTION_PLAY
))
{
if
(
player
.
getPlaybackState
()
==
Player
.
STATE_IDLE
)
{
if
(
playbackPreparer
!=
null
)
{
playbackPreparer
.
onPrepare
();
playbackPreparer
.
onPrepare
(
/* playWhenReady= */
true
);
}
}
else
if
(
player
.
getPlaybackState
()
==
Player
.
STATE_ENDED
)
{
controlDispatcher
.
dispatchSeekTo
(
player
,
player
.
getCurrentWindowIndex
(),
C
.
TIME_UNSET
);
controlDispatcher
.
dispatchSetPlayWhenReady
(
player
,
/* playWhenReady= */
true
);
}
controlDispatcher
.
dispatchSetPlayWhenReady
(
player
,
/* playWhenReady= */
true
);
}
}
...
...
@@ -1182,56 +1197,49 @@ public final class MediaSessionConnector {
@Override
public
void
onPrepare
()
{
if
(
canDispatchToPlaybackPreparer
(
PlaybackStateCompat
.
ACTION_PREPARE
))
{
stopPlayerForPrepare
(
/* playWhenReady= */
false
);
playbackPreparer
.
onPrepare
();
playbackPreparer
.
onPrepare
(
/* playWhenReady= */
false
);
}
}
@Override
public
void
onPrepareFromMediaId
(
String
mediaId
,
Bundle
extras
)
{
if
(
canDispatchToPlaybackPreparer
(
PlaybackStateCompat
.
ACTION_PREPARE_FROM_MEDIA_ID
))
{
stopPlayerForPrepare
(
/* playWhenReady= */
false
);
playbackPreparer
.
onPrepareFromMediaId
(
mediaId
,
extras
);
playbackPreparer
.
onPrepareFromMediaId
(
mediaId
,
/* playWhenReady= */
false
,
extras
);
}
}
@Override
public
void
onPrepareFromSearch
(
String
query
,
Bundle
extras
)
{
if
(
canDispatchToPlaybackPreparer
(
PlaybackStateCompat
.
ACTION_PREPARE_FROM_SEARCH
))
{
stopPlayerForPrepare
(
/* playWhenReady= */
false
);
playbackPreparer
.
onPrepareFromSearch
(
query
,
extras
);
playbackPreparer
.
onPrepareFromSearch
(
query
,
/* playWhenReady= */
false
,
extras
);
}
}
@Override
public
void
onPrepareFromUri
(
Uri
uri
,
Bundle
extras
)
{
if
(
canDispatchToPlaybackPreparer
(
PlaybackStateCompat
.
ACTION_PREPARE_FROM_URI
))
{
stopPlayerForPrepare
(
/* playWhenReady= */
false
);
playbackPreparer
.
onPrepareFromUri
(
uri
,
extras
);
playbackPreparer
.
onPrepareFromUri
(
uri
,
/* playWhenReady= */
false
,
extras
);
}
}
@Override
public
void
onPlayFromMediaId
(
String
mediaId
,
Bundle
extras
)
{
if
(
canDispatchToPlaybackPreparer
(
PlaybackStateCompat
.
ACTION_PLAY_FROM_MEDIA_ID
))
{
stopPlayerForPrepare
(
/* playWhenReady= */
true
);
playbackPreparer
.
onPrepareFromMediaId
(
mediaId
,
extras
);
playbackPreparer
.
onPrepareFromMediaId
(
mediaId
,
/* playWhenReady= */
true
,
extras
);
}
}
@Override
public
void
onPlayFromSearch
(
String
query
,
Bundle
extras
)
{
if
(
canDispatchToPlaybackPreparer
(
PlaybackStateCompat
.
ACTION_PLAY_FROM_SEARCH
))
{
stopPlayerForPrepare
(
/* playWhenReady= */
true
);
playbackPreparer
.
onPrepareFromSearch
(
query
,
extras
);
playbackPreparer
.
onPrepareFromSearch
(
query
,
/* playWhenReady= */
true
,
extras
);
}
}
@Override
public
void
onPlayFromUri
(
Uri
uri
,
Bundle
extras
)
{
if
(
canDispatchToPlaybackPreparer
(
PlaybackStateCompat
.
ACTION_PLAY_FROM_URI
))
{
stopPlayerForPrepare
(
/* playWhenReady= */
true
);
playbackPreparer
.
onPrepareFromUri
(
uri
,
extras
);
playbackPreparer
.
onPrepareFromUri
(
uri
,
/* playWhenReady= */
true
,
extras
);
}
}
...
...
extensions/vp9/README.md
View file @
f6297f4f
...
...
@@ -29,6 +29,7 @@ VP9_EXT_PATH="${EXOPLAYER_ROOT}/extensions/vp9/src/main"
```
*
Download the
[
Android NDK
][]
and set its location in an environment variable.
The build configuration has been tested with Android NDK r19c.
```
NDK_PATH="<path to Android NDK>"
...
...
@@ -54,7 +55,7 @@ git checkout tags/v1.8.0 -b v1.8.0
```
cd ${VP9_EXT_PATH}/jni && \
./generate_libvpx_android_configs.sh
"${NDK_PATH}"
./generate_libvpx_android_configs.sh
```
*
Build the JNI native libraries from the command line:
...
...
@@ -66,7 +67,6 @@ ${NDK_PATH}/ndk-build APP_ABI=all -j4
[
top level README
]:
https://github.com/google/ExoPlayer/blob/release-v2/README.md
[
Android NDK
]:
https://developer.android.com/tools/sdk/ndk/index.html
[
#3520
]:
https://github.com/google/ExoPlayer/issues/3520
## Notes ##
...
...
extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxOutputBuffer.java
View file @
f6297f4f
...
...
@@ -60,8 +60,8 @@ public final class VpxOutputBuffer extends OutputBuffer {
* Initializes the buffer.
*
* @param timeUs The presentation timestamp for the buffer, in microseconds.
* @param mode The output mode. One of {@link VpxDecoder#OUTPUT_MODE_NONE}
and
{@link
* VpxDecoder#OUTPUT_MODE_YUV}.
* @param mode The output mode. One of {@link VpxDecoder#OUTPUT_MODE_NONE}
,
{@link
* VpxDecoder#OUTPUT_MODE_YUV}
and {@link VpxDecoder#OUTPUT_MODE_SURFACE_YUV}
.
*/
public
void
init
(
long
timeUs
,
int
mode
)
{
this
.
timeUs
=
timeUs
;
...
...
@@ -110,6 +110,15 @@ public final class VpxOutputBuffer extends OutputBuffer {
return
true
;
}
/**
* Configures the buffer for the given frame dimensions when passing actual frame data via {@link
* #decoderPrivate}. Called via JNI after decoding completes.
*/
public
void
initForPrivateFrame
(
int
width
,
int
height
)
{
this
.
width
=
width
;
this
.
height
=
height
;
}
private
void
initData
(
int
size
)
{
if
(
data
==
null
||
data
.
capacity
()
<
size
)
{
data
=
ByteBuffer
.
allocateDirect
(
size
);
...
...
extensions/vp9/src/main/jni/Application.mk
View file @
f6297f4f
...
...
@@ -15,6 +15,6 @@
#
APP_OPTIM := release
APP_STL :=
gnustl
_static
APP_STL :=
c++
_static
APP_CPPFLAGS := -frtti
APP_PLATFORM := android-
9
APP_PLATFORM := android-
16
extensions/vp9/src/main/jni/generate_libvpx_android_configs.sh
View file @
f6297f4f
...
...
@@ -20,46 +20,33 @@
set
-e
if
[
$#
-ne
1
]
;
then
echo
"Usage:
${
0
}
<path_to_android_ndk>
"
if
[
$#
-ne
0
]
;
then
echo
"Usage:
${
0
}
"
exit
fi
ndk
=
"
${
1
}
"
shift
1
# configuration parameters common to all architectures
common_params
=
"--disable-examples --disable-docs --enable-realtime-only"
common_params+
=
" --disable-vp8 --disable-vp9-encoder --disable-webm-io"
common_params+
=
" --disable-libyuv --disable-runtime-cpu-detect"
common_params+
=
" --enable-external-build"
# configuration parameters for various architectures
arch[0]
=
"armeabi-v7a"
config[0]
=
"--target=armv7-android-gcc --sdk-path=
$ndk
--enable-neon"
config[0]+
=
" --enable-neon-asm"
config[0]
=
"--target=armv7-android-gcc --enable-neon --enable-neon-asm"
arch[1]
=
"armeabi"
config[1]
=
"--target=armv7-android-gcc --sdk-path=
$ndk
--disable-neon"
config[1]+
=
" --disable-neon-asm"
arch[1]
=
"x86"
config[1]
=
"--force-target=x86-android-gcc --disable-sse2"
config[1]+
=
" --disable-sse3 --disable-ssse3 --disable-sse4_1 --disable-avx"
config[1]+
=
" --disable-avx2 --enable-pic"
arch[2]
=
"
mips
"
config[2]
=
"--force-target=
mips32-android-gcc --sdk-path=
$ndk
"
arch[2]
=
"
arm64-v8a
"
config[2]
=
"--force-target=
armv8-android-gcc --enable-neon
"
arch[3]
=
"x86"
config[3]
=
"--force-target=x86
-android-gcc --sdk-path=
$ndk
--disable-sse2"
arch[3]
=
"x86
_64
"
config[3]
=
"--force-target=x86
_64-android-gcc
--disable-sse2"
config[3]+
=
" --disable-sse3 --disable-ssse3 --disable-sse4_1 --disable-avx"
config[3]+
=
" --disable-avx2 --enable-pic"
arch[4]
=
"arm64-v8a"
config[4]
=
"--force-target=armv8-android-gcc --sdk-path=
$ndk
--enable-neon"
arch[5]
=
"x86_64"
config[5]
=
"--force-target=x86_64-android-gcc --sdk-path=
$ndk
--disable-sse2"
config[5]+
=
" --disable-sse3 --disable-ssse3 --disable-sse4_1 --disable-avx"
config[5]+
=
" --disable-avx2 --enable-pic --disable-neon --disable-neon-asm"
arch[6]
=
"mips64"
config[6]
=
"--force-target=mips64-android-gcc --sdk-path=
$ndk
"
config[3]+
=
" --disable-avx2 --enable-pic --disable-neon --disable-neon-asm"
limit
=
$((${#
arch
[@]
}
-
1
))
...
...
@@ -102,10 +89,7 @@ for i in $(seq 0 ${limit}); do
# configure and make
echo
"build_android_configs: "
echo
"configure
${
config
[
${
i
}
]
}
${
common_params
}
"
../../libvpx/configure
${
config
[
${
i
}
]
}
${
common_params
}
--extra-cflags
=
"
\
-isystem
$ndk
/sysroot/usr/include/arm-linux-androideabi
\
-isystem
$ndk
/sysroot/usr/include
\
"
../../libvpx/configure
${
config
[
${
i
}
]
}
${
common_params
}
rm
-f
libvpx_srcs.txt
for
f
in
${
allowed_files
}
;
do
# the build system supports multiple different configurations. avoid
...
...
extensions/vp9/src/main/jni/vpx_jni.cc
View file @
f6297f4f
...
...
@@ -60,6 +60,7 @@
// JNI references for VpxOutputBuffer class.
static
jmethodID
initForYuvFrame
;
static
jmethodID
initForPrivateFrame
;
static
jfieldID
dataField
;
static
jfieldID
outputModeField
;
static
jfieldID
decoderPrivateField
;
...
...
@@ -481,6 +482,8 @@ DECODER_FUNC(jlong, vpxInit, jboolean disableLoopFilter,
"com/google/android/exoplayer2/ext/vp9/VpxOutputBuffer"
);
initForYuvFrame
=
env
->
GetMethodID
(
outputBufferClass
,
"initForYuvFrame"
,
"(IIIII)Z"
);
initForPrivateFrame
=
env
->
GetMethodID
(
outputBufferClass
,
"initForPrivateFrame"
,
"(II)V"
);
dataField
=
env
->
GetFieldID
(
outputBufferClass
,
"data"
,
"Ljava/nio/ByteBuffer;"
);
outputModeField
=
env
->
GetFieldID
(
outputBufferClass
,
"mode"
,
"I"
);
...
...
@@ -602,6 +605,10 @@ DECODER_FUNC(jint, vpxGetFrame, jlong jContext, jobject jOutputBuffer) {
}
jfb
->
d_w
=
img
->
d_w
;
jfb
->
d_h
=
img
->
d_h
;
env
->
CallVoidMethod
(
jOutputBuffer
,
initForPrivateFrame
,
img
->
d_w
,
img
->
d_h
);
if
(
env
->
ExceptionCheck
())
{
return
-
1
;
}
env
->
SetIntField
(
jOutputBuffer
,
decoderPrivateField
,
id
+
kDecoderPrivateBase
);
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/C.java
View file @
f6297f4f
...
...
@@ -146,8 +146,8 @@ public final class C {
* {@link #ENCODING_INVALID}, {@link #ENCODING_PCM_8BIT}, {@link #ENCODING_PCM_16BIT}, {@link
* #ENCODING_PCM_24BIT}, {@link #ENCODING_PCM_32BIT}, {@link #ENCODING_PCM_FLOAT}, {@link
* #ENCODING_PCM_MU_LAW}, {@link #ENCODING_PCM_A_LAW}, {@link #ENCODING_AC3}, {@link
* #ENCODING_E_AC3}, {@link #ENCODING_
AC4}, {@link #ENCODING_DTS}, {@link #ENCODING_DTS_HD} or
* {@link #ENCODING_DOLBY_TRUEHD}.
* #ENCODING_E_AC3}, {@link #ENCODING_
E_AC3_JOC}, {@link #ENCODING_AC4}, {@link #ENCODING_DTS},
* {@link #ENCODING_D
TS_HD} or {@link #ENCODING_D
OLBY_TRUEHD}.
*/
@Documented
@Retention
(
RetentionPolicy
.
SOURCE
)
...
...
@@ -163,6 +163,7 @@ public final class C {
ENCODING_PCM_A_LAW
,
ENCODING_AC3
,
ENCODING_E_AC3
,
ENCODING_E_AC3_JOC
,
ENCODING_AC4
,
ENCODING_DTS
,
ENCODING_DTS_HD
,
...
...
@@ -210,6 +211,8 @@ public final class C {
public
static
final
int
ENCODING_AC3
=
AudioFormat
.
ENCODING_AC3
;
/** @see AudioFormat#ENCODING_E_AC3 */
public
static
final
int
ENCODING_E_AC3
=
AudioFormat
.
ENCODING_E_AC3
;
/** @see AudioFormat#ENCODING_E_AC3_JOC */
public
static
final
int
ENCODING_E_AC3_JOC
=
AudioFormat
.
ENCODING_E_AC3_JOC
;
/** @see AudioFormat#ENCODING_AC4 */
public
static
final
int
ENCODING_AC4
=
AudioFormat
.
ENCODING_AC4
;
/** @see AudioFormat#ENCODING_DTS */
...
...
library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java
View file @
f6297f4f
...
...
@@ -24,6 +24,7 @@ import androidx.annotation.Nullable;
import
com.google.android.exoplayer2.audio.AudioCapabilities
;
import
com.google.android.exoplayer2.audio.AudioProcessor
;
import
com.google.android.exoplayer2.audio.AudioRendererEventListener
;
import
com.google.android.exoplayer2.audio.DefaultAudioSink
;
import
com.google.android.exoplayer2.audio.MediaCodecAudioRenderer
;
import
com.google.android.exoplayer2.drm.DrmSessionManager
;
import
com.google.android.exoplayer2.drm.FrameworkMediaCrypto
;
...
...
@@ -90,6 +91,7 @@ public class DefaultRenderersFactory implements RenderersFactory {
@ExtensionRendererMode
private
int
extensionRendererMode
;
private
long
allowedVideoJoiningTimeMs
;
private
boolean
playClearSamplesWithoutKeys
;
private
boolean
enableDecoderFallback
;
private
MediaCodecSelector
mediaCodecSelector
;
/** @param context A {@link Context}. */
...
...
@@ -203,6 +205,19 @@ public class DefaultRenderersFactory implements RenderersFactory {
}
/**
* Sets whether to enable fallback to lower-priority decoders if decoder initialization fails.
* This may result in using a decoder that is less efficient or slower than the primary decoder.
*
* @param enableDecoderFallback Whether to enable fallback to lower-priority decoders if decoder
* initialization fails.
* @return This factory, for convenience.
*/
public
DefaultRenderersFactory
setEnableDecoderFallback
(
boolean
enableDecoderFallback
)
{
this
.
enableDecoderFallback
=
enableDecoderFallback
;
return
this
;
}
/**
* Sets a {@link MediaCodecSelector} for use by {@link MediaCodec} based renderers.
*
* <p>The default value is {@link MediaCodecSelector#DEFAULT}.
...
...
@@ -248,6 +263,7 @@ public class DefaultRenderersFactory implements RenderersFactory {
mediaCodecSelector
,
drmSessionManager
,
playClearSamplesWithoutKeys
,
enableDecoderFallback
,
eventHandler
,
videoRendererEventListener
,
allowedVideoJoiningTimeMs
,
...
...
@@ -258,6 +274,7 @@ public class DefaultRenderersFactory implements RenderersFactory {
mediaCodecSelector
,
drmSessionManager
,
playClearSamplesWithoutKeys
,
enableDecoderFallback
,
buildAudioProcessors
(),
eventHandler
,
audioRendererEventListener
,
...
...
@@ -282,6 +299,9 @@ public class DefaultRenderersFactory implements RenderersFactory {
* @param playClearSamplesWithoutKeys Whether renderers are permitted to play clear regions of
* encrypted media prior to having obtained the keys necessary to decrypt encrypted regions of
* the media.
* @param enableDecoderFallback Whether to enable fallback to lower-priority decoders if decoder
* initialization fails. This may result in using a decoder that is slower/less efficient than
* the primary decoder.
* @param eventHandler A handler associated with the main thread's looper.
* @param eventListener An event listener.
* @param allowedVideoJoiningTimeMs The maximum duration for which video renderers can attempt to
...
...
@@ -294,6 +314,7 @@ public class DefaultRenderersFactory implements RenderersFactory {
MediaCodecSelector
mediaCodecSelector
,
@Nullable
DrmSessionManager
<
FrameworkMediaCrypto
>
drmSessionManager
,
boolean
playClearSamplesWithoutKeys
,
boolean
enableDecoderFallback
,
Handler
eventHandler
,
VideoRendererEventListener
eventListener
,
long
allowedVideoJoiningTimeMs
,
...
...
@@ -305,6 +326,7 @@ public class DefaultRenderersFactory implements RenderersFactory {
allowedVideoJoiningTimeMs
,
drmSessionManager
,
playClearSamplesWithoutKeys
,
enableDecoderFallback
,
eventHandler
,
eventListener
,
MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY
));
...
...
@@ -356,6 +378,9 @@ public class DefaultRenderersFactory implements RenderersFactory {
* @param playClearSamplesWithoutKeys Whether renderers are permitted to play clear regions of
* encrypted media prior to having obtained the keys necessary to decrypt encrypted regions of
* the media.
* @param enableDecoderFallback Whether to enable fallback to lower-priority decoders if decoder
* initialization fails. This may result in using a decoder that is slower/less efficient than
* the primary decoder.
* @param audioProcessors An array of {@link AudioProcessor}s that will process PCM audio buffers
* before output. May be empty.
* @param eventHandler A handler to use when invoking event listeners and outputs.
...
...
@@ -368,6 +393,7 @@ public class DefaultRenderersFactory implements RenderersFactory {
MediaCodecSelector
mediaCodecSelector
,
@Nullable
DrmSessionManager
<
FrameworkMediaCrypto
>
drmSessionManager
,
boolean
playClearSamplesWithoutKeys
,
boolean
enableDecoderFallback
,
AudioProcessor
[]
audioProcessors
,
Handler
eventHandler
,
AudioRendererEventListener
eventListener
,
...
...
@@ -378,10 +404,10 @@ public class DefaultRenderersFactory implements RenderersFactory {
mediaCodecSelector
,
drmSessionManager
,
playClearSamplesWithoutKeys
,
enableDecoderFallback
,
eventHandler
,
eventListener
,
AudioCapabilities
.
getCapabilities
(
context
),
audioProcessors
));
new
DefaultAudioSink
(
AudioCapabilities
.
getCapabilities
(
context
),
audioProcessors
)));
if
(
extensionRendererMode
==
EXTENSION_RENDERER_MODE_OFF
)
{
return
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java
View file @
f6297f4f
...
...
@@ -510,7 +510,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
@Override
public
long
getTotalBufferedDuration
()
{
return
Math
.
max
(
0
,
C
.
usToMs
(
playbackInfo
.
totalBufferedDurationUs
)
);
return
C
.
usToMs
(
playbackInfo
.
totalBufferedDurationUs
);
}
@Override
...
...
library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java
View file @
f6297f4f
...
...
@@ -729,13 +729,20 @@ import java.util.concurrent.atomic.AtomicBoolean;
newPlayingPeriodHolder
=
queue
.
advancePlayingPeriod
();
}
// Disable all the renderers if the period being played is changing, or if forced.
if
(
oldPlayingPeriodHolder
!=
newPlayingPeriodHolder
||
forceDisableRenderers
)
{
// Disable all renderers if the period being played is changing, if the seek results in negative
// renderer timestamps, or if forced.
if
(
forceDisableRenderers
||
oldPlayingPeriodHolder
!=
newPlayingPeriodHolder
||
(
newPlayingPeriodHolder
!=
null
&&
newPlayingPeriodHolder
.
toRendererTime
(
periodPositionUs
)
<
0
))
{
for
(
Renderer
renderer
:
enabledRenderers
)
{
disableRenderer
(
renderer
);
}
enabledRenderers
=
new
Renderer
[
0
];
oldPlayingPeriodHolder
=
null
;
if
(
newPlayingPeriodHolder
!=
null
)
{
newPlayingPeriodHolder
.
setRendererOffset
(
/* rendererPositionOffsetUs= */
0
);
}
}
// Update the holders.
...
...
@@ -1798,9 +1805,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
private
long
getTotalBufferedDurationUs
(
long
bufferedPositionInLoadingPeriodUs
)
{
MediaPeriodHolder
loadingPeriodHolder
=
queue
.
getLoadingPeriod
();
return
loadingPeriodHolder
==
null
?
0
:
bufferedPositionInLoadingPeriodUs
-
loadingPeriodHolder
.
toPeriodTime
(
rendererPositionUs
);
if
(
loadingPeriodHolder
==
null
)
{
return
0
;
}
long
totalBufferedDurationUs
=
bufferedPositionInLoadingPeriodUs
-
loadingPeriodHolder
.
toPeriodTime
(
rendererPositionUs
);
return
Math
.
max
(
0
,
totalBufferedDurationUs
);
}
private
void
updateLoadControlTrackSelection
(
...
...
library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java
View file @
f6297f4f
...
...
@@ -29,11 +29,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.10.
1
"
;
public
static
final
String
VERSION
=
"2.10.
2
"
;
/** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public
static
final
String
VERSION_SLASHY
=
"ExoPlayerLib/2.10.
1
"
;
public
static
final
String
VERSION_SLASHY
=
"ExoPlayerLib/2.10.
2
"
;
/**
* The version of the library expressed as an integer, for example 1002003.
...
...
@@ -43,7 +43,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
=
201000
1
;
public
static
final
int
VERSION_INT
=
201000
2
;
/**
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}
...
...
library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java
View file @
f6297f4f
...
...
@@ -67,8 +67,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
* Creates a new holder with information required to play it as part of a timeline.
*
* @param rendererCapabilities The renderer capabilities.
* @param rendererPositionOffsetUs The time offset of the start of the media period to provide to
* renderers.
* @param rendererPositionOffsetUs The renderer time of the start of the period, in microseconds.
* @param trackSelector The track selector.
* @param allocator The allocator.
* @param mediaSource The media source that produced the media period.
...
...
@@ -82,7 +81,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
MediaSource
mediaSource
,
MediaPeriodInfo
info
)
{
this
.
rendererCapabilities
=
rendererCapabilities
;
this
.
rendererPositionOffsetUs
=
rendererPositionOffsetUs
-
info
.
startPositionUs
;
this
.
rendererPositionOffsetUs
=
rendererPositionOffsetUs
;
this
.
trackSelector
=
trackSelector
;
this
.
mediaSource
=
mediaSource
;
this
.
uid
=
info
.
id
.
periodUid
;
...
...
@@ -115,6 +114,15 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
return
rendererPositionOffsetUs
;
}
/**
* Sets the renderer time of the start of the period, in microseconds.
*
* @param rendererPositionOffsetUs The new renderer position offset, in microseconds.
*/
public
void
setRendererOffset
(
long
rendererPositionOffsetUs
)
{
this
.
rendererPositionOffsetUs
=
rendererPositionOffsetUs
;
}
/** Returns start position of period in renderer time. */
public
long
getStartPositionRendererTime
()
{
return
info
.
startPositionUs
+
rendererPositionOffsetUs
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java
View file @
f6297f4f
...
...
@@ -144,8 +144,8 @@ import com.google.android.exoplayer2.util.Assertions;
MediaPeriodInfo
info
)
{
long
rendererPositionOffsetUs
=
loading
==
null
?
info
.
startPositionUs
:
(
loading
.
getRendererOffset
()
+
loading
.
info
.
durationUs
);
?
(
info
.
id
.
isAd
()
?
info
.
contentPositionUs
:
0
)
:
(
loading
.
getRendererOffset
()
+
loading
.
info
.
durationUs
-
info
.
startPositionUs
);
MediaPeriodHolder
newPeriodHolder
=
new
MediaPeriodHolder
(
rendererCapabilities
,
...
...
library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java
View file @
f6297f4f
...
...
@@ -1125,6 +1125,7 @@ public final class DefaultAudioSink implements AudioSink {
case
C
.
ENCODING_AC3
:
return
640
*
1000
/
8
;
case
C
.
ENCODING_E_AC3
:
case
C
.
ENCODING_E_AC3_JOC
:
return
6144
*
1000
/
8
;
case
C
.
ENCODING_AC4
:
return
2688
*
1000
/
8
;
...
...
@@ -1154,7 +1155,7 @@ public final class DefaultAudioSink implements AudioSink {
return
DtsUtil
.
parseDtsAudioSampleCount
(
buffer
);
}
else
if
(
encoding
==
C
.
ENCODING_AC3
)
{
return
Ac3Util
.
getAc3SyncframeAudioSampleCount
();
}
else
if
(
encoding
==
C
.
ENCODING_E_AC3
)
{
}
else
if
(
encoding
==
C
.
ENCODING_E_AC3
||
encoding
==
C
.
ENCODING_E_AC3_JOC
)
{
return
Ac3Util
.
parseEAc3SyncframeAudioSampleCount
(
buffer
);
}
else
if
(
encoding
==
C
.
ENCODING_AC4
)
{
return
Ac4Util
.
parseAc4SyncframeAudioSampleCount
(
buffer
);
...
...
@@ -1177,11 +1178,10 @@ public final class DefaultAudioSink implements AudioSink {
@TargetApi
(
21
)
private
int
writeNonBlockingWithAvSyncV21
(
AudioTrack
audioTrack
,
ByteBuffer
buffer
,
int
size
,
long
presentationTimeUs
)
{
// TODO: Uncomment this when [Internal ref: b/33627517] is clarified or fixed.
// if (Util.SDK_INT >= 23) {
// // The underlying platform AudioTrack writes AV sync headers directly.
// return audioTrack.write(buffer, size, WRITE_NON_BLOCKING, presentationTimeUs * 1000);
// }
if
(
Util
.
SDK_INT
>=
26
)
{
// The underlying platform AudioTrack writes AV sync headers directly.
return
audioTrack
.
write
(
buffer
,
size
,
WRITE_NON_BLOCKING
,
presentationTimeUs
*
1000
);
}
if
(
avSyncHeader
==
null
)
{
avSyncHeader
=
ByteBuffer
.
allocate
(
16
);
avSyncHeader
.
order
(
ByteOrder
.
BIG_ENDIAN
);
...
...
library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java
View file @
f6297f4f
...
...
@@ -245,12 +245,50 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
@Nullable
Handler
eventHandler
,
@Nullable
AudioRendererEventListener
eventListener
,
AudioSink
audioSink
)
{
this
(
context
,
mediaCodecSelector
,
drmSessionManager
,
playClearSamplesWithoutKeys
,
/* enableDecoderFallback= */
false
,
eventHandler
,
eventListener
,
audioSink
);
}
/**
* @param context A context.
* @param mediaCodecSelector A decoder selector.
* @param drmSessionManager For use with encrypted content. May be null if support for encrypted
* content is not required.
* @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
* For example a media file may start with a short clear region so as to allow playback to
* begin in parallel with key acquisition. This parameter specifies whether the renderer is
* permitted to play clear regions of encrypted media files before {@code drmSessionManager}
* has obtained the keys necessary to decrypt encrypted regions of the media.
* @param enableDecoderFallback Whether to enable fallback to lower-priority decoders if decoder
* initialization fails. This may result in using a decoder that is slower/less efficient than
* the primary decoder.
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @param audioSink The sink to which audio will be output.
*/
public
MediaCodecAudioRenderer
(
Context
context
,
MediaCodecSelector
mediaCodecSelector
,
@Nullable
DrmSessionManager
<
FrameworkMediaCrypto
>
drmSessionManager
,
boolean
playClearSamplesWithoutKeys
,
boolean
enableDecoderFallback
,
@Nullable
Handler
eventHandler
,
@Nullable
AudioRendererEventListener
eventListener
,
AudioSink
audioSink
)
{
super
(
C
.
TRACK_TYPE_AUDIO
,
mediaCodecSelector
,
drmSessionManager
,
playClearSamplesWithoutKeys
,
/* enableDecoderFallback= */
false
,
enableDecoderFallback
,
/* assumedMinimumCodecOperatingRate= */
44100
);
this
.
context
=
context
.
getApplicationContext
();
this
.
audioSink
=
audioSink
;
...
...
@@ -341,7 +379,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
* @return Whether passthrough playback is supported.
*/
protected
boolean
allowPassthrough
(
int
channelCount
,
String
mimeType
)
{
return
audioSink
.
supportsOutput
(
channelCount
,
MimeTypes
.
getEncoding
(
mimeType
))
;
return
getPassthroughEncoding
(
channelCount
,
mimeType
)
!=
C
.
ENCODING_INVALID
;
}
@Override
...
...
@@ -437,11 +475,14 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
@C
.
Encoding
int
encoding
;
MediaFormat
format
;
if
(
passthroughMediaFormat
!=
null
)
{
encoding
=
MimeTypes
.
getEncoding
(
passthroughMediaFormat
.
getString
(
MediaFormat
.
KEY_MIME
));
format
=
passthroughMediaFormat
;
encoding
=
getPassthroughEncoding
(
format
.
getInteger
(
MediaFormat
.
KEY_CHANNEL_COUNT
),
format
.
getString
(
MediaFormat
.
KEY_MIME
));
}
else
{
encoding
=
pcmEncoding
;
format
=
outputFormat
;
encoding
=
pcmEncoding
;
}
int
channelCount
=
format
.
getInteger
(
MediaFormat
.
KEY_CHANNEL_COUNT
);
int
sampleRate
=
format
.
getInteger
(
MediaFormat
.
KEY_SAMPLE_RATE
);
...
...
@@ -464,6 +505,28 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
}
/**
* Returns the {@link C.Encoding} constant to use for passthrough of the given format, or {@link
* C#ENCODING_INVALID} if passthrough is not possible.
*/
@C
.
Encoding
protected
int
getPassthroughEncoding
(
int
channelCount
,
String
mimeType
)
{
if
(
MimeTypes
.
AUDIO_E_AC3_JOC
.
equals
(
mimeType
))
{
if
(
audioSink
.
supportsOutput
(
channelCount
,
C
.
ENCODING_E_AC3_JOC
))
{
return
MimeTypes
.
getEncoding
(
MimeTypes
.
AUDIO_E_AC3_JOC
);
}
// E-AC3 receivers can decode JOC streams, but in 2-D rather than 3-D, so try to fall back.
mimeType
=
MimeTypes
.
AUDIO_E_AC3
;
}
@C
.
Encoding
int
encoding
=
MimeTypes
.
getEncoding
(
mimeType
);
if
(
audioSink
.
supportsOutput
(
channelCount
,
encoding
))
{
return
encoding
;
}
else
{
return
C
.
ENCODING_INVALID
;
}
}
/**
* Called when the audio session id becomes known. The default implementation is a no-op. One
* reason for overriding this method would be to instantiate and enable a {@link Virtualizer} in
* order to spatialize the audio channels. For this use case, any {@link Virtualizer} instances
...
...
library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java
View file @
f6297f4f
...
...
@@ -53,7 +53,6 @@ import java.lang.annotation.RetentionPolicy;
import
java.nio.ByteBuffer
;
import
java.util.ArrayDeque
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.List
;
/**
...
...
@@ -454,15 +453,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
* @param crypto For drm protected playbacks, a {@link MediaCrypto} to use for decryption.
* @param codecOperatingRate The codec operating rate, or {@link #CODEC_OPERATING_RATE_UNSET} if
* no codec operating rate should be set.
* @throws DecoderQueryException If an error occurs querying {@code codecInfo}.
*/
protected
abstract
void
configureCodec
(
MediaCodecInfo
codecInfo
,
MediaCodec
codec
,
Format
format
,
MediaCrypto
crypto
,
float
codecOperatingRate
)
throws
DecoderQueryException
;
float
codecOperatingRate
);
protected
final
void
maybeInitCodec
()
throws
ExoPlaybackException
{
if
(
codec
!=
null
||
inputFormat
==
null
)
{
...
...
@@ -742,11 +739,11 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
try
{
List
<
MediaCodecInfo
>
allAvailableCodecInfos
=
getAvailableCodecInfos
(
mediaCryptoRequiresSecureDecoder
);
availableCodecInfos
=
new
ArrayDeque
<>();
if
(
enableDecoderFallback
)
{
availableCodecInfos
=
new
ArrayDeque
<>(
allAvailableCodecInfos
);
}
else
{
availableCodecInfos
=
new
ArrayDeque
<>(
Collections
.
singletonList
(
allAvailableCodecInfos
.
get
(
0
)));
availableCodecInfos
.
addAll
(
allAvailableCodecInfos
);
}
else
if
(!
allAvailableCodecInfos
.
isEmpty
())
{
availableCodecInfos
.
add
(
allAvailableCodecInfos
.
get
(
0
));
}
preferredDecoderInitializationException
=
null
;
}
catch
(
DecoderQueryException
e
)
{
...
...
library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java
View file @
f6297f4f
...
...
@@ -176,7 +176,7 @@ public final class MediaCodecUtil {
// E-AC3 decoders can decode JOC streams, but in 2-D rather than 3-D.
CodecKey
eac3Key
=
new
CodecKey
(
MimeTypes
.
AUDIO_E_AC3
,
key
.
secure
,
key
.
tunneling
);
ArrayList
<
MediaCodecInfo
>
eac3DecoderInfos
=
getDecoderInfosInternal
(
eac3Key
,
mediaCodecList
,
mimeType
);
getDecoderInfosInternal
(
eac3Key
,
mediaCodecList
,
MimeTypes
.
AUDIO_E_AC3
);
decoderInfos
.
addAll
(
eac3DecoderInfos
);
}
applyWorkarounds
(
mimeType
,
decoderInfos
);
...
...
library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java
View file @
f6297f4f
...
...
@@ -951,6 +951,7 @@ public final class DownloadHelper {
downloadHelper
.
onMediaPrepared
();
return
true
;
case
DOWNLOAD_HELPER_CALLBACK_MESSAGE_FAILED:
release
();
downloadHelper
.
onMediaPreparationFailed
((
IOException
)
Util
.
castNonNull
(
msg
.
obj
));
return
true
;
default
:
...
...
library/core/src/main/java/com/google/android/exoplayer2/scheduler/Requirements.java
View file @
f6297f4f
...
...
@@ -27,6 +27,7 @@ import android.os.Parcel;
import
android.os.Parcelable
;
import
android.os.PowerManager
;
import
androidx.annotation.IntDef
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.Util
;
import
java.lang.annotation.Documented
;
import
java.lang.annotation.Retention
;
...
...
@@ -128,7 +129,7 @@ public final class Requirements implements Parcelable {
ConnectivityManager
connectivityManager
=
(
ConnectivityManager
)
context
.
getSystemService
(
Context
.
CONNECTIVITY_SERVICE
);
NetworkInfo
networkInfo
=
connectivityManager
.
getActiveNetworkInfo
();
NetworkInfo
networkInfo
=
Assertions
.
checkNotNull
(
connectivityManager
)
.
getActiveNetworkInfo
();
if
(
networkInfo
==
null
||
!
networkInfo
.
isConnected
()
||
!
isInternetConnectivityValidated
(
connectivityManager
))
{
...
...
library/core/src/main/java/com/google/android/exoplayer2/scheduler/RequirementsWatcher.java
View file @
f6297f4f
...
...
@@ -28,6 +28,7 @@ import android.os.Handler;
import
android.os.Looper
;
import
android.os.PowerManager
;
import
androidx.annotation.RequiresApi
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.Util
;
/**
...
...
@@ -126,7 +127,8 @@ public final class RequirementsWatcher {
@TargetApi
(
23
)
private
void
registerNetworkCallbackV23
()
{
ConnectivityManager
connectivityManager
=
(
ConnectivityManager
)
context
.
getSystemService
(
Context
.
CONNECTIVITY_SERVICE
);
Assertions
.
checkNotNull
(
(
ConnectivityManager
)
context
.
getSystemService
(
Context
.
CONNECTIVITY_SERVICE
));
NetworkRequest
request
=
new
NetworkRequest
.
Builder
()
.
addCapability
(
NetworkCapabilities
.
NET_CAPABILITY_VALIDATED
)
...
...
library/core/src/main/java/com/google/android/exoplayer2/source/SilenceMediaSource.java
0 → 100644
View file @
f6297f4f
/*
* Copyright (C) 2019 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
.
source
;
import
androidx.annotation.Nullable
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.Format
;
import
com.google.android.exoplayer2.FormatHolder
;
import
com.google.android.exoplayer2.SeekParameters
;
import
com.google.android.exoplayer2.decoder.DecoderInputBuffer
;
import
com.google.android.exoplayer2.trackselection.TrackSelection
;
import
com.google.android.exoplayer2.upstream.Allocator
;
import
com.google.android.exoplayer2.upstream.TransferListener
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.MimeTypes
;
import
com.google.android.exoplayer2.util.Util
;
import
java.util.ArrayList
;
import
org.checkerframework.checker.nullness.compatqual.NullableType
;
/** Media source with a single period consisting of silent raw audio of a given duration. */
public
final
class
SilenceMediaSource
extends
BaseMediaSource
{
private
static
final
int
SAMPLE_RATE_HZ
=
44100
;
@C
.
PcmEncoding
private
static
final
int
ENCODING
=
C
.
ENCODING_PCM_16BIT
;
private
static
final
int
CHANNEL_COUNT
=
2
;
private
static
final
Format
FORMAT
=
Format
.
createAudioSampleFormat
(
/* id=*/
null
,
MimeTypes
.
AUDIO_RAW
,
/* codecs= */
null
,
/* bitrate= */
Format
.
NO_VALUE
,
/* maxInputSize= */
Format
.
NO_VALUE
,
CHANNEL_COUNT
,
SAMPLE_RATE_HZ
,
ENCODING
,
/* initializationData= */
null
,
/* drmInitData= */
null
,
/* selectionFlags= */
0
,
/* language= */
null
);
private
static
final
byte
[]
SILENCE_SAMPLE
=
new
byte
[
Util
.
getPcmFrameSize
(
ENCODING
,
CHANNEL_COUNT
)
*
1024
];
private
final
long
durationUs
;
/**
* Creates a new media source providing silent audio of the given duration.
*
* @param durationUs The duration of silent audio to output, in microseconds.
*/
public
SilenceMediaSource
(
long
durationUs
)
{
Assertions
.
checkArgument
(
durationUs
>=
0
);
this
.
durationUs
=
durationUs
;
}
@Override
public
void
prepareSourceInternal
(
@Nullable
TransferListener
mediaTransferListener
)
{
refreshSourceInfo
(
new
SinglePeriodTimeline
(
durationUs
,
/* isSeekable= */
true
,
/* isDynamic= */
false
),
/* manifest= */
null
);
}
@Override
public
void
maybeThrowSourceInfoRefreshError
()
{}
@Override
public
MediaPeriod
createPeriod
(
MediaPeriodId
id
,
Allocator
allocator
,
long
startPositionUs
)
{
return
new
SilenceMediaPeriod
(
durationUs
);
}
@Override
public
void
releasePeriod
(
MediaPeriod
mediaPeriod
)
{}
@Override
public
void
releaseSourceInternal
()
{}
private
static
final
class
SilenceMediaPeriod
implements
MediaPeriod
{
private
static
final
TrackGroupArray
TRACKS
=
new
TrackGroupArray
(
new
TrackGroup
(
FORMAT
));
private
final
long
durationUs
;
private
final
ArrayList
<
SampleStream
>
sampleStreams
;
public
SilenceMediaPeriod
(
long
durationUs
)
{
this
.
durationUs
=
durationUs
;
sampleStreams
=
new
ArrayList
<>();
}
@Override
public
void
prepare
(
Callback
callback
,
long
positionUs
)
{
callback
.
onPrepared
(
/* mediaPeriod= */
this
);
}
@Override
public
void
maybeThrowPrepareError
()
{}
@Override
public
TrackGroupArray
getTrackGroups
()
{
return
TRACKS
;
}
@Override
public
long
selectTracks
(
@NullableType
TrackSelection
[]
selections
,
boolean
[]
mayRetainStreamFlags
,
@NullableType
SampleStream
[]
streams
,
boolean
[]
streamResetFlags
,
long
positionUs
)
{
for
(
int
i
=
0
;
i
<
selections
.
length
;
i
++)
{
if
(
streams
[
i
]
!=
null
&&
(
selections
[
i
]
==
null
||
!
mayRetainStreamFlags
[
i
]))
{
sampleStreams
.
remove
(
streams
[
i
]);
streams
[
i
]
=
null
;
}
if
(
streams
[
i
]
==
null
&&
selections
[
i
]
!=
null
)
{
SilenceSampleStream
stream
=
new
SilenceSampleStream
(
durationUs
);
stream
.
seekTo
(
positionUs
);
sampleStreams
.
add
(
stream
);
streams
[
i
]
=
stream
;
streamResetFlags
[
i
]
=
true
;
}
}
return
positionUs
;
}
@Override
public
void
discardBuffer
(
long
positionUs
,
boolean
toKeyframe
)
{}
@Override
public
long
readDiscontinuity
()
{
return
C
.
TIME_UNSET
;
}
@Override
public
long
seekToUs
(
long
positionUs
)
{
for
(
int
i
=
0
;
i
<
sampleStreams
.
size
();
i
++)
{
((
SilenceSampleStream
)
sampleStreams
.
get
(
i
)).
seekTo
(
positionUs
);
}
return
positionUs
;
}
@Override
public
long
getAdjustedSeekPositionUs
(
long
positionUs
,
SeekParameters
seekParameters
)
{
return
positionUs
;
}
@Override
public
long
getBufferedPositionUs
()
{
return
C
.
TIME_END_OF_SOURCE
;
}
@Override
public
long
getNextLoadPositionUs
()
{
return
C
.
TIME_END_OF_SOURCE
;
}
@Override
public
boolean
continueLoading
(
long
positionUs
)
{
return
false
;
}
@Override
public
void
reevaluateBuffer
(
long
positionUs
)
{}
}
private
static
final
class
SilenceSampleStream
implements
SampleStream
{
private
final
long
durationBytes
;
private
boolean
sentFormat
;
private
long
positionBytes
;
public
SilenceSampleStream
(
long
durationUs
)
{
durationBytes
=
getAudioByteCount
(
durationUs
);
seekTo
(
0
);
}
public
void
seekTo
(
long
positionUs
)
{
positionBytes
=
getAudioByteCount
(
positionUs
);
}
@Override
public
boolean
isReady
()
{
return
true
;
}
@Override
public
void
maybeThrowError
()
{}
@Override
public
int
readData
(
FormatHolder
formatHolder
,
DecoderInputBuffer
buffer
,
boolean
formatRequired
)
{
if
(!
sentFormat
||
formatRequired
)
{
formatHolder
.
format
=
FORMAT
;
sentFormat
=
true
;
return
C
.
RESULT_FORMAT_READ
;
}
long
bytesRemaining
=
durationBytes
-
positionBytes
;
if
(
bytesRemaining
==
0
)
{
buffer
.
addFlag
(
C
.
BUFFER_FLAG_END_OF_STREAM
);
return
C
.
RESULT_BUFFER_READ
;
}
int
bytesToWrite
=
(
int
)
Math
.
min
(
SILENCE_SAMPLE
.
length
,
bytesRemaining
);
buffer
.
ensureSpaceForWrite
(
bytesToWrite
);
buffer
.
addFlag
(
C
.
BUFFER_FLAG_KEY_FRAME
);
buffer
.
data
.
put
(
SILENCE_SAMPLE
,
/* offset= */
0
,
bytesToWrite
);
buffer
.
timeUs
=
getAudioPositionUs
(
positionBytes
);
positionBytes
+=
bytesToWrite
;
return
C
.
RESULT_BUFFER_READ
;
}
@Override
public
int
skipData
(
long
positionUs
)
{
long
oldPositionBytes
=
positionBytes
;
seekTo
(
positionUs
);
return
(
int
)
((
positionBytes
-
oldPositionBytes
)
/
SILENCE_SAMPLE
.
length
);
}
}
private
static
long
getAudioByteCount
(
long
durationUs
)
{
long
audioSampleCount
=
durationUs
*
SAMPLE_RATE_HZ
/
C
.
MICROS_PER_SECOND
;
return
Util
.
getPcmFrameSize
(
ENCODING
,
CHANNEL_COUNT
)
*
audioSampleCount
;
}
private
static
long
getAudioPositionUs
(
long
bytes
)
{
long
audioSampleCount
=
bytes
/
Util
.
getPcmFrameSize
(
ENCODING
,
CHANNEL_COUNT
);
return
audioSampleCount
*
C
.
MICROS_PER_SECOND
/
SAMPLE_RATE_HZ
;
}
}
library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java
View file @
f6297f4f
...
...
@@ -80,6 +80,11 @@ public final class Cea608Decoder extends CeaDecoder {
* at which point the non-displayed memory becomes the displayed memory (and vice versa).
*/
private
static
final
byte
CTRL_RESUME_CAPTION_LOADING
=
0x20
;
private
static
final
byte
CTRL_BACKSPACE
=
0x21
;
private
static
final
byte
CTRL_DELETE_TO_END_OF_ROW
=
0x24
;
/**
* Command initiating roll-up style captioning, with the maximum of 2 rows displayed
* simultaneously.
...
...
@@ -95,25 +100,31 @@ public final class Cea608Decoder extends CeaDecoder {
* simultaneously.
*/
private
static
final
byte
CTRL_ROLL_UP_CAPTIONS_4_ROWS
=
0x27
;
/**
* Command initiating paint-on style captioning. Subsequent data should be addressed immediately
* to displayed memory without need for the {@link #CTRL_RESUME_CAPTION_LOADING} command.
*/
private
static
final
byte
CTRL_RESUME_DIRECT_CAPTIONING
=
0x29
;
/**
* Command indicating the end of a pop-on style caption. At this point the caption loaded in
* non-displayed memory should be swapped with the one in displayed memory. If no
* {@link #CTRL_RESUME_CAPTION_LOADING} command has been received, this command forces the
* receiver into pop-on style.
* TEXT commands are switching to TEXT service. All consecutive incoming data must be filtered out
* until a command is received that switches back to the CAPTION service.
*/
private
static
final
byte
CTRL_END_OF_CAPTION
=
0x2F
;
private
static
final
byte
CTRL_TEXT_RESTART
=
0x2A
;
private
static
final
byte
CTRL_RESUME_TEXT_DISPLAY
=
0x2B
;
private
static
final
byte
CTRL_ERASE_DISPLAYED_MEMORY
=
0x2C
;
private
static
final
byte
CTRL_CARRIAGE_RETURN
=
0x2D
;
private
static
final
byte
CTRL_ERASE_NON_DISPLAYED_MEMORY
=
0x2E
;
private
static
final
byte
CTRL_DELETE_TO_END_OF_ROW
=
0x24
;
private
static
final
byte
CTRL_BACKSPACE
=
0x21
;
/**
* Command indicating the end of a pop-on style caption. At this point the caption loaded in
* non-displayed memory should be swapped with the one in displayed memory. If no {@link
* #CTRL_RESUME_CAPTION_LOADING} command has been received, this command forces the receiver into
* pop-on style.
*/
private
static
final
byte
CTRL_END_OF_CAPTION
=
0x2F
;
// Basic North American 608 CC char set, mostly ASCII. Indexed by (char-0x20).
private
static
final
int
[]
BASIC_CHARACTER_SET
=
new
int
[]
{
...
...
@@ -237,6 +248,11 @@ public final class Cea608Decoder extends CeaDecoder {
private
byte
repeatableControlCc2
;
private
int
currentChannel
;
// The incoming characters may belong to 3 different services based on the last received control
// codes. The 3 services are Captioning, Text and XDS. The decoder only processes Captioning
// service bytes and drops the rest.
private
boolean
isInCaptionService
;
public
Cea608Decoder
(
String
mimeType
,
int
accessibilityChannel
)
{
ccData
=
new
ParsableByteArray
();
cueBuilders
=
new
ArrayList
<>();
...
...
@@ -268,6 +284,7 @@ public final class Cea608Decoder extends CeaDecoder {
setCaptionMode
(
CC_MODE_UNKNOWN
);
resetCueBuilders
();
isInCaptionService
=
true
;
}
@Override
...
...
@@ -288,6 +305,7 @@ public final class Cea608Decoder extends CeaDecoder {
repeatableControlCc1
=
0
;
repeatableControlCc2
=
0
;
currentChannel
=
NTSC_CC_CHANNEL_1
;
isInCaptionService
=
true
;
}
@Override
...
...
@@ -363,6 +381,12 @@ public final class Cea608Decoder extends CeaDecoder {
continue
;
}
maybeUpdateIsInCaptionService
(
ccData1
,
ccData2
);
if
(!
isInCaptionService
)
{
// Only the Captioning service is supported. Drop all other bytes.
continue
;
}
// Special North American character set.
// ccData1 - 0|0|0|1|C|0|0|1
// ccData2 - 0|0|1|1|X|X|X|X
...
...
@@ -629,6 +653,29 @@ public final class Cea608Decoder extends CeaDecoder {
cueBuilders
.
add
(
currentCueBuilder
);
}
private
void
maybeUpdateIsInCaptionService
(
byte
cc1
,
byte
cc2
)
{
if
(
isXdsControlCode
(
cc1
))
{
isInCaptionService
=
false
;
}
else
if
(
isServiceSwitchCommand
(
cc1
))
{
switch
(
cc2
)
{
case
CTRL_TEXT_RESTART:
case
CTRL_RESUME_TEXT_DISPLAY:
isInCaptionService
=
false
;
break
;
case
CTRL_END_OF_CAPTION:
case
CTRL_RESUME_CAPTION_LOADING:
case
CTRL_RESUME_DIRECT_CAPTIONING:
case
CTRL_ROLL_UP_CAPTIONS_2_ROWS:
case
CTRL_ROLL_UP_CAPTIONS_3_ROWS:
case
CTRL_ROLL_UP_CAPTIONS_4_ROWS:
isInCaptionService
=
true
;
break
;
default
:
// No update.
}
}
}
private
static
char
getChar
(
byte
ccData
)
{
int
index
=
(
ccData
&
0x7F
)
-
0x20
;
return
(
char
)
BASIC_CHARACTER_SET
[
index
];
...
...
@@ -683,6 +730,15 @@ public final class Cea608Decoder extends CeaDecoder {
return
(
cc1
&
0xF0
)
==
0x10
;
}
private
static
boolean
isXdsControlCode
(
byte
cc1
)
{
return
0x01
<=
cc1
&&
cc1
<=
0x0F
;
}
private
static
boolean
isServiceSwitchCommand
(
byte
cc1
)
{
// cc1 - 0|0|0|1|C|1|0|0
return
(
cc1
&
0xF7
)
==
0x14
;
}
private
static
class
CueBuilder
{
// 608 captions define a 15 row by 32 column screen grid. These constants convert from 608
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java
View file @
f6297f4f
...
...
@@ -429,6 +429,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
/* lineType= */
Cue
.
LINE_TYPE_FRACTION
,
lineAnchor
,
width
,
height
,
/* textSizeType= */
Cue
.
TEXT_SIZE_TYPE_FRACTIONAL_IGNORE_PADDING
,
/* textSize= */
regionTextHeight
);
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java
View file @
f6297f4f
...
...
@@ -231,11 +231,11 @@ import java.util.TreeSet;
new
Cue
(
bitmap
,
region
.
position
,
Cue
.
ANCHOR_TYPE_
MIDDLE
,
Cue
.
ANCHOR_TYPE_
START
,
region
.
line
,
region
.
lineAnchor
,
region
.
width
,
/* height= */
Cue
.
DIMEN_UNSET
));
region
.
height
));
}
// Create text based cues.
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRegion.java
View file @
f6297f4f
...
...
@@ -28,6 +28,7 @@ import com.google.android.exoplayer2.text.Cue;
public
final
@Cue
.
LineType
int
lineType
;
public
final
@Cue
.
AnchorType
int
lineAnchor
;
public
final
float
width
;
public
final
float
height
;
public
final
@Cue
.
TextSizeType
int
textSizeType
;
public
final
float
textSize
;
...
...
@@ -39,6 +40,7 @@ import com.google.android.exoplayer2.text.Cue;
/* lineType= */
Cue
.
TYPE_UNSET
,
/* lineAnchor= */
Cue
.
TYPE_UNSET
,
/* width= */
Cue
.
DIMEN_UNSET
,
/* height= */
Cue
.
DIMEN_UNSET
,
/* textSizeType= */
Cue
.
TYPE_UNSET
,
/* textSize= */
Cue
.
DIMEN_UNSET
);
}
...
...
@@ -50,6 +52,7 @@ import com.google.android.exoplayer2.text.Cue;
@Cue
.
LineType
int
lineType
,
@Cue
.
AnchorType
int
lineAnchor
,
float
width
,
float
height
,
int
textSizeType
,
float
textSize
)
{
this
.
id
=
id
;
...
...
@@ -58,6 +61,7 @@ import com.google.android.exoplayer2.text.Cue;
this
.
lineType
=
lineType
;
this
.
lineAnchor
=
lineAnchor
;
this
.
width
=
width
;
this
.
height
=
height
;
this
.
textSizeType
=
textSizeType
;
this
.
textSize
=
textSize
;
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/CssParser.java
View file @
f6297f4f
...
...
@@ -20,6 +20,8 @@ import com.google.android.exoplayer2.util.ColorParser;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
com.google.android.exoplayer2.util.Util
;
import
java.util.Arrays
;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.regex.Matcher
;
import
java.util.regex.Pattern
;
...
...
@@ -35,8 +37,8 @@ import java.util.regex.Pattern;
private
static
final
String
PROPERTY_TEXT_DECORATION
=
"text-decoration"
;
private
static
final
String
VALUE_BOLD
=
"bold"
;
private
static
final
String
VALUE_UNDERLINE
=
"underline"
;
private
static
final
String
BLOCK
_START
=
"{"
;
private
static
final
String
BLOCK
_END
=
"}"
;
private
static
final
String
RULE
_START
=
"{"
;
private
static
final
String
RULE
_END
=
"}"
;
private
static
final
String
PROPERTY_FONT_STYLE
=
"font-style"
;
private
static
final
String
VALUE_ITALIC
=
"italic"
;
...
...
@@ -52,37 +54,47 @@ import java.util.regex.Pattern;
}
/**
* Takes a CSS style block and consumes up to the first empty line found. Attempts to parse the
* contents of the style block and returns a {@link WebvttCssStyle} instance if successful, or
* {@code null} otherwise.
* Takes a CSS style block and consumes up to the first empty line. Attempts to parse the contents
* of the style block and returns a list of {@link WebvttCssStyle} instances if successful. If
* parsing fails, it returns a list including only the styles which have been successfully parsed
* up to the style rule which was malformed.
*
* @param input The input from which the style block should be read.
* @return A {@link WebvttCssStyle} that represents the parsed block.
* @return A list of {@link WebvttCssStyle}s that represents the parsed block, or a list
* containing the styles up to the parsing failure.
*/
public
WebvttCssStyle
parseBlock
(
ParsableByteArray
input
)
{
public
List
<
WebvttCssStyle
>
parseBlock
(
ParsableByteArray
input
)
{
stringBuilder
.
setLength
(
0
);
int
initialInputPosition
=
input
.
getPosition
();
skipStyleBlock
(
input
);
styleInput
.
reset
(
input
.
data
,
input
.
getPosition
());
styleInput
.
setPosition
(
initialInputPosition
);
String
selector
=
parseSelector
(
styleInput
,
stringBuilder
);
if
(
selector
==
null
||
!
BLOCK_START
.
equals
(
parseNextToken
(
styleInput
,
stringBuilder
)))
{
return
null
;
}
WebvttCssStyle
style
=
new
WebvttCssStyle
();
applySelectorToStyle
(
style
,
selector
);
String
token
=
null
;
boolean
blockEndFound
=
false
;
while
(!
blockEndFound
)
{
int
position
=
styleInput
.
getPosition
();
token
=
parseNextToken
(
styleInput
,
stringBuilder
);
blockEndFound
=
token
==
null
||
BLOCK_END
.
equals
(
token
);
if
(!
blockEndFound
)
{
styleInput
.
setPosition
(
position
);
parseStyleDeclaration
(
styleInput
,
style
,
stringBuilder
);
List
<
WebvttCssStyle
>
styles
=
new
ArrayList
<>();
String
selector
;
while
((
selector
=
parseSelector
(
styleInput
,
stringBuilder
))
!=
null
)
{
if
(!
RULE_START
.
equals
(
parseNextToken
(
styleInput
,
stringBuilder
)))
{
return
styles
;
}
WebvttCssStyle
style
=
new
WebvttCssStyle
();
applySelectorToStyle
(
style
,
selector
);
String
token
=
null
;
boolean
blockEndFound
=
false
;
while
(!
blockEndFound
)
{
int
position
=
styleInput
.
getPosition
();
token
=
parseNextToken
(
styleInput
,
stringBuilder
);
blockEndFound
=
token
==
null
||
RULE_END
.
equals
(
token
);
if
(!
blockEndFound
)
{
styleInput
.
setPosition
(
position
);
parseStyleDeclaration
(
styleInput
,
style
,
stringBuilder
);
}
}
// Check that the style rule ended correctly.
if
(
RULE_END
.
equals
(
token
))
{
styles
.
add
(
style
);
}
}
return
BLOCK_END
.
equals
(
token
)
?
style
:
null
;
// Check that the style block ended correctly.
return
styles
;
}
/**
...
...
@@ -107,7 +119,7 @@ import java.util.regex.Pattern;
if
(
token
==
null
)
{
return
null
;
}
if
(
BLOCK
_START
.
equals
(
token
))
{
if
(
RULE
_START
.
equals
(
token
))
{
input
.
setPosition
(
position
);
return
""
;
}
...
...
@@ -156,7 +168,7 @@ import java.util.regex.Pattern;
String
token
=
parseNextToken
(
input
,
stringBuilder
);
if
(
";"
.
equals
(
token
))
{
// The style declaration is well formed.
}
else
if
(
BLOCK
_END
.
equals
(
token
))
{
}
else
if
(
RULE
_END
.
equals
(
token
))
{
// The style declaration is well formed and we can go on, but the closing bracket had to be
// fed back.
input
.
setPosition
(
position
);
...
...
@@ -250,7 +262,7 @@ import java.util.regex.Pattern;
// Syntax error.
return
null
;
}
if
(
BLOCK
_END
.
equals
(
token
)
||
";"
.
equals
(
token
))
{
if
(
RULE
_END
.
equals
(
token
)
||
";"
.
equals
(
token
))
{
input
.
setPosition
(
position
);
expressionEndFound
=
true
;
}
else
{
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoder.java
View file @
f6297f4f
...
...
@@ -80,10 +80,7 @@ public final class WebvttDecoder extends SimpleSubtitleDecoder {
throw
new
SubtitleDecoderException
(
"A style block was found after the first cue."
);
}
parsableWebvttData
.
readLine
();
// Consume the "STYLE" header.
WebvttCssStyle
styleBlock
=
cssParser
.
parseBlock
(
parsableWebvttData
);
if
(
styleBlock
!=
null
)
{
definedStyles
.
add
(
styleBlock
);
}
definedStyles
.
addAll
(
cssParser
.
parseBlock
(
parsableWebvttData
));
}
else
if
(
event
==
EVENT_CUE
)
{
if
(
cueParser
.
parseCue
(
parsableWebvttData
,
webvttCueBuilder
,
definedStyles
))
{
subtitles
.
add
(
webvttCueBuilder
.
build
());
...
...
library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java
View file @
f6297f4f
...
...
@@ -757,7 +757,7 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
for
(
int
i
=
0
;
i
<
values
.
length
;
i
++)
{
logValues
[
i
]
=
new
double
[
values
[
i
].
length
];
for
(
int
j
=
0
;
j
<
values
[
i
].
length
;
j
++)
{
logValues
[
i
][
j
]
=
Math
.
log
(
values
[
i
][
j
]);
logValues
[
i
][
j
]
=
values
[
i
][
j
]
==
Format
.
NO_VALUE
?
0
:
Math
.
log
(
values
[
i
][
j
]);
}
}
return
logValues
;
...
...
@@ -779,7 +779,8 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
double
totalBitrateDiff
=
logBitrates
[
i
][
logBitrates
[
i
].
length
-
1
]
-
logBitrates
[
i
][
0
];
for
(
int
j
=
0
;
j
<
logBitrates
[
i
].
length
-
1
;
j
++)
{
double
switchBitrate
=
0.5
*
(
logBitrates
[
i
][
j
]
+
logBitrates
[
i
][
j
+
1
]);
switchPoints
[
i
][
j
]
=
(
switchBitrate
-
logBitrates
[
i
][
0
])
/
totalBitrateDiff
;
switchPoints
[
i
][
j
]
=
totalBitrateDiff
==
0.0
?
1.0
:
(
switchBitrate
-
logBitrates
[
i
][
0
])
/
totalBitrateDiff
;
}
}
return
switchPoints
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java
View file @
f6297f4f
...
...
@@ -1934,6 +1934,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
getAdaptiveAudioTracks
(
selectedGroup
,
formatSupports
[
selectedGroupIndex
],
params
.
maxAudioBitrate
,
params
.
allowAudioMixedMimeTypeAdaptiveness
,
params
.
allowAudioMixedSampleRateAdaptiveness
);
if
(
adaptiveTracks
.
length
>
0
)
{
...
...
@@ -1951,6 +1952,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
private
static
int
[]
getAdaptiveAudioTracks
(
TrackGroup
group
,
int
[]
formatSupport
,
int
maxAudioBitrate
,
boolean
allowMixedMimeTypeAdaptiveness
,
boolean
allowMixedSampleRateAdaptiveness
)
{
int
selectedConfigurationTrackCount
=
0
;
...
...
@@ -1967,6 +1969,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
group
,
formatSupport
,
configuration
,
maxAudioBitrate
,
allowMixedMimeTypeAdaptiveness
,
allowMixedSampleRateAdaptiveness
);
if
(
configurationCount
>
selectedConfigurationTrackCount
)
{
...
...
@@ -1977,13 +1980,16 @@ public class DefaultTrackSelector extends MappingTrackSelector {
}
if
(
selectedConfigurationTrackCount
>
1
)
{
Assertions
.
checkNotNull
(
selectedConfiguration
);
int
[]
adaptiveIndices
=
new
int
[
selectedConfigurationTrackCount
];
int
index
=
0
;
for
(
int
i
=
0
;
i
<
group
.
length
;
i
++)
{
Format
format
=
group
.
getFormat
(
i
);
if
(
isSupportedAdaptiveAudioTrack
(
group
.
getFormat
(
i
)
,
format
,
formatSupport
[
i
],
Assertions
.
checkNotNull
(
selectedConfiguration
),
selectedConfiguration
,
maxAudioBitrate
,
allowMixedMimeTypeAdaptiveness
,
allowMixedSampleRateAdaptiveness
))
{
adaptiveIndices
[
index
++]
=
i
;
...
...
@@ -1998,6 +2004,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
TrackGroup
group
,
int
[]
formatSupport
,
AudioConfigurationTuple
configuration
,
int
maxAudioBitrate
,
boolean
allowMixedMimeTypeAdaptiveness
,
boolean
allowMixedSampleRateAdaptiveness
)
{
int
count
=
0
;
...
...
@@ -2006,6 +2013,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
group
.
getFormat
(
i
),
formatSupport
[
i
],
configuration
,
maxAudioBitrate
,
allowMixedMimeTypeAdaptiveness
,
allowMixedSampleRateAdaptiveness
))
{
count
++;
...
...
@@ -2018,9 +2026,11 @@ public class DefaultTrackSelector extends MappingTrackSelector {
Format
format
,
int
formatSupport
,
AudioConfigurationTuple
configuration
,
int
maxAudioBitrate
,
boolean
allowMixedMimeTypeAdaptiveness
,
boolean
allowMixedSampleRateAdaptiveness
)
{
return
isSupported
(
formatSupport
,
false
)
&&
(
format
.
bitrate
==
Format
.
NO_VALUE
||
format
.
bitrate
<=
maxAudioBitrate
)
&&
(
format
.
channelCount
!=
Format
.
NO_VALUE
&&
format
.
channelCount
==
configuration
.
channelCount
)
&&
(
allowMixedMimeTypeAdaptiveness
...
...
library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java
View file @
f6297f4f
...
...
@@ -42,6 +42,7 @@ import java.util.Map;
* <li>rtmp: For fetching data over RTMP. Only supported if the project using ExoPlayer has an
* explicit dependency on ExoPlayer's RTMP extension.
* <li>data: For parsing data inlined in the URI as defined in RFC 2397.
* <li>udp: For fetching data over UDP (e.g. udp://something.com/media).
* <li>http(s): For fetching data over HTTP and HTTPS (e.g. https://www.something.com/media.mp4),
* if constructed using {@link #DefaultDataSource(Context, TransferListener, String,
* boolean)}, or any other schemes supported by a base data source if constructed using {@link
...
...
@@ -55,6 +56,7 @@ public final class DefaultDataSource implements DataSource {
private
static
final
String
SCHEME_ASSET
=
"asset"
;
private
static
final
String
SCHEME_CONTENT
=
"content"
;
private
static
final
String
SCHEME_RTMP
=
"rtmp"
;
private
static
final
String
SCHEME_UDP
=
"udp"
;
private
static
final
String
SCHEME_RAW
=
RawResourceDataSource
.
RAW_RESOURCE_SCHEME
;
private
final
Context
context
;
...
...
@@ -62,12 +64,13 @@ public final class DefaultDataSource implements DataSource {
private
final
DataSource
baseDataSource
;
// Lazily initialized.
private
@Nullable
DataSource
fileDataSource
;
private
@Nullable
DataSource
assetDataSource
;
private
@Nullable
DataSource
contentDataSource
;
private
@Nullable
DataSource
rtmpDataSource
;
private
@Nullable
DataSource
dataSchemeDataSource
;
private
@Nullable
DataSource
rawResourceDataSource
;
@Nullable
private
DataSource
fileDataSource
;
@Nullable
private
DataSource
assetDataSource
;
@Nullable
private
DataSource
contentDataSource
;
@Nullable
private
DataSource
rtmpDataSource
;
@Nullable
private
DataSource
udpDataSource
;
@Nullable
private
DataSource
dataSchemeDataSource
;
@Nullable
private
DataSource
rawResourceDataSource
;
private
@Nullable
DataSource
dataSource
;
...
...
@@ -218,6 +221,7 @@ public final class DefaultDataSource implements DataSource {
maybeAddListenerToDataSource
(
assetDataSource
,
transferListener
);
maybeAddListenerToDataSource
(
contentDataSource
,
transferListener
);
maybeAddListenerToDataSource
(
rtmpDataSource
,
transferListener
);
maybeAddListenerToDataSource
(
udpDataSource
,
transferListener
);
maybeAddListenerToDataSource
(
dataSchemeDataSource
,
transferListener
);
maybeAddListenerToDataSource
(
rawResourceDataSource
,
transferListener
);
}
...
...
@@ -240,6 +244,8 @@ public final class DefaultDataSource implements DataSource {
dataSource
=
getContentDataSource
();
}
else
if
(
SCHEME_RTMP
.
equals
(
scheme
))
{
dataSource
=
getRtmpDataSource
();
}
else
if
(
SCHEME_UDP
.
equals
(
scheme
))
{
dataSource
=
getUdpDataSource
();
}
else
if
(
DataSchemeDataSource
.
SCHEME_DATA
.
equals
(
scheme
))
{
dataSource
=
getDataSchemeDataSource
();
}
else
if
(
SCHEME_RAW
.
equals
(
scheme
))
{
...
...
@@ -277,6 +283,14 @@ public final class DefaultDataSource implements DataSource {
}
}
private
DataSource
getUdpDataSource
()
{
if
(
udpDataSource
==
null
)
{
udpDataSource
=
new
UdpDataSource
();
addListenersToDataSource
(
udpDataSource
);
}
return
udpDataSource
;
}
private
DataSource
getFileDataSource
()
{
if
(
fileDataSource
==
null
)
{
fileDataSource
=
new
FileDataSource
();
...
...
library/core/src/main/java/com/google/android/exoplayer2/upstream/ResolvingDataSource.java
0 → 100644
View file @
f6297f4f
/*
* Copyright (C) 2019 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
.
upstream
;
import
android.net.Uri
;
import
androidx.annotation.Nullable
;
import
java.io.IOException
;
import
java.util.List
;
import
java.util.Map
;
/** {@link DataSource} wrapper allowing just-in-time resolution of {@link DataSpec DataSpecs}. */
public
final
class
ResolvingDataSource
implements
DataSource
{
/** Resolves {@link DataSpec DataSpecs}. */
public
interface
Resolver
{
/**
* Resolves a {@link DataSpec} before forwarding it to the wrapped {@link DataSource}. This
* method is allowed to block until the {@link DataSpec} has been resolved.
*
* <p>Note that this method is called for every new connection, so caching of results is
* recommended, especially if network operations are involved.
*
* @param dataSpec The original {@link DataSpec}.
* @return The resolved {@link DataSpec}.
* @throws IOException If an {@link IOException} occurred while resolving the {@link DataSpec}.
*/
DataSpec
resolveDataSpec
(
DataSpec
dataSpec
)
throws
IOException
;
/**
* Resolves a URI reported by {@link DataSource#getUri()} for event reporting and caching
* purposes.
*
* <p>Implementations do not need to overwrite this method unless they want to change the
* reported URI.
*
* <p>This method is <em>not</em> allowed to block.
*
* @param uri The URI as reported by {@link DataSource#getUri()}.
* @return The resolved URI used for event reporting and caching.
*/
default
Uri
resolveReportedUri
(
Uri
uri
)
{
return
uri
;
}
}
/** {@link DataSource.Factory} for {@link ResolvingDataSource} instances. */
public
static
final
class
Factory
implements
DataSource
.
Factory
{
private
final
DataSource
.
Factory
upstreamFactory
;
private
final
Resolver
resolver
;
/**
* Creates factory for {@link ResolvingDataSource} instances.
*
* @param upstreamFactory The wrapped {@link DataSource.Factory} handling the resolved {@link
* DataSpec DataSpecs}.
* @param resolver The {@link Resolver} to resolve the {@link DataSpec DataSpecs}.
*/
public
Factory
(
DataSource
.
Factory
upstreamFactory
,
Resolver
resolver
)
{
this
.
upstreamFactory
=
upstreamFactory
;
this
.
resolver
=
resolver
;
}
@Override
public
DataSource
createDataSource
()
{
return
new
ResolvingDataSource
(
upstreamFactory
.
createDataSource
(),
resolver
);
}
}
private
final
DataSource
upstreamDataSource
;
private
final
Resolver
resolver
;
private
boolean
upstreamOpened
;
/**
* @param upstreamDataSource The wrapped {@link DataSource}.
* @param resolver The {@link Resolver} to resolve the {@link DataSpec DataSpecs}.
*/
public
ResolvingDataSource
(
DataSource
upstreamDataSource
,
Resolver
resolver
)
{
this
.
upstreamDataSource
=
upstreamDataSource
;
this
.
resolver
=
resolver
;
}
@Override
public
void
addTransferListener
(
TransferListener
transferListener
)
{
upstreamDataSource
.
addTransferListener
(
transferListener
);
}
@Override
public
long
open
(
DataSpec
dataSpec
)
throws
IOException
{
DataSpec
resolvedDataSpec
=
resolver
.
resolveDataSpec
(
dataSpec
);
upstreamOpened
=
true
;
return
upstreamDataSource
.
open
(
resolvedDataSpec
);
}
@Override
public
int
read
(
byte
[]
buffer
,
int
offset
,
int
readLength
)
throws
IOException
{
return
upstreamDataSource
.
read
(
buffer
,
offset
,
readLength
);
}
@Nullable
@Override
public
Uri
getUri
()
{
Uri
reportedUri
=
upstreamDataSource
.
getUri
();
return
reportedUri
==
null
?
null
:
resolver
.
resolveReportedUri
(
reportedUri
);
}
@Override
public
Map
<
String
,
List
<
String
>>
getResponseHeaders
()
{
return
upstreamDataSource
.
getResponseHeaders
();
}
@Override
public
void
close
()
throws
IOException
{
if
(
upstreamOpened
)
{
upstreamOpened
=
false
;
upstreamDataSource
.
close
();
}
}
}
library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java
View file @
f6297f4f
...
...
@@ -134,9 +134,9 @@ public final class CacheDataSource implements DataSource {
private
@Nullable
DataSource
currentDataSource
;
private
boolean
currentDataSpecLengthUnset
;
private
@Nullabl
e
Uri
uri
;
private
@Nullabl
e
Uri
actualUri
;
private
@HttpMethod
int
httpMethod
;
@Nullable
privat
e
Uri
uri
;
@Nullable
privat
e
Uri
actualUri
;
@HttpMethod
private
int
httpMethod
;
private
int
flags
;
private
@Nullable
String
key
;
private
long
readPosition
;
...
...
@@ -319,7 +319,7 @@ public final class CacheDataSource implements DataSource {
}
return
bytesRead
;
}
catch
(
IOException
e
)
{
if
(
currentDataSpecLengthUnset
&&
isCausedByPositionOutOfRange
(
e
))
{
if
(
currentDataSpecLengthUnset
&&
CacheUtil
.
isCausedByPositionOutOfRange
(
e
))
{
setNoBytesRemainingAndMaybeStoreLength
();
return
C
.
RESULT_END_OF_INPUT
;
}
...
...
@@ -484,20 +484,6 @@ public final class CacheDataSource implements DataSource {
return
redirectedUri
!=
null
?
redirectedUri
:
defaultUri
;
}
private
static
boolean
isCausedByPositionOutOfRange
(
IOException
e
)
{
Throwable
cause
=
e
;
while
(
cause
!=
null
)
{
if
(
cause
instanceof
DataSourceException
)
{
int
reason
=
((
DataSourceException
)
cause
).
reason
;
if
(
reason
==
DataSourceException
.
POSITION_OUT_OF_RANGE
)
{
return
true
;
}
}
cause
=
cause
.
getCause
();
}
return
false
;
}
private
boolean
isReadingFromUpstream
()
{
return
!
isReadingFromCache
();
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java
View file @
f6297f4f
...
...
@@ -20,6 +20,7 @@ import androidx.annotation.Nullable;
import
android.util.Pair
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.upstream.DataSource
;
import
com.google.android.exoplayer2.upstream.DataSourceException
;
import
com.google.android.exoplayer2.upstream.DataSpec
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.PriorityTaskManager
;
...
...
@@ -78,13 +79,7 @@ public final class CacheUtil {
DataSpec
dataSpec
,
Cache
cache
,
@Nullable
CacheKeyFactory
cacheKeyFactory
)
{
String
key
=
buildCacheKey
(
dataSpec
,
cacheKeyFactory
);
long
position
=
dataSpec
.
absoluteStreamPosition
;
long
requestLength
;
if
(
dataSpec
.
length
!=
C
.
LENGTH_UNSET
)
{
requestLength
=
dataSpec
.
length
;
}
else
{
long
contentLength
=
ContentMetadata
.
getContentLength
(
cache
.
getContentMetadata
(
key
));
requestLength
=
contentLength
==
C
.
LENGTH_UNSET
?
C
.
LENGTH_UNSET
:
contentLength
-
position
;
}
long
requestLength
=
getRequestLength
(
dataSpec
,
cache
,
key
);
long
bytesAlreadyCached
=
0
;
long
bytesLeft
=
requestLength
;
while
(
bytesLeft
!=
0
)
{
...
...
@@ -179,53 +174,66 @@ public final class CacheUtil {
Assertions
.
checkNotNull
(
dataSource
);
Assertions
.
checkNotNull
(
buffer
);
String
key
=
buildCacheKey
(
dataSpec
,
cacheKeyFactory
);
long
bytesLeft
;
ProgressNotifier
progressNotifier
=
null
;
if
(
progressListener
!=
null
)
{
progressNotifier
=
new
ProgressNotifier
(
progressListener
);
Pair
<
Long
,
Long
>
lengthAndBytesAlreadyCached
=
getCached
(
dataSpec
,
cache
,
cacheKeyFactory
);
progressNotifier
.
init
(
lengthAndBytesAlreadyCached
.
first
,
lengthAndBytesAlreadyCached
.
second
);
bytesLeft
=
lengthAndBytesAlreadyCached
.
first
;
}
else
{
bytesLeft
=
getRequestLength
(
dataSpec
,
cache
,
key
);
}
String
key
=
buildCacheKey
(
dataSpec
,
cacheKeyFactory
);
long
position
=
dataSpec
.
absoluteStreamPosition
;
long
bytesLeft
;
if
(
dataSpec
.
length
!=
C
.
LENGTH_UNSET
)
{
bytesLeft
=
dataSpec
.
length
;
}
else
{
long
contentLength
=
ContentMetadata
.
getContentLength
(
cache
.
getContentMetadata
(
key
));
bytesLeft
=
contentLength
==
C
.
LENGTH_UNSET
?
C
.
LENGTH_UNSET
:
contentLength
-
position
;
}
boolean
lengthUnset
=
bytesLeft
==
C
.
LENGTH_UNSET
;
while
(
bytesLeft
!=
0
)
{
throwExceptionIfInterruptedOrCancelled
(
isCanceled
);
long
blockLength
=
cache
.
getCachedLength
(
key
,
position
,
bytesLeft
!=
C
.
LENGTH_UNSET
?
bytesLeft
:
Long
.
MAX_VALUE
);
cache
.
getCachedLength
(
key
,
position
,
lengthUnset
?
Long
.
MAX_VALUE
:
bytesLeft
);
if
(
blockLength
>
0
)
{
// Skip already cached data.
}
else
{
// There is a hole in the cache which is at least "-blockLength" long.
blockLength
=
-
blockLength
;
long
length
=
blockLength
==
Long
.
MAX_VALUE
?
C
.
LENGTH_UNSET
:
blockLength
;
boolean
isLastBlock
=
length
==
bytesLeft
;
long
read
=
readAndDiscard
(
dataSpec
,
position
,
blockL
ength
,
l
ength
,
dataSource
,
buffer
,
priorityTaskManager
,
priority
,
progressNotifier
,
isLastBlock
,
isCanceled
);
if
(
read
<
blockLength
)
{
// Reached to the end of the data.
if
(
enableEOFException
&&
bytesLeft
!=
C
.
LENGTH_UNSET
)
{
if
(
enableEOFException
&&
!
lengthUnset
)
{
throw
new
EOFException
();
}
break
;
}
}
position
+=
blockLength
;
bytesLeft
-=
bytesLeft
==
C
.
LENGTH_UNSET
?
0
:
blockLength
;
if
(!
lengthUnset
)
{
bytesLeft
-=
blockLength
;
}
}
}
private
static
long
getRequestLength
(
DataSpec
dataSpec
,
Cache
cache
,
String
key
)
{
if
(
dataSpec
.
length
!=
C
.
LENGTH_UNSET
)
{
return
dataSpec
.
length
;
}
else
{
long
contentLength
=
ContentMetadata
.
getContentLength
(
cache
.
getContentMetadata
(
key
));
return
contentLength
==
C
.
LENGTH_UNSET
?
C
.
LENGTH_UNSET
:
contentLength
-
dataSpec
.
absoluteStreamPosition
;
}
}
...
...
@@ -242,6 +250,7 @@ public final class CacheUtil {
* caching.
* @param priority The priority of this task.
* @param progressNotifier A notifier through which to report progress updates, or {@code null}.
* @param isLastBlock Whether this read block is the last block of the content.
* @param isCanceled An optional flag that will interrupt caching if set to true.
* @return Number of read bytes, or 0 if no data is available because the end of the opened range
* has been reached.
...
...
@@ -255,54 +264,64 @@ public final class CacheUtil {
PriorityTaskManager
priorityTaskManager
,
int
priority
,
@Nullable
ProgressNotifier
progressNotifier
,
boolean
isLastBlock
,
AtomicBoolean
isCanceled
)
throws
IOException
,
InterruptedException
{
long
positionOffset
=
absoluteStreamPosition
-
dataSpec
.
absoluteStreamPosition
;
long
initialPositionOffset
=
positionOffset
;
long
endOffset
=
length
!=
C
.
LENGTH_UNSET
?
positionOffset
+
length
:
C
.
POSITION_UNSET
;
while
(
true
)
{
if
(
priorityTaskManager
!=
null
)
{
// Wait for any other thread with higher priority to finish its job.
priorityTaskManager
.
proceed
(
priority
);
}
throwExceptionIfInterruptedOrCancelled
(
isCanceled
);
try
{
throwExceptionIfInterruptedOrCancelled
(
isCanceled
);
// Create a new dataSpec setting length to C.LENGTH_UNSET to prevent getting an error in
// case the given length exceeds the end of input.
dataSpec
=
new
DataSpec
(
dataSpec
.
uri
,
dataSpec
.
httpMethod
,
dataSpec
.
httpBody
,
absoluteStreamPosition
,
/* position= */
dataSpec
.
position
+
positionOffset
,
C
.
LENGTH_UNSET
,
dataSpec
.
key
,
dataSpec
.
flags
);
long
resolvedLength
=
dataSource
.
open
(
dataSpec
);
if
(
progressNotifier
!=
null
&&
resolvedLength
!=
C
.
LENGTH_UNSET
)
{
long
resolvedLength
=
C
.
LENGTH_UNSET
;
boolean
isDataSourceOpen
=
false
;
if
(
endOffset
!=
C
.
POSITION_UNSET
)
{
// If a specific length is given, first try to open the data source for that length to
// avoid more data then required to be requested. If the given length exceeds the end of
// input we will get a "position out of range" error. In that case try to open the source
// again with unset length.
try
{
resolvedLength
=
dataSource
.
open
(
dataSpec
.
subrange
(
positionOffset
,
endOffset
-
positionOffset
));
isDataSourceOpen
=
true
;
}
catch
(
IOException
exception
)
{
if
(!
isLastBlock
||
!
isCausedByPositionOutOfRange
(
exception
))
{
throw
exception
;
}
Util
.
closeQuietly
(
dataSource
);
}
}
if
(!
isDataSourceOpen
)
{
resolvedLength
=
dataSource
.
open
(
dataSpec
.
subrange
(
positionOffset
,
C
.
LENGTH_UNSET
));
}
if
(
isLastBlock
&&
progressNotifier
!=
null
&&
resolvedLength
!=
C
.
LENGTH_UNSET
)
{
progressNotifier
.
onRequestLengthResolved
(
positionOffset
+
resolvedLength
);
}
long
totalBytesRead
=
0
;
while
(
totalBytesRead
!=
length
)
{
while
(
positionOffset
!=
endOffset
)
{
throwExceptionIfInterruptedOrCancelled
(
isCanceled
);
int
bytesRead
=
dataSource
.
read
(
buffer
,
0
,
length
!=
C
.
LENGTH
_UNSET
?
(
int
)
Math
.
min
(
buffer
.
length
,
length
-
totalBytesRead
)
endOffset
!=
C
.
POSITION
_UNSET
?
(
int
)
Math
.
min
(
buffer
.
length
,
endOffset
-
positionOffset
)
:
buffer
.
length
);
if
(
bytesRead
==
C
.
RESULT_END_OF_INPUT
)
{
if
(
progressNotifier
!=
null
)
{
progressNotifier
.
onRequestLengthResolved
(
positionOffset
+
totalBytesRead
);
progressNotifier
.
onRequestLengthResolved
(
positionOffset
);
}
break
;
}
totalBytesRead
+=
bytesRead
;
positionOffset
+=
bytesRead
;
if
(
progressNotifier
!=
null
)
{
progressNotifier
.
onBytesCached
(
bytesRead
);
}
}
return
totalBytesRead
;
return
positionOffset
-
initialPositionOffset
;
}
catch
(
PriorityTaskManager
.
PriorityTooLowException
exception
)
{
// catch and try again
}
finally
{
...
...
@@ -340,6 +359,20 @@ public final class CacheUtil {
}
}
/*package*/
static
boolean
isCausedByPositionOutOfRange
(
IOException
e
)
{
Throwable
cause
=
e
;
while
(
cause
!=
null
)
{
if
(
cause
instanceof
DataSourceException
)
{
int
reason
=
((
DataSourceException
)
cause
).
reason
;
if
(
reason
==
DataSourceException
.
POSITION_OUT_OF_RANGE
)
{
return
true
;
}
}
cause
=
cause
.
getCause
();
}
return
false
;
}
private
static
String
buildCacheKey
(
DataSpec
dataSpec
,
@Nullable
CacheKeyFactory
cacheKeyFactory
)
{
return
(
cacheKeyFactory
!=
null
?
cacheKeyFactory
:
DEFAULT_CACHE_KEY_FACTORY
)
...
...
library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java
View file @
f6297f4f
...
...
@@ -348,8 +348,9 @@ public final class MimeTypes {
case
MimeTypes
.
AUDIO_AC3
:
return
C
.
ENCODING_AC3
;
case
MimeTypes
.
AUDIO_E_AC3
:
case
MimeTypes
.
AUDIO_E_AC3_JOC
:
return
C
.
ENCODING_E_AC3
;
case
MimeTypes
.
AUDIO_E_AC3_JOC
:
return
C
.
ENCODING_E_AC3_JOC
;
case
MimeTypes
.
AUDIO_AC4
:
return
C
.
ENCODING_AC4
;
case
MimeTypes
.
AUDIO_DTS
:
...
...
library/core/src/main/java/com/google/android/exoplayer2/util/Util.java
View file @
f6297f4f
...
...
@@ -1713,7 +1713,12 @@ public final class Util {
if
(
connectivityManager
==
null
)
{
return
C
.
NETWORK_TYPE_UNKNOWN
;
}
networkInfo
=
connectivityManager
.
getActiveNetworkInfo
();
try
{
networkInfo
=
connectivityManager
.
getActiveNetworkInfo
();
}
catch
(
SecurityException
e
)
{
// Expected if permission was revoked.
return
C
.
NETWORK_TYPE_UNKNOWN
;
}
if
(
networkInfo
==
null
||
!
networkInfo
.
isConnected
())
{
return
C
.
NETWORK_TYPE_OFFLINE
;
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java
View file @
f6297f4f
...
...
@@ -550,8 +550,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
MediaCodec
codec
,
Format
format
,
MediaCrypto
crypto
,
float
codecOperatingRate
)
throws
DecoderQueryException
{
float
codecOperatingRate
)
{
codecMaxValues
=
getCodecMaxValues
(
codecInfo
,
format
,
getStreamFormats
());
MediaFormat
mediaFormat
=
getMediaFormat
(
...
...
@@ -1173,11 +1172,9 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
* @param format The format for which the codec is being configured.
* @param streamFormats The possible stream formats.
* @return Suitable {@link CodecMaxValues}.
* @throws DecoderQueryException If an error occurs querying {@code codecInfo}.
*/
protected
CodecMaxValues
getCodecMaxValues
(
MediaCodecInfo
codecInfo
,
Format
format
,
Format
[]
streamFormats
)
throws
DecoderQueryException
{
MediaCodecInfo
codecInfo
,
Format
format
,
Format
[]
streamFormats
)
{
int
maxWidth
=
format
.
width
;
int
maxHeight
=
format
.
height
;
int
maxInputSize
=
getMaxInputSize
(
codecInfo
,
format
);
...
...
@@ -1227,17 +1224,15 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
}
/**
* Returns a maximum video size to use when configuring a codec for {@code format} in a way
*
that will allow possible adaptation to other compatible formats that are expected to have th
e
*
same
aspect ratio, but whose sizes are unknown.
* Returns a maximum video size to use when configuring a codec for {@code format} in a way
that
*
will allow possible adaptation to other compatible formats that are expected to have the sam
e
* aspect ratio, but whose sizes are unknown.
*
* @param codecInfo Information about the {@link MediaCodec} being configured.
* @param format The format for which the codec is being configured.
* @return The maximum video size to use, or null if the size of {@code format} should be used.
* @throws DecoderQueryException If an error occurs querying {@code codecInfo}.
*/
private
static
Point
getCodecMaxSize
(
MediaCodecInfo
codecInfo
,
Format
format
)
throws
DecoderQueryException
{
private
static
Point
getCodecMaxSize
(
MediaCodecInfo
codecInfo
,
Format
format
)
{
boolean
isVerticalVideo
=
format
.
height
>
format
.
width
;
int
formatLongEdgePx
=
isVerticalVideo
?
format
.
height
:
format
.
width
;
int
formatShortEdgePx
=
isVerticalVideo
?
format
.
width
:
format
.
height
;
...
...
@@ -1255,12 +1250,18 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
return
alignedSize
;
}
}
else
{
// Conservatively assume the codec requires 16px width and height alignment.
longEdgePx
=
Util
.
ceilDivide
(
longEdgePx
,
16
)
*
16
;
shortEdgePx
=
Util
.
ceilDivide
(
shortEdgePx
,
16
)
*
16
;
if
(
longEdgePx
*
shortEdgePx
<=
MediaCodecUtil
.
maxH264DecodableFrameSize
())
{
return
new
Point
(
isVerticalVideo
?
shortEdgePx
:
longEdgePx
,
isVerticalVideo
?
longEdgePx
:
shortEdgePx
);
try
{
// Conservatively assume the codec requires 16px width and height alignment.
longEdgePx
=
Util
.
ceilDivide
(
longEdgePx
,
16
)
*
16
;
shortEdgePx
=
Util
.
ceilDivide
(
shortEdgePx
,
16
)
*
16
;
if
(
longEdgePx
*
shortEdgePx
<=
MediaCodecUtil
.
maxH264DecodableFrameSize
())
{
return
new
Point
(
isVerticalVideo
?
shortEdgePx
:
longEdgePx
,
isVerticalVideo
?
longEdgePx
:
shortEdgePx
);
}
}
catch
(
DecoderQueryException
e
)
{
// We tried our best. Give up!
return
null
;
}
}
}
...
...
library/core/src/test/assets/webvtt/with_css_styles
View file @
f6297f4f
...
...
@@ -13,8 +13,6 @@ STYLE
::cue(#id2) {
color: peachpuff;
}
STYLE
::cue(v[voice="LaGord"]) { background-color: lime }
STYLE
...
...
library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java
View file @
f6297f4f
...
...
@@ -514,7 +514,7 @@ public final class TtmlDecoderTest {
assertThat
(
cue
.
position
).
isEqualTo
(
24
f
/
100
f
);
assertThat
(
cue
.
line
).
isEqualTo
(
28
f
/
100
f
);
assertThat
(
cue
.
size
).
isEqualTo
(
51
f
/
100
f
);
assertThat
(
cue
.
bitmapHeight
).
isEqualTo
(
Cue
.
DIMEN_UNSET
);
assertThat
(
cue
.
bitmapHeight
).
isEqualTo
(
12
f
/
100
f
);
cues
=
subtitle
.
getCues
(
4000000
);
assertThat
(
cues
).
hasSize
(
1
);
...
...
@@ -524,7 +524,7 @@ public final class TtmlDecoderTest {
assertThat
(
cue
.
position
).
isEqualTo
(
21
f
/
100
f
);
assertThat
(
cue
.
line
).
isEqualTo
(
35
f
/
100
f
);
assertThat
(
cue
.
size
).
isEqualTo
(
57
f
/
100
f
);
assertThat
(
cue
.
bitmapHeight
).
isEqualTo
(
Cue
.
DIMEN_UNSET
);
assertThat
(
cue
.
bitmapHeight
).
isEqualTo
(
6
f
/
100
f
);
cues
=
subtitle
.
getCues
(
7500000
);
assertThat
(
cues
).
hasSize
(
1
);
...
...
@@ -534,7 +534,7 @@ public final class TtmlDecoderTest {
assertThat
(
cue
.
position
).
isEqualTo
(
24
f
/
100
f
);
assertThat
(
cue
.
line
).
isEqualTo
(
28
f
/
100
f
);
assertThat
(
cue
.
size
).
isEqualTo
(
51
f
/
100
f
);
assertThat
(
cue
.
bitmapHeight
).
isEqualTo
(
Cue
.
DIMEN_UNSET
);
assertThat
(
cue
.
bitmapHeight
).
isEqualTo
(
12
f
/
100
f
);
}
@Test
...
...
@@ -549,7 +549,7 @@ public final class TtmlDecoderTest {
assertThat
(
cue
.
position
).
isEqualTo
(
307
f
/
1280
f
);
assertThat
(
cue
.
line
).
isEqualTo
(
562
f
/
720
f
);
assertThat
(
cue
.
size
).
isEqualTo
(
653
f
/
1280
f
);
assertThat
(
cue
.
bitmapHeight
).
isEqualTo
(
Cue
.
DIMEN_UNSET
);
assertThat
(
cue
.
bitmapHeight
).
isEqualTo
(
86
f
/
720
f
);
cues
=
subtitle
.
getCues
(
4000000
);
assertThat
(
cues
).
hasSize
(
1
);
...
...
@@ -559,7 +559,7 @@ public final class TtmlDecoderTest {
assertThat
(
cue
.
position
).
isEqualTo
(
269
f
/
1280
f
);
assertThat
(
cue
.
line
).
isEqualTo
(
612
f
/
720
f
);
assertThat
(
cue
.
size
).
isEqualTo
(
730
f
/
1280
f
);
assertThat
(
cue
.
bitmapHeight
).
isEqualTo
(
Cue
.
DIMEN_UNSET
);
assertThat
(
cue
.
bitmapHeight
).
isEqualTo
(
43
f
/
720
f
);
}
@Test
...
...
library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/CssParserTest.java
View file @
f6297f4f
...
...
@@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
com.google.android.exoplayer2.util.Util
;
import
java.util.List
;
import
org.junit.Before
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
...
...
@@ -87,21 +88,32 @@ public final class CssParserTest {
@Test
public
void
testParseMethodSimpleInput
()
{
String
styleBlock1
=
" ::cue { color : black; background-color: PapayaWhip }"
;
WebvttCssStyle
expectedStyle
=
new
WebvttCssStyle
();
String
styleBlock1
=
" ::cue { color : black; background-color: PapayaWhip }"
;
expectedStyle
.
setFontColor
(
0xFF000000
);
expectedStyle
.
setBackgroundColor
(
0xFFFFEFD5
);
assertParserProduces
(
expectedStyle
,
styleBlock1
);
assertParserProduces
(
styleBlock1
,
expectedStyle
);
String
styleBlock2
=
" ::cue { color : black }\n\n::cue { color : invalid }"
;
expectedStyle
=
new
WebvttCssStyle
();
expectedStyle
.
setFontColor
(
0xFF000000
);
assertParserProduces
(
expectedStyle
,
styleBlock2
);
assertParserProduces
(
styleBlock2
,
expectedStyle
);
String
styleBlock3
=
"
\n
::cue {\n background-color\n:#00fFFe}"
;
String
styleBlock3
=
"::cue {\n background-color\n:#00fFFe}"
;
expectedStyle
=
new
WebvttCssStyle
();
expectedStyle
.
setBackgroundColor
(
0xFF00FFFE
);
assertParserProduces
(
expectedStyle
,
styleBlock3
);
assertParserProduces
(
styleBlock3
,
expectedStyle
);
}
@Test
public
void
testParseMethodMultipleRulesInBlockInput
()
{
String
styleBlock
=
"::cue {\n background-color\n:#00fFFe} \n::cue {\n background-color\n:#00000000}\n"
;
WebvttCssStyle
expectedStyle
=
new
WebvttCssStyle
();
expectedStyle
.
setBackgroundColor
(
0xFF00FFFE
);
WebvttCssStyle
secondExpectedStyle
=
new
WebvttCssStyle
();
secondExpectedStyle
.
setBackgroundColor
(
0x000000
);
assertParserProduces
(
styleBlock
,
expectedStyle
,
secondExpectedStyle
);
}
@Test
...
...
@@ -116,7 +128,7 @@ public final class CssParserTest {
expectedStyle
.
setFontFamily
(
"courier"
);
expectedStyle
.
setBold
(
true
);
assertParserProduces
(
expectedStyle
,
styleBlock
);
assertParserProduces
(
styleBlock
,
expectedStyle
);
}
@Test
...
...
@@ -128,7 +140,7 @@ public final class CssParserTest {
expectedStyle
.
setBackgroundColor
(
0x190A0B0C
);
expectedStyle
.
setFontColor
(
0xFF010101
);
assertParserProduces
(
expectedStyle
,
styleBlock
);
assertParserProduces
(
styleBlock
,
expectedStyle
);
}
@Test
...
...
@@ -203,25 +215,29 @@ public final class CssParserTest {
assertThat
(
input
.
readLine
()).
isEqualTo
(
expectedLine
);
}
private
void
assertParserProduces
(
WebvttCssStyle
expected
,
String
styleBlock
){
private
void
assertParserProduces
(
String
styleBlock
,
WebvttCssStyle
...
expectedStyles
)
{
ParsableByteArray
input
=
new
ParsableByteArray
(
Util
.
getUtf8Bytes
(
styleBlock
));
WebvttCssStyle
actualElem
=
parser
.
parseBlock
(
input
);
assertThat
(
actualElem
.
hasBackgroundColor
()).
isEqualTo
(
expected
.
hasBackgroundColor
());
if
(
expected
.
hasBackgroundColor
())
{
assertThat
(
actualElem
.
getBackgroundColor
()).
isEqualTo
(
expected
.
getBackgroundColor
());
}
assertThat
(
actualElem
.
hasFontColor
()).
isEqualTo
(
expected
.
hasFontColor
());
if
(
expected
.
hasFontColor
())
{
assertThat
(
actualElem
.
getFontColor
()).
isEqualTo
(
expected
.
getFontColor
());
List
<
WebvttCssStyle
>
styles
=
parser
.
parseBlock
(
input
);
assertThat
(
styles
.
size
()).
isEqualTo
(
expectedStyles
.
length
);
for
(
int
i
=
0
;
i
<
expectedStyles
.
length
;
i
++)
{
WebvttCssStyle
expected
=
expectedStyles
[
i
];
WebvttCssStyle
actualElem
=
styles
.
get
(
i
);
assertThat
(
actualElem
.
hasBackgroundColor
()).
isEqualTo
(
expected
.
hasBackgroundColor
());
if
(
expected
.
hasBackgroundColor
())
{
assertThat
(
actualElem
.
getBackgroundColor
()).
isEqualTo
(
expected
.
getBackgroundColor
());
}
assertThat
(
actualElem
.
hasFontColor
()).
isEqualTo
(
expected
.
hasFontColor
());
if
(
expected
.
hasFontColor
())
{
assertThat
(
actualElem
.
getFontColor
()).
isEqualTo
(
expected
.
getFontColor
());
}
assertThat
(
actualElem
.
getFontFamily
()).
isEqualTo
(
expected
.
getFontFamily
());
assertThat
(
actualElem
.
getFontSize
()).
isEqualTo
(
expected
.
getFontSize
());
assertThat
(
actualElem
.
getFontSizeUnit
()).
isEqualTo
(
expected
.
getFontSizeUnit
());
assertThat
(
actualElem
.
getStyle
()).
isEqualTo
(
expected
.
getStyle
());
assertThat
(
actualElem
.
isLinethrough
()).
isEqualTo
(
expected
.
isLinethrough
());
assertThat
(
actualElem
.
isUnderline
()).
isEqualTo
(
expected
.
isUnderline
());
assertThat
(
actualElem
.
getTextAlign
()).
isEqualTo
(
expected
.
getTextAlign
());
}
assertThat
(
actualElem
.
getFontFamily
()).
isEqualTo
(
expected
.
getFontFamily
());
assertThat
(
actualElem
.
getFontSize
()).
isEqualTo
(
expected
.
getFontSize
());
assertThat
(
actualElem
.
getFontSizeUnit
()).
isEqualTo
(
expected
.
getFontSizeUnit
());
assertThat
(
actualElem
.
getStyle
()).
isEqualTo
(
expected
.
getStyle
());
assertThat
(
actualElem
.
isLinethrough
()).
isEqualTo
(
expected
.
isLinethrough
());
assertThat
(
actualElem
.
isUnderline
()).
isEqualTo
(
expected
.
isUnderline
());
assertThat
(
actualElem
.
getTextAlign
()).
isEqualTo
(
expected
.
getTextAlign
());
}
}
library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java
View file @
f6297f4f
...
...
@@ -341,6 +341,76 @@ public final class DefaultTrackSelectorTest {
assertFixedSelection
(
result
.
selections
.
get
(
0
),
trackGroups
,
formatWithSelectionFlag
);
}
/** Tests that adaptive audio track selections respect the maximum audio bitrate. */
public
void
testSelectAdaptiveAudioTrackGroupWithMaxBitrate
()
throws
ExoPlaybackException
{
Format
format128k
=
Format
.
createAudioSampleFormat
(
/* id= */
"128"
,
/* sampleMimeType= */
MimeTypes
.
AUDIO_AAC
,
/* codecs= */
"mp4a.40.2"
,
/* bitrate= */
128
*
1024
,
/* maxInputSize= */
Format
.
NO_VALUE
,
/* channelCount= */
2
,
/* sampleRate= */
44100
,
/* initializationData= */
null
,
/* drmInitData= */
null
,
/* selectionFlags= */
0
,
/* language= */
null
);
Format
format192k
=
Format
.
createAudioSampleFormat
(
/* id= */
"192"
,
/* sampleMimeType= */
MimeTypes
.
AUDIO_AAC
,
/* codecs= */
"mp4a.40.2"
,
/* bitrate= */
192
*
1024
,
/* maxInputSize= */
Format
.
NO_VALUE
,
/* channelCount= */
2
,
/* sampleRate= */
44100
,
/* initializationData= */
null
,
/* drmInitData= */
null
,
/* selectionFlags= */
0
,
/* language= */
null
);
Format
format256k
=
Format
.
createAudioSampleFormat
(
/* id= */
"256"
,
/* sampleMimeType= */
MimeTypes
.
AUDIO_AAC
,
/* codecs= */
"mp4a.40.2"
,
/* bitrate= */
256
*
1024
,
/* maxInputSize= */
Format
.
NO_VALUE
,
/* channelCount= */
2
,
/* sampleRate= */
44100
,
/* initializationData= */
null
,
/* drmInitData= */
null
,
/* selectionFlags= */
0
,
/* language= */
null
);
RendererCapabilities
[]
rendererCapabilities
=
{
ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES
};
TrackGroupArray
trackGroups
=
new
TrackGroupArray
(
new
TrackGroup
(
format192k
,
format128k
,
format256k
));
TrackSelectorResult
result
=
trackSelector
.
selectTracks
(
rendererCapabilities
,
trackGroups
,
periodId
,
TIMELINE
);
assertAdaptiveSelection
(
result
.
selections
.
get
(
0
),
trackGroups
.
get
(
0
),
0
,
1
,
2
);
trackSelector
.
setParameters
(
trackSelector
.
buildUponParameters
().
setMaxAudioBitrate
(
256
*
1024
-
1
));
result
=
trackSelector
.
selectTracks
(
rendererCapabilities
,
trackGroups
,
periodId
,
TIMELINE
);
assertAdaptiveSelection
(
result
.
selections
.
get
(
0
),
trackGroups
.
get
(
0
),
0
,
1
);
trackSelector
.
setParameters
(
trackSelector
.
buildUponParameters
().
setMaxAudioBitrate
(
192
*
1024
));
result
=
trackSelector
.
selectTracks
(
rendererCapabilities
,
trackGroups
,
periodId
,
TIMELINE
);
assertAdaptiveSelection
(
result
.
selections
.
get
(
0
),
trackGroups
.
get
(
0
),
0
,
1
);
trackSelector
.
setParameters
(
trackSelector
.
buildUponParameters
().
setMaxAudioBitrate
(
192
*
1024
-
1
));
result
=
trackSelector
.
selectTracks
(
rendererCapabilities
,
trackGroups
,
periodId
,
TIMELINE
);
assertAdaptiveSelection
(
result
.
selections
.
get
(
0
),
trackGroups
.
get
(
0
),
1
);
trackSelector
.
setParameters
(
trackSelector
.
buildUponParameters
().
setMaxAudioBitrate
(
10
));
result
=
trackSelector
.
selectTracks
(
rendererCapabilities
,
trackGroups
,
periodId
,
TIMELINE
);
assertAdaptiveSelection
(
result
.
selections
.
get
(
0
),
trackGroups
.
get
(
0
),
1
);
}
/**
* Tests that track selector will select audio track with language that match preferred language
* given by {@link Parameters}.
...
...
@@ -893,7 +963,6 @@ public final class DefaultTrackSelectorTest {
Format
forcedDefault
=
buildTextFormat
(
"forcedDefault"
,
"eng"
,
C
.
SELECTION_FLAG_FORCED
|
C
.
SELECTION_FLAG_DEFAULT
);
Format
defaultOnly
=
buildTextFormat
(
"defaultOnly"
,
"eng"
,
C
.
SELECTION_FLAG_DEFAULT
);
Format
forcedOnlySpanish
=
buildTextFormat
(
"forcedOnlySpanish"
,
"spa"
,
C
.
SELECTION_FLAG_FORCED
);
Format
noFlag
=
buildTextFormat
(
"noFlag"
,
"eng"
);
RendererCapabilities
[]
textRendererCapabilities
=
...
...
library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java
View file @
f6297f4f
...
...
@@ -35,6 +35,7 @@ import com.google.android.exoplayer2.offline.Downloader;
import
com.google.android.exoplayer2.offline.DownloaderConstructorHelper
;
import
com.google.android.exoplayer2.offline.DownloaderFactory
;
import
com.google.android.exoplayer2.offline.StreamKey
;
import
com.google.android.exoplayer2.testutil.CacheAsserts.RequestSet
;
import
com.google.android.exoplayer2.testutil.FakeDataSet
;
import
com.google.android.exoplayer2.testutil.FakeDataSource
;
import
com.google.android.exoplayer2.testutil.FakeDataSource.Factory
;
...
...
@@ -108,7 +109,7 @@ public class DashDownloaderTest {
DashDownloader
dashDownloader
=
getDashDownloader
(
fakeDataSet
,
new
StreamKey
(
0
,
0
,
0
));
dashDownloader
.
download
(
progressListener
);
assertCachedData
(
cache
,
fakeDataSet
);
assertCachedData
(
cache
,
new
RequestSet
(
fakeDataSet
).
useBoundedDataSpecFor
(
"audio_init_data"
)
);
}
@Test
...
...
@@ -127,7 +128,7 @@ public class DashDownloaderTest {
DashDownloader
dashDownloader
=
getDashDownloader
(
fakeDataSet
,
new
StreamKey
(
0
,
0
,
0
));
dashDownloader
.
download
(
progressListener
);
assertCachedData
(
cache
,
fakeDataSet
);
assertCachedData
(
cache
,
new
RequestSet
(
fakeDataSet
).
useBoundedDataSpecFor
(
"audio_init_data"
)
);
}
@Test
...
...
@@ -146,7 +147,7 @@ public class DashDownloaderTest {
DashDownloader
dashDownloader
=
getDashDownloader
(
fakeDataSet
,
new
StreamKey
(
0
,
0
,
0
),
new
StreamKey
(
0
,
1
,
0
));
dashDownloader
.
download
(
progressListener
);
assertCachedData
(
cache
,
fakeDataSet
);
assertCachedData
(
cache
,
new
RequestSet
(
fakeDataSet
).
useBoundedDataSpecFor
(
"audio_init_data"
)
);
}
@Test
...
...
@@ -167,7 +168,7 @@ public class DashDownloaderTest {
DashDownloader
dashDownloader
=
getDashDownloader
(
fakeDataSet
);
dashDownloader
.
download
(
progressListener
);
assertCachedData
(
cache
,
fakeDataSet
);
assertCachedData
(
cache
,
new
RequestSet
(
fakeDataSet
).
useBoundedDataSpecFor
(
"audio_init_data"
)
);
}
@Test
...
...
@@ -256,7 +257,7 @@ public class DashDownloaderTest {
// Expected.
}
dashDownloader
.
download
(
progressListener
);
assertCachedData
(
cache
,
fakeDataSet
);
assertCachedData
(
cache
,
new
RequestSet
(
fakeDataSet
).
useBoundedDataSpecFor
(
"audio_init_data"
)
);
}
@Test
...
...
library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadManagerDashTest.java
View file @
f6297f4f
...
...
@@ -33,6 +33,7 @@ import com.google.android.exoplayer2.offline.DownloadRequest;
import
com.google.android.exoplayer2.offline.DownloaderConstructorHelper
;
import
com.google.android.exoplayer2.offline.StreamKey
;
import
com.google.android.exoplayer2.scheduler.Requirements
;
import
com.google.android.exoplayer2.testutil.CacheAsserts.RequestSet
;
import
com.google.android.exoplayer2.testutil.DummyMainThread
;
import
com.google.android.exoplayer2.testutil.DummyMainThread.TestRunnable
;
import
com.google.android.exoplayer2.testutil.FakeDataSet
;
...
...
@@ -154,7 +155,7 @@ public class DownloadManagerDashTest {
public
void
testHandleDownloadRequest
()
throws
Throwable
{
handleDownloadRequest
(
fakeStreamKey1
,
fakeStreamKey2
);
blockUntilTasksCompleteAndThrowAnyDownloadError
();
assertCachedData
(
cache
,
fakeDataSet
);
assertCachedData
(
cache
,
new
RequestSet
(
fakeDataSet
).
useBoundedDataSpecFor
(
"audio_init_data"
)
);
}
@Test
...
...
@@ -162,7 +163,7 @@ public class DownloadManagerDashTest {
handleDownloadRequest
(
fakeStreamKey1
);
handleDownloadRequest
(
fakeStreamKey2
);
blockUntilTasksCompleteAndThrowAnyDownloadError
();
assertCachedData
(
cache
,
fakeDataSet
);
assertCachedData
(
cache
,
new
RequestSet
(
fakeDataSet
).
useBoundedDataSpecFor
(
"audio_init_data"
)
);
}
@Test
...
...
@@ -176,7 +177,7 @@ public class DownloadManagerDashTest {
handleDownloadRequest
(
fakeStreamKey1
);
blockUntilTasksCompleteAndThrowAnyDownloadError
();
assertCachedData
(
cache
,
fakeDataSet
);
assertCachedData
(
cache
,
new
RequestSet
(
fakeDataSet
).
useBoundedDataSpecFor
(
"audio_init_data"
)
);
}
@Test
...
...
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java
View file @
f6297f4f
...
...
@@ -322,6 +322,7 @@ import java.util.Map;
if
(
enabledTrackGroupCount
==
0
)
{
chunkSource
.
reset
();
downstreamTrackFormat
=
null
;
pendingResetUpstreamFormats
=
true
;
mediaChunks
.
clear
();
if
(
loader
.
isLoading
())
{
if
(
sampleQueuesBuilt
)
{
...
...
library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java
View file @
f6297f4f
...
...
@@ -44,6 +44,7 @@ import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
import
com.google.android.exoplayer2.offline.DownloaderFactory
;
import
com.google.android.exoplayer2.offline.StreamKey
;
import
com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist
;
import
com.google.android.exoplayer2.testutil.CacheAsserts.RequestSet
;
import
com.google.android.exoplayer2.testutil.FakeDataSet
;
import
com.google.android.exoplayer2.testutil.FakeDataSource.Factory
;
import
com.google.android.exoplayer2.upstream.DummyDataSource
;
...
...
@@ -129,12 +130,13 @@ public class HlsDownloaderTest {
assertCachedData
(
cache
,
fakeDataSet
,
MASTER_PLAYLIST_URI
,
MEDIA_PLAYLIST_1_URI
,
MEDIA_PLAYLIST_1_DIR
+
"fileSequence0.ts"
,
MEDIA_PLAYLIST_1_DIR
+
"fileSequence1.ts"
,
MEDIA_PLAYLIST_1_DIR
+
"fileSequence2.ts"
);
new
RequestSet
(
fakeDataSet
)
.
subset
(
MASTER_PLAYLIST_URI
,
MEDIA_PLAYLIST_1_URI
,
MEDIA_PLAYLIST_1_DIR
+
"fileSequence0.ts"
,
MEDIA_PLAYLIST_1_DIR
+
"fileSequence1.ts"
,
MEDIA_PLAYLIST_1_DIR
+
"fileSequence2.ts"
));
}
@Test
...
...
@@ -186,11 +188,12 @@ public class HlsDownloaderTest {
assertCachedData
(
cache
,
fakeDataSet
,
MEDIA_PLAYLIST_1_URI
,
MEDIA_PLAYLIST_1_DIR
+
"fileSequence0.ts"
,
MEDIA_PLAYLIST_1_DIR
+
"fileSequence1.ts"
,
MEDIA_PLAYLIST_1_DIR
+
"fileSequence2.ts"
);
new
RequestSet
(
fakeDataSet
)
.
subset
(
MEDIA_PLAYLIST_1_URI
,
MEDIA_PLAYLIST_1_DIR
+
"fileSequence0.ts"
,
MEDIA_PLAYLIST_1_DIR
+
"fileSequence1.ts"
,
MEDIA_PLAYLIST_1_DIR
+
"fileSequence2.ts"
));
}
@Test
...
...
library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java
View file @
f6297f4f
...
...
@@ -220,11 +220,26 @@ public class DefaultTimeBar extends View implements TimeBar {
private
@Nullable
long
[]
adGroupTimesMs
;
private
@Nullable
boolean
[]
playedAdGroups
;
/** Creates a new time bar. */
public
DefaultTimeBar
(
Context
context
)
{
this
(
context
,
null
);
}
public
DefaultTimeBar
(
Context
context
,
@Nullable
AttributeSet
attrs
)
{
this
(
context
,
attrs
,
0
);
}
public
DefaultTimeBar
(
Context
context
,
@Nullable
AttributeSet
attrs
,
int
defStyleAttr
)
{
this
(
context
,
attrs
,
defStyleAttr
,
attrs
);
}
// Suppress warnings due to usage of View methods in the constructor.
@SuppressWarnings
(
"nullness:method.invocation.invalid"
)
public
DefaultTimeBar
(
Context
context
,
AttributeSet
attrs
)
{
super
(
context
,
attrs
);
public
DefaultTimeBar
(
Context
context
,
@Nullable
AttributeSet
attrs
,
int
defStyleAttr
,
@Nullable
AttributeSet
timebarAttrs
)
{
super
(
context
,
attrs
,
defStyleAttr
);
seekBounds
=
new
Rect
();
progressBar
=
new
Rect
();
bufferedBar
=
new
Rect
();
...
...
@@ -251,9 +266,9 @@ public class DefaultTimeBar extends View implements TimeBar {
int
defaultScrubberEnabledSize
=
dpToPx
(
density
,
DEFAULT_SCRUBBER_ENABLED_SIZE_DP
);
int
defaultScrubberDisabledSize
=
dpToPx
(
density
,
DEFAULT_SCRUBBER_DISABLED_SIZE_DP
);
int
defaultScrubberDraggedSize
=
dpToPx
(
density
,
DEFAULT_SCRUBBER_DRAGGED_SIZE_DP
);
if
(
a
ttrs
!=
null
)
{
TypedArray
a
=
context
.
getTheme
().
obtainStyledAttributes
(
attrs
,
R
.
styleable
.
DefaultTimeBar
,
0
,
0
);
if
(
timebarA
ttrs
!=
null
)
{
TypedArray
a
=
context
.
getTheme
().
obtainStyledAttributes
(
timebarAttrs
,
R
.
styleable
.
DefaultTimeBar
,
0
,
0
);
try
{
scrubberDrawable
=
a
.
getDrawable
(
R
.
styleable
.
DefaultTimeBar_scrubber_drawable
);
if
(
scrubberDrawable
!=
null
)
{
...
...
library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java
View file @
f6297f4f
...
...
@@ -28,6 +28,7 @@ import android.view.KeyEvent;
import
android.view.LayoutInflater
;
import
android.view.MotionEvent
;
import
android.view.View
;
import
android.view.ViewGroup
;
import
android.widget.FrameLayout
;
import
android.widget.ImageView
;
import
android.widget.TextView
;
...
...
@@ -97,6 +98,9 @@ import java.util.Locale;
* <li>Corresponding method: None
* <li>Default: {@code R.layout.exo_player_control_view}
* </ul>
* <li>All attributes that can be set on {@link DefaultTimeBar} can also be set on a
* PlayerControlView, and will be propagated to the inflated {@link DefaultTimeBar} unless the
* layout is overridden to specify a custom {@code exo_progress} (see below).
* </ul>
*
* <h3>Overriding the layout file</h3>
...
...
@@ -154,7 +158,15 @@ import java.util.Locale;
* <ul>
* <li>Type: {@link TextView}
* </ul>
* <li><b>{@code exo_progress_placeholder}</b> - A placeholder that's replaced with the inflated
* {@link DefaultTimeBar}. Ignored if an {@code exo_progress} view exists.
* <ul>
* <li>Type: {@link View}
* </ul>
* <li><b>{@code exo_progress}</b> - Time bar that's updated during playback and allows seeking.
* {@link DefaultTimeBar} attributes set on the PlayerControlView will not be automatically
* propagated through to this instance. If a view exists with this id, any {@code
* exo_progress_placeholder} view will be ignored.
* <ul>
* <li>Type: {@link TimeBar}
* </ul>
...
...
@@ -188,6 +200,18 @@ public class PlayerControlView extends FrameLayout {
void
onVisibilityChange
(
int
visibility
);
}
/** Listener to be notified when progress has been updated. */
public
interface
ProgressUpdateListener
{
/**
* Called when progress needs to be updated.
*
* @param position The current position.
* @param bufferedPosition The current buffered position.
*/
void
onProgressUpdate
(
long
position
,
long
bufferedPosition
);
}
/** The default fast forward increment, in milliseconds. */
public
static
final
int
DEFAULT_FAST_FORWARD_MS
=
15000
;
/** The default rewind increment, in milliseconds. */
...
...
@@ -235,7 +259,8 @@ public class PlayerControlView extends FrameLayout {
@Nullable
private
Player
player
;
private
com
.
google
.
android
.
exoplayer2
.
ControlDispatcher
controlDispatcher
;
private
VisibilityListener
visibilityListener
;
@Nullable
private
VisibilityListener
visibilityListener
;
@Nullable
private
ProgressUpdateListener
progressUpdateListener
;
@Nullable
private
PlaybackPreparer
playbackPreparer
;
private
boolean
isAttachedToWindow
;
...
...
@@ -317,9 +342,27 @@ public class PlayerControlView extends FrameLayout {
LayoutInflater
.
from
(
context
).
inflate
(
controllerLayoutId
,
this
);
setDescendantFocusability
(
FOCUS_AFTER_DESCENDANTS
);
TimeBar
customTimeBar
=
findViewById
(
R
.
id
.
exo_progress
);
View
timeBarPlaceholder
=
findViewById
(
R
.
id
.
exo_progress_placeholder
);
if
(
customTimeBar
!=
null
)
{
timeBar
=
customTimeBar
;
}
else
if
(
timeBarPlaceholder
!=
null
)
{
// Propagate attrs as timebarAttrs so that DefaultTimeBar's custom attributes are transferred,
// but standard attributes (e.g. background) are not.
DefaultTimeBar
defaultTimeBar
=
new
DefaultTimeBar
(
context
,
null
,
0
,
playbackAttrs
);
defaultTimeBar
.
setId
(
R
.
id
.
exo_progress
);
defaultTimeBar
.
setLayoutParams
(
timeBarPlaceholder
.
getLayoutParams
());
ViewGroup
parent
=
((
ViewGroup
)
timeBarPlaceholder
.
getParent
());
int
timeBarIndex
=
parent
.
indexOfChild
(
timeBarPlaceholder
);
parent
.
removeView
(
timeBarPlaceholder
);
parent
.
addView
(
defaultTimeBar
,
timeBarIndex
);
timeBar
=
defaultTimeBar
;
}
else
{
timeBar
=
null
;
}
durationView
=
findViewById
(
R
.
id
.
exo_duration
);
positionView
=
findViewById
(
R
.
id
.
exo_position
);
timeBar
=
findViewById
(
R
.
id
.
exo_progress
);
if
(
timeBar
!=
null
)
{
timeBar
.
addListener
(
componentListener
);
}
...
...
@@ -455,6 +498,15 @@ public class PlayerControlView extends FrameLayout {
}
/**
* Sets the {@link ProgressUpdateListener}.
*
* @param listener The listener to be notified about when progress is updated.
*/
public
void
setProgressUpdateListener
(
@Nullable
ProgressUpdateListener
listener
)
{
this
.
progressUpdateListener
=
listener
;
}
/**
* Sets the {@link PlaybackPreparer}.
*
* @param playbackPreparer The {@link PlaybackPreparer}.
...
...
@@ -855,6 +907,9 @@ public class PlayerControlView extends FrameLayout {
timeBar
.
setPosition
(
position
);
timeBar
.
setBufferedPosition
(
bufferedPosition
);
}
if
(
progressUpdateListener
!=
null
)
{
progressUpdateListener
.
onProgressUpdate
(
position
,
bufferedPosition
);
}
// Cancel any pending updates and schedule a new one if necessary.
removeCallbacks
(
updateProgressAction
);
...
...
library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java
View file @
f6297f4f
...
...
@@ -966,7 +966,8 @@ public class PlayerNotificationManager {
@Nullable
NotificationCompat
.
Builder
builder
,
boolean
ongoing
,
@Nullable
Bitmap
largeIcon
)
{
if
(
player
.
getPlaybackState
()
==
Player
.
STATE_IDLE
)
{
if
(
player
.
getPlaybackState
()
==
Player
.
STATE_IDLE
&&
(
player
.
getCurrentTimeline
().
isEmpty
()
||
playbackPreparer
==
null
))
{
builderActions
=
null
;
return
null
;
}
...
...
library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java
View file @
f6297f4f
...
...
@@ -35,7 +35,6 @@ import android.util.AttributeSet;
import
android.view.KeyEvent
;
import
android.view.LayoutInflater
;
import
android.view.MotionEvent
;
import
android.view.Surface
;
import
android.view.SurfaceView
;
import
android.view.TextureView
;
import
android.view.View
;
...
...
@@ -50,7 +49,6 @@ import com.google.android.exoplayer2.ExoPlaybackException;
import
com.google.android.exoplayer2.PlaybackPreparer
;
import
com.google.android.exoplayer2.Player
;
import
com.google.android.exoplayer2.Player.DiscontinuityReason
;
import
com.google.android.exoplayer2.Player.VideoComponent
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.metadata.id3.ApicFrame
;
import
com.google.android.exoplayer2.source.TrackGroupArray
;
...
...
@@ -165,9 +163,10 @@ import java.util.List;
* <li>Corresponding method: None
* <li>Default: {@code R.layout.exo_player_control_view}
* </ul>
* <li>All attributes that can be set on a {@link PlayerControlView} can also be set on a
* PlayerView, and will be propagated to the inflated {@link PlayerControlView} unless the
* layout is overridden to specify a custom {@code exo_controller} (see below).
* <li>All attributes that can be set on {@link PlayerControlView} and {@link DefaultTimeBar} can
* also be set on a PlayerView, and will be propagated to the inflated {@link
* PlayerControlView} unless the layout is overridden to specify a custom {@code
* exo_controller} (see below).
* </ul>
*
* <h3>Overriding the layout file</h3>
...
...
@@ -217,9 +216,10 @@ import java.util.List;
* <li>Type: {@link View}
* </ul>
* <li><b>{@code exo_controller}</b> - An already inflated {@link PlayerControlView}. Allows use
* of a custom extension of {@link PlayerControlView}. Note that attributes such as {@code
* rewind_increment} will not be automatically propagated through to this instance. If a view
* exists with this id, any {@code exo_controller_placeholder} view will be ignored.
* of a custom extension of {@link PlayerControlView}. {@link PlayerControlView} and {@link
* DefaultTimeBar} attributes set on the PlayerView will not be automatically propagated
* through to this instance. If a view exists with this id, any {@code
* exo_controller_placeholder} view will be ignored.
* <ul>
* <li>Type: {@link PlayerControlView}
* </ul>
...
...
@@ -303,6 +303,7 @@ public class PlayerView extends FrameLayout implements AdsLoader.AdViewProvider
private
boolean
controllerHideDuringAds
;
private
boolean
controllerHideOnTouch
;
private
int
textureViewRotation
;
private
boolean
isTouching
;
public
PlayerView
(
Context
context
)
{
this
(
context
,
null
);
...
...
@@ -405,7 +406,6 @@ public class PlayerView extends FrameLayout implements AdsLoader.AdViewProvider
break
;
case
SURFACE_TYPE_MONO360_VIEW:
SphericalSurfaceView
sphericalSurfaceView
=
new
SphericalSurfaceView
(
context
);
sphericalSurfaceView
.
setSurfaceListener
(
componentListener
);
sphericalSurfaceView
.
setSingleTapListener
(
componentListener
);
surfaceView
=
sphericalSurfaceView
;
break
;
...
...
@@ -459,8 +459,9 @@ public class PlayerView extends FrameLayout implements AdsLoader.AdViewProvider
this
.
controller
=
customController
;
}
else
if
(
controllerPlaceholder
!=
null
)
{
// Propagate attrs as playbackAttrs so that PlayerControlView's custom attributes are
// transferred, but standard
FrameLayout
attributes (e.g. background) are not.
// transferred, but standard attributes (e.g. background) are not.
this
.
controller
=
new
PlayerControlView
(
context
,
null
,
0
,
attrs
);
controller
.
setId
(
R
.
id
.
exo_controller
);
controller
.
setLayoutParams
(
controllerPlaceholder
.
getLayoutParams
());
ViewGroup
parent
=
((
ViewGroup
)
controllerPlaceholder
.
getParent
());
int
controllerIndex
=
parent
.
indexOfChild
(
controllerPlaceholder
);
...
...
@@ -771,11 +772,20 @@ public class PlayerView extends FrameLayout implements AdsLoader.AdViewProvider
if
(
player
!=
null
&&
player
.
isPlayingAd
())
{
return
super
.
dispatchKeyEvent
(
event
);
}
boolean
isDpadWhenControlHidden
=
isDpadKey
(
event
.
getKeyCode
())
&&
useController
&&
!
controller
.
isVisible
();
boolean
handled
=
isDpadWhenControlHidden
||
dispatchMediaKeyEvent
(
event
)
||
super
.
dispatchKeyEvent
(
event
);
if
(
handled
)
{
boolean
isDpadAndUseController
=
isDpadKey
(
event
.
getKeyCode
())
&&
useController
;
boolean
handled
=
false
;
if
(
isDpadAndUseController
&&
!
controller
.
isVisible
())
{
// Handle the key event by showing the controller.
maybeShowController
(
true
);
handled
=
true
;
}
else
if
(
dispatchMediaKeyEvent
(
event
)
||
super
.
dispatchKeyEvent
(
event
))
{
// The key event was handled as a media key or by the super class. We should also show the
// controller, or extend its show timeout if already visible.
maybeShowController
(
true
);
handled
=
true
;
}
else
if
(
isDpadAndUseController
)
{
// The key event wasn't handled, but we should extend the controller's show timeout.
maybeShowController
(
true
);
}
return
handled
;
...
...
@@ -1039,11 +1049,21 @@ public class PlayerView extends FrameLayout implements AdsLoader.AdViewProvider
}
@Override
public
boolean
onTouchEvent
(
MotionEvent
ev
)
{
if
(
ev
.
getActionMasked
()
!=
MotionEvent
.
ACTION_DOWN
)
{
return
false
;
public
boolean
onTouchEvent
(
MotionEvent
event
)
{
switch
(
event
.
getAction
())
{
case
MotionEvent
.
ACTION_DOWN
:
isTouching
=
true
;
return
true
;
case
MotionEvent
.
ACTION_UP
:
if
(
isTouching
)
{
isTouching
=
false
;
performClick
();
return
true
;
}
return
false
;
default
:
return
false
;
}
return
performClick
();
}
@Override
...
...
@@ -1359,7 +1379,6 @@ public class PlayerView extends FrameLayout implements AdsLoader.AdViewProvider
TextOutput
,
VideoListener
,
OnLayoutChangeListener
,
SphericalSurfaceView
.
SurfaceListener
,
SingleTapListener
{
// TextOutput implementation
...
...
@@ -1449,18 +1468,6 @@ public class PlayerView extends FrameLayout implements AdsLoader.AdViewProvider
applyTextureViewRotation
((
TextureView
)
view
,
textureViewRotation
);
}
// SphericalSurfaceView.SurfaceTextureListener implementation
@Override
public
void
surfaceChanged
(
@Nullable
Surface
surface
)
{
if
(
player
!=
null
)
{
VideoComponent
videoComponent
=
player
.
getVideoComponent
();
if
(
videoComponent
!=
null
)
{
videoComponent
.
setVideoSurface
(
surface
);
}
}
}
// SingleTapListener implementation
@Override
...
...
library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java
View file @
f6297f4f
...
...
@@ -362,10 +362,16 @@ import com.google.android.exoplayer2.util.Util;
int
width
=
Math
.
round
(
parentWidth
*
cueSize
);
int
height
=
cueBitmapHeight
!=
Cue
.
DIMEN_UNSET
?
Math
.
round
(
parentHeight
*
cueBitmapHeight
)
:
Math
.
round
(
width
*
((
float
)
cueBitmap
.
getHeight
()
/
cueBitmap
.
getWidth
()));
int
x
=
Math
.
round
(
cueLineAnchor
==
Cue
.
ANCHOR_TYPE_END
?
(
anchorX
-
width
)
:
cueLineAnchor
==
Cue
.
ANCHOR_TYPE_MIDDLE
?
(
anchorX
-
(
width
/
2
))
:
anchorX
);
int
y
=
Math
.
round
(
cuePositionAnchor
==
Cue
.
ANCHOR_TYPE_END
?
(
anchorY
-
height
)
:
cuePositionAnchor
==
Cue
.
ANCHOR_TYPE_MIDDLE
?
(
anchorY
-
(
height
/
2
))
:
anchorY
);
int
x
=
Math
.
round
(
cuePositionAnchor
==
Cue
.
ANCHOR_TYPE_END
?
(
anchorX
-
width
)
:
cuePositionAnchor
==
Cue
.
ANCHOR_TYPE_MIDDLE
?
(
anchorX
-
(
width
/
2
))
:
anchorX
);
int
y
=
Math
.
round
(
cueLineAnchor
==
Cue
.
ANCHOR_TYPE_END
?
(
anchorY
-
height
)
:
cueLineAnchor
==
Cue
.
ANCHOR_TYPE_MIDDLE
?
(
anchorY
-
(
height
/
2
))
:
anchorY
);
bitmapRect
=
new
Rect
(
x
,
y
,
x
+
width
,
y
+
height
);
}
...
...
library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalSurfaceView.java
View file @
f6297f4f
...
...
@@ -53,20 +53,6 @@ import javax.microedition.khronos.opengles.GL10;
*/
public
final
class
SphericalSurfaceView
extends
GLSurfaceView
{
/**
* This listener can be used to be notified when the {@link Surface} associated with this view is
* changed.
*/
public
interface
SurfaceListener
{
/**
* Invoked when the surface is changed or there isn't one anymore. Any previous surface
* shouldn't be used after this call.
*
* @param surface The new surface or null if there isn't one anymore.
*/
void
surfaceChanged
(
@Nullable
Surface
surface
);
}
// Arbitrary vertical field of view.
private
static
final
int
FIELD_OF_VIEW_DEGREES
=
90
;
private
static
final
float
Z_NEAR
=
.
1
f
;
...
...
@@ -84,7 +70,6 @@ public final class SphericalSurfaceView extends GLSurfaceView {
private
final
Handler
mainHandler
;
private
final
TouchTracker
touchTracker
;
private
final
SceneRenderer
scene
;
private
@Nullable
SurfaceListener
surfaceListener
;
private
@Nullable
SurfaceTexture
surfaceTexture
;
private
@Nullable
Surface
surface
;
private
@Nullable
Player
.
VideoComponent
videoComponent
;
...
...
@@ -156,15 +141,6 @@ public final class SphericalSurfaceView extends GLSurfaceView {
}
}
/**
* Sets the {@link SurfaceListener} used to listen to surface events.
*
* @param listener The listener for surface events.
*/
public
void
setSurfaceListener
(
@Nullable
SurfaceListener
listener
)
{
surfaceListener
=
listener
;
}
/** Sets the {@link SingleTapListener} used to listen to single tap events on this view. */
public
void
setSingleTapListener
(
@Nullable
SingleTapListener
listener
)
{
touchTracker
.
setSingleTapListener
(
listener
);
...
...
@@ -196,8 +172,8 @@ public final class SphericalSurfaceView extends GLSurfaceView {
mainHandler
.
post
(
()
->
{
if
(
surface
!=
null
)
{
if
(
surfaceListener
!=
null
)
{
surfaceListener
.
surfaceChanged
(
null
);
if
(
videoComponent
!=
null
)
{
videoComponent
.
clearVideoSurface
(
surface
);
}
releaseSurface
(
surfaceTexture
,
surface
);
surfaceTexture
=
null
;
...
...
@@ -214,8 +190,8 @@ public final class SphericalSurfaceView extends GLSurfaceView {
Surface
oldSurface
=
this
.
surface
;
this
.
surfaceTexture
=
surfaceTexture
;
this
.
surface
=
new
Surface
(
surfaceTexture
);
if
(
surfaceListener
!=
null
)
{
surfaceListener
.
surfaceChanged
(
surface
);
if
(
videoComponent
!=
null
)
{
videoComponent
.
setVideoSurface
(
surface
);
}
releaseSurface
(
oldSurfaceTexture
,
oldSurface
);
});
...
...
library/ui/src/main/res/layout/exo_playback_control_view.xml
View file @
f6297f4f
...
...
@@ -76,8 +76,7 @@
android:includeFontPadding=
"false"
android:textColor=
"#FFBEBEBE"
/>
<com.google.android.exoplayer2.ui.DefaultTimeBar
android:id=
"@id/exo_progress"
<View
android:id=
"@id/exo_progress_placeholder"
android:layout_width=
"0dp"
android:layout_weight=
"1"
android:layout_height=
"26dp"
/>
...
...
library/ui/src/main/res/values/attrs.xml
View file @
f6297f4f
...
...
@@ -24,25 +24,43 @@
<enum
name=
"zoom"
value=
"4"
/>
</attr>
<!-- Must be kept in sync with
SimpleExo
PlayerView -->
<!-- Must be kept in sync with PlayerView -->
<attr
name=
"surface_type"
format=
"enum"
>
<enum
name=
"none"
value=
"0"
/>
<enum
name=
"surface_view"
value=
"1"
/>
<enum
name=
"texture_view"
value=
"2"
/>
<enum
name=
"spherical_view"
value=
"3"
/>
</attr>
<attr
name=
"show_timeout"
format=
"integer"
/>
<attr
name=
"rewind_increment"
format=
"integer"
/>
<attr
name=
"fastforward_increment"
format=
"integer"
/>
<attr
name=
"player_layout_id"
format=
"reference"
/>
<attr
name=
"controller_layout_id"
format=
"reference"
/>
<!-- Must be kept in sync with RepeatModeUtil -->
<attr
name=
"repeat_toggle_modes"
>
<flag
name=
"none"
value=
"0"
/>
<flag
name=
"one"
value=
"1"
/>
<flag
name=
"all"
value=
"2"
/>
</attr>
<!-- PlayerControlView attributes -->
<attr
name=
"show_timeout"
format=
"integer"
/>
<attr
name=
"rewind_increment"
format=
"integer"
/>
<attr
name=
"fastforward_increment"
format=
"integer"
/>
<attr
name=
"show_shuffle_button"
format=
"boolean"
/>
<attr
name=
"time_bar_min_update_interval"
format=
"integer"
/>
<attr
name=
"controller_layout_id"
format=
"reference"
/>
<!-- DefaultTimeBar attributes -->
<attr
name=
"bar_height"
format=
"dimension"
/>
<attr
name=
"touch_target_height"
format=
"dimension"
/>
<attr
name=
"ad_marker_width"
format=
"dimension"
/>
<attr
name=
"scrubber_enabled_size"
format=
"dimension"
/>
<attr
name=
"scrubber_disabled_size"
format=
"dimension"
/>
<attr
name=
"scrubber_dragged_size"
format=
"dimension"
/>
<attr
name=
"scrubber_drawable"
format=
"reference"
/>
<attr
name=
"played_color"
format=
"color"
/>
<attr
name=
"scrubber_color"
format=
"color"
/>
<attr
name=
"buffered_color"
format=
"color"
/>
<attr
name=
"unplayed_color"
format=
"color"
/>
<attr
name=
"ad_marker_color"
format=
"color"
/>
<attr
name=
"played_ad_marker_color"
format=
"color"
/>
<declare-styleable
name=
"PlayerView"
>
<attr
name=
"use_artwork"
format=
"boolean"
/>
...
...
@@ -58,9 +76,11 @@
<enum
name=
"always"
value=
"2"
/>
</attr>
<attr
name=
"keep_content_on_player_reset"
format=
"boolean"
/>
<attr
name=
"resize_mode"
/>
<attr
name=
"player_layout_id"
format=
"reference"
/>
<attr
name=
"surface_type"
/>
<attr
name=
"player_layout_id"
/>
<!-- AspectRatioFrameLayout attributes -->
<attr
name=
"resize_mode"
/>
<!-- PlayerControlView attributes -->
<attr
name=
"show_timeout"
/>
<attr
name=
"rewind_increment"
/>
...
...
@@ -69,6 +89,20 @@
<attr
name=
"show_shuffle_button"
/>
<attr
name=
"time_bar_min_update_interval"
/>
<attr
name=
"controller_layout_id"
/>
<!-- DefaultTimeBar attributes -->
<attr
name=
"bar_height"
/>
<attr
name=
"touch_target_height"
/>
<attr
name=
"ad_marker_width"
/>
<attr
name=
"scrubber_enabled_size"
/>
<attr
name=
"scrubber_disabled_size"
/>
<attr
name=
"scrubber_dragged_size"
/>
<attr
name=
"scrubber_drawable"
/>
<attr
name=
"played_color"
/>
<attr
name=
"scrubber_color"
/>
<attr
name=
"buffered_color"
/>
<attr
name=
"unplayed_color"
/>
<attr
name=
"ad_marker_color"
/>
<attr
name=
"played_ad_marker_color"
/>
</declare-styleable>
<declare-styleable
name=
"AspectRatioFrameLayout"
>
...
...
@@ -83,22 +117,36 @@
<attr
name=
"show_shuffle_button"
/>
<attr
name=
"time_bar_min_update_interval"
/>
<attr
name=
"controller_layout_id"
/>
<!-- DefaultTimeBar attributes -->
<attr
name=
"bar_height"
/>
<attr
name=
"touch_target_height"
/>
<attr
name=
"ad_marker_width"
/>
<attr
name=
"scrubber_enabled_size"
/>
<attr
name=
"scrubber_disabled_size"
/>
<attr
name=
"scrubber_dragged_size"
/>
<attr
name=
"scrubber_drawable"
/>
<attr
name=
"played_color"
/>
<attr
name=
"scrubber_color"
/>
<attr
name=
"buffered_color"
/>
<attr
name=
"unplayed_color"
/>
<attr
name=
"ad_marker_color"
/>
<attr
name=
"played_ad_marker_color"
/>
</declare-styleable>
<declare-styleable
name=
"DefaultTimeBar"
>
<attr
name=
"bar_height"
format=
"dimension"
/>
<attr
name=
"touch_target_height"
format=
"dimension"
/>
<attr
name=
"ad_marker_width"
format=
"dimension"
/>
<attr
name=
"scrubber_enabled_size"
format=
"dimension"
/>
<attr
name=
"scrubber_disabled_size"
format=
"dimension"
/>
<attr
name=
"scrubber_dragged_size"
format=
"dimension"
/>
<attr
name=
"scrubber_drawable"
format=
"reference"
/>
<attr
name=
"played_color"
format=
"color"
/>
<attr
name=
"scrubber_color"
format=
"color"
/>
<attr
name=
"buffered_color"
format=
"color"
/>
<attr
name=
"unplayed_color"
format=
"color"
/>
<attr
name=
"ad_marker_color"
format=
"color"
/>
<attr
name=
"played_ad_marker_color"
format=
"color"
/>
<attr
name=
"bar_height"
/>
<attr
name=
"touch_target_height"
/>
<attr
name=
"ad_marker_width"
/>
<attr
name=
"scrubber_enabled_size"
/>
<attr
name=
"scrubber_disabled_size"
/>
<attr
name=
"scrubber_dragged_size"
/>
<attr
name=
"scrubber_drawable"
/>
<attr
name=
"played_color"
/>
<attr
name=
"scrubber_color"
/>
<attr
name=
"buffered_color"
/>
<attr
name=
"unplayed_color"
/>
<attr
name=
"ad_marker_color"
/>
<attr
name=
"played_ad_marker_color"
/>
</declare-styleable>
</resources>
library/ui/src/main/res/values/ids.xml
View file @
f6297f4f
...
...
@@ -33,6 +33,7 @@
<item
name=
"exo_repeat_toggle"
type=
"id"
/>
<item
name=
"exo_duration"
type=
"id"
/>
<item
name=
"exo_position"
type=
"id"
/>
<item
name=
"exo_progress_placeholder"
type=
"id"
/>
<item
name=
"exo_progress"
type=
"id"
/>
<item
name=
"exo_buffering"
type=
"id"
/>
<item
name=
"exo_error_message"
type=
"id"
/>
...
...
testutils/src/main/java/com/google/android/exoplayer2/testutil/DebugRenderersFactory.java
View file @
f6297f4f
...
...
@@ -30,7 +30,6 @@ import com.google.android.exoplayer2.drm.DrmSessionManager;
import
com.google.android.exoplayer2.drm.FrameworkMediaCrypto
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecInfo
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecSelector
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException
;
import
com.google.android.exoplayer2.video.MediaCodecVideoRenderer
;
import
com.google.android.exoplayer2.video.VideoRendererEventListener
;
import
java.nio.ByteBuffer
;
...
...
@@ -55,6 +54,7 @@ public class DebugRenderersFactory extends DefaultRenderersFactory {
MediaCodecSelector
mediaCodecSelector
,
@Nullable
DrmSessionManager
<
FrameworkMediaCrypto
>
drmSessionManager
,
boolean
playClearSamplesWithoutKeys
,
boolean
enableDecoderFallback
,
Handler
eventHandler
,
VideoRendererEventListener
eventListener
,
long
allowedVideoJoiningTimeMs
,
...
...
@@ -113,8 +113,7 @@ public class DebugRenderersFactory extends DefaultRenderersFactory {
MediaCodec
codec
,
Format
format
,
MediaCrypto
crypto
,
float
operatingRate
)
throws
DecoderQueryException
{
float
operatingRate
)
{
// If the codec is being initialized whilst the renderer is started, default behavior is to
// render the first frame (i.e. the keyframe before the current position), then drop frames up
// to the current playback position. For test runs that place a maximum limit on the number of
...
...
testutils/src/main/java/com/google/android/exoplayer2/testutil/HostActivity.java
View file @
f6297f4f
...
...
@@ -166,7 +166,8 @@ public final class HostActivity extends Activity implements SurfaceHolder.Callba
public
void
onCreate
(
Bundle
savedInstanceState
)
{
super
.
onCreate
(
savedInstanceState
);
requestWindowFeature
(
Window
.
FEATURE_NO_TITLE
);
setContentView
(
getResources
().
getIdentifier
(
"host_activity"
,
"layout"
,
getPackageName
()));
setContentView
(
getResources
().
getIdentifier
(
"exo_testutils_host_activity"
,
"layout"
,
getPackageName
()));
surfaceView
=
findViewById
(
getResources
().
getIdentifier
(
"surface_view"
,
"id"
,
getPackageName
()));
surfaceView
.
getHolder
().
addCallback
(
this
);
...
...
testutils/src/main/res/layout/host_activity.xml
→
testutils/src/main/res/layout/
exo_testutils_
host_activity.xml
View file @
f6297f4f
File moved
testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/CacheAsserts.java
View file @
f6297f4f
...
...
@@ -33,58 +33,89 @@ import java.util.ArrayList;
/** Assertion methods for {@link Cache}. */
public
final
class
CacheAsserts
{
/**
* Asserts that the cache content is equal to the data in the {@code fakeDataSet}.
*
* @throws IOException If an error occurred reading from the Cache.
*/
public
static
void
assertCachedData
(
Cache
cache
,
FakeDataSet
fakeDataSet
)
throws
IOException
{
ArrayList
<
FakeData
>
allData
=
fakeDataSet
.
getAllData
();
Uri
[]
uris
=
new
Uri
[
allData
.
size
()];
for
(
int
i
=
0
;
i
<
allData
.
size
();
i
++)
{
uris
[
i
]
=
allData
.
get
(
i
).
uri
;
/** Defines a set of data requests. */
public
static
final
class
RequestSet
{
private
final
FakeDataSet
fakeDataSet
;
private
DataSpec
[]
dataSpecs
;
public
RequestSet
(
FakeDataSet
fakeDataSet
)
{
this
.
fakeDataSet
=
fakeDataSet
;
ArrayList
<
FakeData
>
allData
=
fakeDataSet
.
getAllData
();
dataSpecs
=
new
DataSpec
[
allData
.
size
()];
for
(
int
i
=
0
;
i
<
dataSpecs
.
length
;
i
++)
{
dataSpecs
[
i
]
=
new
DataSpec
(
allData
.
get
(
i
).
uri
);
}
}
assertCachedData
(
cache
,
fakeDataSet
,
uris
);
}
/**
* Asserts that the cache content is equal to the given subset of data in the {@code fakeDataSet}.
*
* @throws IOException If an error occurred reading from the Cache.
*/
public
static
void
assertCachedData
(
Cache
cache
,
FakeDataSet
fakeDataSet
,
String
...
uriStrings
)
throws
IOException
{
Uri
[]
uris
=
new
Uri
[
uriStrings
.
length
];
for
(
int
i
=
0
;
i
<
uriStrings
.
length
;
i
++)
{
uris
[
i
]
=
Uri
.
parse
(
uriStrings
[
i
]);
public
RequestSet
subset
(
String
...
uriStrings
)
{
dataSpecs
=
new
DataSpec
[
uriStrings
.
length
];
for
(
int
i
=
0
;
i
<
dataSpecs
.
length
;
i
++)
{
dataSpecs
[
i
]
=
new
DataSpec
(
Uri
.
parse
(
uriStrings
[
i
]));
}
return
this
;
}
public
RequestSet
subset
(
Uri
...
uris
)
{
dataSpecs
=
new
DataSpec
[
uris
.
length
];
for
(
int
i
=
0
;
i
<
dataSpecs
.
length
;
i
++)
{
dataSpecs
[
i
]
=
new
DataSpec
(
uris
[
i
]);
}
return
this
;
}
public
RequestSet
subset
(
DataSpec
...
dataSpecs
)
{
this
.
dataSpecs
=
dataSpecs
;
return
this
;
}
public
int
getCount
()
{
return
dataSpecs
.
length
;
}
public
byte
[]
getData
(
int
i
)
{
return
fakeDataSet
.
getData
(
dataSpecs
[
i
].
uri
).
getData
();
}
public
DataSpec
getDataSpec
(
int
i
)
{
return
dataSpecs
[
i
];
}
public
RequestSet
useBoundedDataSpecFor
(
String
uriString
)
{
FakeData
data
=
fakeDataSet
.
getData
(
uriString
);
for
(
int
i
=
0
;
i
<
dataSpecs
.
length
;
i
++)
{
DataSpec
spec
=
dataSpecs
[
i
];
if
(
spec
.
uri
.
getPath
().
equals
(
uriString
))
{
dataSpecs
[
i
]
=
spec
.
subrange
(
0
,
data
.
getData
().
length
);
return
this
;
}
}
throw
new
IllegalStateException
();
}
assertCachedData
(
cache
,
fakeDataSet
,
uris
);
}
/**
* Asserts that the cache cont
ent is equal to the given subset of data in the {@code fakeData
Set}.
* Asserts that the cache cont
ains necessary data for the {@code request
Set}.
*
* @throws IOException If an error occurred reading from the Cache.
*/
public
static
void
assertCachedData
(
Cache
cache
,
FakeDataSet
fakeDataSet
,
Uri
...
uris
)
throws
IOException
{
public
static
void
assertCachedData
(
Cache
cache
,
RequestSet
requestSet
)
throws
IOException
{
int
totalLength
=
0
;
for
(
Uri
uri
:
uris
)
{
byte
[]
data
=
fakeDataSet
.
getData
(
uri
).
getData
(
);
assertDataCached
(
cache
,
uri
,
data
);
for
(
int
i
=
0
;
i
<
requestSet
.
getCount
();
i
++
)
{
byte
[]
data
=
requestSet
.
getData
(
i
);
assertDataCached
(
cache
,
requestSet
.
getDataSpec
(
i
)
,
data
);
totalLength
+=
data
.
length
;
}
assertThat
(
cache
.
getCacheSpace
()).
isEqualTo
(
totalLength
);
}
/**
* Asserts that the cache cont
ains the given data for {@code uriString
}.
* Asserts that the cache cont
ent is equal to the data in the {@code fakeDataSet
}.
*
* @throws IOException If an error occurred reading from the Cache.
*/
public
static
void
assertDataCached
(
Cache
cache
,
Uri
uri
,
byte
[]
expected
)
throws
IOException
{
DataSpec
dataSpec
=
new
DataSpec
(
uri
);
assertDataCached
(
cache
,
dataSpec
,
expected
);
public
static
void
assertCachedData
(
Cache
cache
,
FakeDataSet
fakeDataSet
)
throws
IOException
{
assertCachedData
(
cache
,
new
RequestSet
(
fakeDataSet
));
}
/**
...
...
@@ -95,15 +126,18 @@ public final class CacheAsserts {
public
static
void
assertDataCached
(
Cache
cache
,
DataSpec
dataSpec
,
byte
[]
expected
)
throws
IOException
{
DataSource
dataSource
=
new
CacheDataSource
(
cache
,
DummyDataSource
.
INSTANCE
,
0
);
dataSource
.
open
(
dataSpec
)
;
byte
[]
bytes
;
try
{
byte
[]
bytes
=
TestUtil
.
readToEnd
(
dataSource
);
assertWithMessage
(
"Cached data doesn't match expected for '"
+
dataSpec
.
uri
+
"',"
)
.
that
(
bytes
)
.
isEqualTo
(
expected
);
dataSource
.
open
(
dataSpec
);
bytes
=
TestUtil
.
readToEnd
(
dataSource
);
}
catch
(
IOException
e
)
{
throw
new
IOException
(
"Opening/reading cache failed: "
+
dataSpec
,
e
);
}
finally
{
dataSource
.
close
();
}
assertWithMessage
(
"Cached data doesn't match expected for '"
+
dataSpec
.
uri
+
"',"
)
.
that
(
bytes
)
.
isEqualTo
(
expected
);
}
/**
...
...
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