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
f7ed789f
authored
Jun 26, 2018
by
ojw28
Committed by
GitHub
Jun 26, 2018
Browse files
Options
_('Browse Files')
Download
Plain Diff
Merge pull request #4434 from google/dev-v2-r2.8.2
r2.8.2
parents
2b55c91a
d880fac5
Hide whitespace changes
Inline
Side-by-side
Showing
93 changed files
with
1628 additions
and
1014 deletions
RELEASENOTES.md
constants.gradle
demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java
demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java
extensions/cast/build.gradle
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
library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.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/analytics/AnalyticsCollector.java
library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleDecoder.java
library/core/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java
library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReader.java
library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/Sniffer.java
library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FixedSampleSizeRechunker.java
library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java
library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java
library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/PsshAtomUtil.java
library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/OpusReader.java
library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisUtil.java
library/core/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java
library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java
library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java
library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloader.java
library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java
library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java
library/core/src/main/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSource.java
library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java
library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java
library/core/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java
library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunk.java
library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunkOutput.java
library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ContainerMediaChunk.java
library/core/src/main/java/com/google/android/exoplayer2/source/chunk/DataChunk.java
library/core/src/main/java/com/google/android/exoplayer2/source/chunk/InitializationChunk.java
library/core/src/main/java/com/google/android/exoplayer2/source/chunk/MediaChunk.java
library/core/src/main/java/com/google/android/exoplayer2/source/chunk/SingleSampleMediaChunk.java
library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java
library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java
library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaDecoder.java
library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java
library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java
library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.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/Mp4WebvttDecoder.java
library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java
library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttParserUtil.java
library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java
library/core/src/main/java/com/google/android/exoplayer2/trackselection/BaseTrackSelection.java
library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java
library/core/src/main/java/com/google/android/exoplayer2/trackselection/FixedTrackSelection.java
library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java
library/core/src/main/java/com/google/android/exoplayer2/trackselection/RandomTrackSelection.java
library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java
library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionArray.java
library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelector.java
library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectorResult.java
library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSchemeDataSource.java
library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java
library/core/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.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/upstream/cache/ContentMetadataInternal.java
library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipher.java
library/core/src/main/java/com/google/android/exoplayer2/util/ColorParser.java
library/core/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java
library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java
library/core/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java
library/core/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java
library/core/src/main/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArray.java
library/core/src/main/java/com/google/android/exoplayer2/util/Util.java
library/core/src/main/java/com/google/android/exoplayer2/video/DummySurface.java
library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java
library/core/src/main/java/com/google/android/exoplayer2/video/VideoFrameReleaseTimeHelper.java
library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java
library/core/src/test/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelectionTest.java
library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java
library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java
library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java
library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloadHelper.java
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java
library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java
library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java
library/ui/src/main/java/com/google/android/exoplayer2/ui/TrackSelectionView.java
RELEASENOTES.md
View file @
f7ed789f
# Release notes #
### 2.8.2 ###
*
IMA: Don't advertise support for video/mpeg ad media, as we don't have an
extractor for this (
[
#4297
](
https://github.com/google/ExoPlayer/issues/4297
)
).
*
DASH: Fix playback getting stuck when playing representations that have both
sidx atoms and non-zero presentationTimeOffset values.
*
HLS:
*
Allow injection of custom playlist trackers.
*
Fix adaptation in live playlists with EXT-X-PROGRAM-DATE-TIME tags.
*
Mitigate memory leaks when
`MediaSource`
loads are slow to cancel
(
[
#4249
](
https://github.com/google/ExoPlayer/issues/4249
)
).
*
Fix inconsistent
`Player.EventListener`
invocations for recursive player state
changes (
[
#4276
](
https://github.com/google/ExoPlayer/issues/4276
)
).
*
Fix
`MediaCodec.native_setSurface`
crash on Moto C
(
[
#4315
](
https://github.com/google/ExoPlayer/issues/4315
)
).
*
Fix missing whitespace in CEA-608
(
[
#3906
](
https://github.com/google/ExoPlayer/issues/3906
)
).
*
Fix crash downloading HLS media playlists
(
[
#4396
](
https://github.com/google/ExoPlayer/issues/4396
)
).
*
Fix a bug where download cancellation was ignored
(
[
#4403
](
https://github.com/google/ExoPlayer/issues/4403
)
).
*
Set
`METADATA_KEY_TITLE`
on media descriptions
(
[
#4292
](
https://github.com/google/ExoPlayer/issues/4292
)
).
*
Allow apps to register custom MIME types
(
[
#4264
](
https://github.com/google/ExoPlayer/issues/4264
)
).
### 2.8.1 ###
*
HLS:
...
...
@@ -59,7 +85,7 @@
periods are created, released and being read from.
*
Support live stream clipping with
`ClippingMediaSource`
.
*
Allow setting tags for all media sources in their factories. The tag of the
current window can be retrieved with
`
Exo
Player.getCurrentTag`
.
current window can be retrieved with
`Player.getCurrentTag`
.
*
UI components:
*
Add support for displaying error messages and a buffering spinner in
`PlayerView`
.
...
...
constants.gradle
View file @
f7ed789f
...
...
@@ -13,8 +13,8 @@
// limitations under the License.
project
.
ext
{
// ExoPlayer version and version code.
releaseVersion
=
'2.8.
1
'
releaseVersionCode
=
280
1
releaseVersion
=
'2.8.
2
'
releaseVersionCode
=
280
2
// Important: ExoPlayer specifies a minSdkVersion of 14 because various
// components provided by the library may be of use on older devices.
// However, please note that the core media playback functionality provided
...
...
@@ -25,7 +25,7 @@ project.ext {
buildToolsVersion
=
'27.0.3'
testSupportLibraryVersion
=
'0.5'
supportLibraryVersion
=
'27.0.0'
playServicesLibraryVersion
=
'1
2.0.0
'
playServicesLibraryVersion
=
'1
5.0.1
'
dexmakerVersion
=
'1.2'
mockitoVersion
=
'1.9.5'
junitVersion
=
'4.12'
...
...
demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java
View file @
f7ed789f
...
...
@@ -136,6 +136,7 @@ public class PlayerActivity extends Activity
private
DataSource
.
Factory
mediaDataSourceFactory
;
private
SimpleExoPlayer
player
;
private
FrameworkMediaDrm
mediaDrm
;
private
MediaSource
mediaSource
;
private
DefaultTrackSelector
trackSelector
;
private
DefaultTrackSelector
.
Parameters
trackSelectorParameters
;
...
...
@@ -487,8 +488,9 @@ public class PlayerActivity extends Activity
keyRequestPropertiesArray
[
i
+
1
]);
}
}
return
new
DefaultDrmSessionManager
<>(
uuid
,
FrameworkMediaDrm
.
newInstance
(
uuid
),
drmCallback
,
null
,
multiSession
);
releaseMediaDrm
();
mediaDrm
=
FrameworkMediaDrm
.
newInstance
(
uuid
);
return
new
DefaultDrmSessionManager
<>(
uuid
,
mediaDrm
,
drmCallback
,
null
,
multiSession
);
}
private
void
releasePlayer
()
{
...
...
@@ -502,6 +504,23 @@ public class PlayerActivity extends Activity
mediaSource
=
null
;
trackSelector
=
null
;
}
releaseMediaDrm
();
}
private
void
releaseMediaDrm
()
{
if
(
mediaDrm
!=
null
)
{
mediaDrm
.
release
();
mediaDrm
=
null
;
}
}
private
void
releaseAdsLoader
()
{
if
(
adsLoader
!=
null
)
{
adsLoader
.
release
();
adsLoader
=
null
;
loadedAdTagUri
=
null
;
playerView
.
getOverlayFrameLayout
().
removeAllViews
();
}
}
private
void
updateTrackSelectorParameters
()
{
...
...
@@ -576,15 +595,6 @@ public class PlayerActivity extends Activity
}
}
private
void
releaseAdsLoader
()
{
if
(
adsLoader
!=
null
)
{
adsLoader
.
release
();
adsLoader
=
null
;
loadedAdTagUri
=
null
;
playerView
.
getOverlayFrameLayout
().
removeAllViews
();
}
}
// User controls
private
void
updateButtonVisibilities
()
{
...
...
demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java
View file @
f7ed789f
...
...
@@ -94,9 +94,15 @@ public class SampleChooserActivity extends Activity
SampleListLoader
loaderTask
=
new
SampleListLoader
();
loaderTask
.
execute
(
uris
);
// Ping the download service in case it's not running (but should be).
startService
(
new
Intent
(
this
,
DemoDownloadService
.
class
).
setAction
(
DownloadService
.
ACTION_INIT
));
// Start the download service if it should be running but it's not currently.
// Starting the service in the foreground causes notification flicker if there is no scheduled
// action. Starting it in the background throws an exception if the app is in the background too
// (e.g. if device screen is locked).
try
{
DownloadService
.
start
(
this
,
DemoDownloadService
.
class
);
}
catch
(
IllegalStateException
e
)
{
DownloadService
.
startForeground
(
this
,
DemoDownloadService
.
class
);
}
}
@Override
...
...
extensions/cast/build.gradle
View file @
f7ed789f
...
...
@@ -26,16 +26,6 @@ android {
}
dependencies
{
// These dependencies are necessary to force the supportLibraryVersion of
// com.android.support:support-v4, com.android.support:appcompat-v7 and
// com.android.support:mediarouter-v7 to be used. Else older versions are
// used, for example:
// com.google.android.gms:play-services-cast-framework:12.0.0
// |-- com.google.android.gms:play-services-basement:12.0.0
// |-- com.android.support:support-v4:26.1.0
api
'com.android.support:support-v4:'
+
supportLibraryVersion
api
'com.android.support:appcompat-v7:'
+
supportLibraryVersion
api
'com.android.support:mediarouter-v7:'
+
supportLibraryVersion
api
'com.google.android.gms:play-services-cast-framework:'
+
playServicesLibraryVersion
implementation
project
(
modulePrefix
+
'library-core'
)
implementation
project
(
modulePrefix
+
'library-ui'
)
...
...
@@ -44,6 +34,15 @@ dependencies {
testImplementation
'org.mockito:mockito-core:'
+
mockitoVersion
testImplementation
'org.robolectric:robolectric:'
+
robolectricVersion
testImplementation
project
(
modulePrefix
+
'testutils-robolectric'
)
// These dependencies are necessary to force the supportLibraryVersion of
// com.android.support:support-v4, com.android.support:appcompat-v7 and
// com.android.support:mediarouter-v7 to be used. Else older versions are
// used, for example via:
// com.google.android.gms:play-services-cast-framework:15.0.1
// |-- com.android.support:mediarouter-v7:26.1.0
api
'com.android.support:support-v4:'
+
supportLibraryVersion
api
'com.android.support:mediarouter-v7:'
+
supportLibraryVersion
api
'com.android.support:recyclerview-v7:'
+
supportLibraryVersion
}
ext
{
...
...
extensions/ima/build.gradle
View file @
f7ed789f
...
...
@@ -26,17 +26,16 @@ android {
}
dependencies
{
// This dependency is necessary to force the supportLibraryVersion of
// com.android.support:support-v4 to be used. Else an older version (25.2.0)
// is included via:
// com.google.android.gms:play-services-ads:12.0.0
// |-- com.google.android.gms:play-services-ads-lite:12.0.0
// |-- com.google.android.gms:play-services-basement:12.0.0
// |-- com.android.support:support-v4:26.1.0
api
'com.android.support:support-v4:'
+
supportLibraryVersion
api
'com.google.ads.interactivemedia.v3:interactivemedia:3.8.5'
api
'com.google.ads.interactivemedia.v3:interactivemedia:3.8.7'
implementation
project
(
modulePrefix
+
'library-core'
)
implementation
'com.google.android.gms:play-services-ads:'
+
playServicesLibraryVersion
// These dependencies are necessary to force the supportLibraryVersion of
// com.android.support:support-v4 and com.android.support:customtabs to be
// used. Else older versions are used, for example via:
// com.google.android.gms:play-services-ads:15.0.1
// |-- com.android.support:customtabs:26.1.0
implementation
'com.android.support:support-v4:'
+
supportLibraryVersion
implementation
'com.android.support:customtabs:'
+
supportLibraryVersion
}
ext
{
...
...
extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java
View file @
f7ed789f
...
...
@@ -447,9 +447,13 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
}
else
if
(
contentType
==
C
.
TYPE_HLS
)
{
supportedMimeTypes
.
add
(
MimeTypes
.
APPLICATION_M3U8
);
}
else
if
(
contentType
==
C
.
TYPE_OTHER
)
{
supportedMimeTypes
.
addAll
(
Arrays
.
asList
(
MimeTypes
.
VIDEO_MP4
,
MimeTypes
.
VIDEO_WEBM
,
MimeTypes
.
VIDEO_H263
,
MimeTypes
.
VIDEO_MPEG
,
MimeTypes
.
AUDIO_MP4
,
MimeTypes
.
AUDIO_MPEG
));
supportedMimeTypes
.
addAll
(
Arrays
.
asList
(
MimeTypes
.
VIDEO_MP4
,
MimeTypes
.
VIDEO_WEBM
,
MimeTypes
.
VIDEO_H263
,
MimeTypes
.
AUDIO_MP4
,
MimeTypes
.
AUDIO_MPEG
));
}
else
if
(
contentType
==
C
.
TYPE_SS
)
{
// IMA does not support Smooth Streaming ad media.
}
...
...
extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java
View file @
f7ed789f
...
...
@@ -600,8 +600,9 @@ public final class MediaSessionConnector {
}
}
if
(
description
.
getTitle
()
!=
null
)
{
builder
.
putString
(
MediaMetadataCompat
.
METADATA_KEY_DISPLAY_TITLE
,
String
.
valueOf
(
description
.
getTitle
()));
String
title
=
String
.
valueOf
(
description
.
getTitle
());
builder
.
putString
(
MediaMetadataCompat
.
METADATA_KEY_TITLE
,
title
);
builder
.
putString
(
MediaMetadataCompat
.
METADATA_KEY_DISPLAY_TITLE
,
title
);
}
if
(
description
.
getSubtitle
()
!=
null
)
{
builder
.
putString
(
MediaMetadataCompat
.
METADATA_KEY_DISPLAY_SUBTITLE
,
...
...
library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java
View file @
f7ed789f
...
...
@@ -89,12 +89,12 @@ import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
* model">
*
* <ul>
* <li>
It is strongly recommended that ExoPlayer instances are created and accessed from a singl
e
*
application thread. The application's main thread is ideal. Accessing an instance from
* m
ultiple threads is discouraged as it may cause synchronization problems
.
* <li>Registered listeners are called on the thread th
at created the ExoPlayer instance, unless
*
the thread that created the ExoPlayer instance does not have a {@link Looper}. In that
*
case, registered listeners will be called on the application's main thread
.
* <li>
ExoPlayer instances must be accessed from a single application thread. This must be th
e
*
thread the player is created on if that thread has a {@link Looper}, or the application's
* m
ain thread otherwise
.
* <li>Registered listeners are called on the thread th
e player is created on if that thread has a
*
{@link Looper}, or the application's main thread otherwise. Note that this means registered
*
listeners are called on the same thread which must be used to access the player
.
* <li>An internal playback thread is responsible for playback. Injected player components such as
* Renderers, MediaSources, TrackSelectors and LoadControls are called by the player on this
* thread.
...
...
library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java
View file @
f7ed789f
...
...
@@ -33,8 +33,10 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.Clock
;
import
com.google.android.exoplayer2.util.Util
;
import
java.util.ArrayDeque
;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.Set
;
import
java.util.concurrent.CopyOnWriteArraySet
;
/**
...
...
@@ -53,6 +55,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
private
final
CopyOnWriteArraySet
<
Player
.
EventListener
>
listeners
;
private
final
Timeline
.
Window
window
;
private
final
Timeline
.
Period
period
;
private
final
ArrayDeque
<
PlaybackInfoUpdate
>
pendingPlaybackInfoUpdates
;
private
boolean
playWhenReady
;
private
@RepeatMode
int
repeatMode
;
...
...
@@ -112,6 +115,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
/* startPositionUs= */
0
,
TrackGroupArray
.
EMPTY
,
emptyTrackSelectorResult
);
pendingPlaybackInfoUpdates
=
new
ArrayDeque
<>();
internalPlayer
=
new
ExoPlayerImplInternal
(
renderers
,
...
...
@@ -185,7 +189,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
/* positionDiscontinuity= */
false
,
/* ignored */
DISCONTINUITY_REASON_INTERNAL
,
TIMELINE_CHANGE_REASON_RESET
,
/* seekProcessed= */
false
);
/* seekProcessed= */
false
,
/* playWhenReadyChanged= */
false
);
}
@Override
...
...
@@ -193,10 +198,13 @@ import java.util.concurrent.CopyOnWriteArraySet;
if
(
this
.
playWhenReady
!=
playWhenReady
)
{
this
.
playWhenReady
=
playWhenReady
;
internalPlayer
.
setPlayWhenReady
(
playWhenReady
);
PlaybackInfo
playbackInfo
=
this
.
playbackInfo
;
for
(
Player
.
EventListener
listener
:
listeners
)
{
listener
.
onPlayerStateChanged
(
playWhenReady
,
playbackInfo
.
playbackState
);
}
updatePlaybackInfo
(
playbackInfo
,
/* positionDiscontinuity= */
false
,
/* ignored */
DISCONTINUITY_REASON_INTERNAL
,
/* ignored */
TIMELINE_CHANGE_REASON_RESET
,
/* seekProcessed= */
false
,
/* playWhenReadyChanged= */
true
);
}
}
...
...
@@ -352,7 +360,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
/* positionDiscontinuity= */
false
,
/* ignored */
DISCONTINUITY_REASON_INTERNAL
,
TIMELINE_CHANGE_REASON_RESET
,
/* seekProcessed= */
false
);
/* seekProcessed= */
false
,
/* playWhenReadyChanged= */
false
);
}
@Override
...
...
@@ -615,7 +624,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
positionDiscontinuity
,
positionDiscontinuityReason
,
timelineChangeReason
,
seekProcessed
);
seekProcessed
,
/* playWhenReadyChanged= */
false
);
}
}
...
...
@@ -643,51 +653,33 @@ import java.util.concurrent.CopyOnWriteArraySet;
}
private
void
updatePlaybackInfo
(
PlaybackInfo
newP
laybackInfo
,
PlaybackInfo
p
laybackInfo
,
boolean
positionDiscontinuity
,
@Player
.
DiscontinuityReason
int
positionDiscontinuityReason
,
@Player
.
TimelineChangeReason
int
timelineChangeReason
,
boolean
seekProcessed
)
{
boolean
timelineOrManifestChanged
=
playbackInfo
.
timeline
!=
newPlaybackInfo
.
timeline
||
playbackInfo
.
manifest
!=
newPlaybackInfo
.
manifest
;
boolean
playbackStateChanged
=
playbackInfo
.
playbackState
!=
newPlaybackInfo
.
playbackState
;
boolean
isLoadingChanged
=
playbackInfo
.
isLoading
!=
newPlaybackInfo
.
isLoading
;
boolean
trackSelectorResultChanged
=
playbackInfo
.
trackSelectorResult
!=
newPlaybackInfo
.
trackSelectorResult
;
playbackInfo
=
newPlaybackInfo
;
if
(
timelineOrManifestChanged
||
timelineChangeReason
==
TIMELINE_CHANGE_REASON_PREPARED
)
{
for
(
Player
.
EventListener
listener
:
listeners
)
{
listener
.
onTimelineChanged
(
playbackInfo
.
timeline
,
playbackInfo
.
manifest
,
timelineChangeReason
);
}
}
if
(
positionDiscontinuity
)
{
for
(
Player
.
EventListener
listener
:
listeners
)
{
listener
.
onPositionDiscontinuity
(
positionDiscontinuityReason
);
}
}
if
(
trackSelectorResultChanged
)
{
trackSelector
.
onSelectionActivated
(
playbackInfo
.
trackSelectorResult
.
info
);
for
(
Player
.
EventListener
listener
:
listeners
)
{
listener
.
onTracksChanged
(
playbackInfo
.
trackGroups
,
playbackInfo
.
trackSelectorResult
.
selections
);
}
}
if
(
isLoadingChanged
)
{
for
(
Player
.
EventListener
listener
:
listeners
)
{
listener
.
onLoadingChanged
(
playbackInfo
.
isLoading
);
}
}
if
(
playbackStateChanged
)
{
for
(
Player
.
EventListener
listener
:
listeners
)
{
listener
.
onPlayerStateChanged
(
playWhenReady
,
playbackInfo
.
playbackState
);
}
boolean
seekProcessed
,
boolean
playWhenReadyChanged
)
{
boolean
isRunningRecursiveListenerNotification
=
!
pendingPlaybackInfoUpdates
.
isEmpty
();
pendingPlaybackInfoUpdates
.
addLast
(
new
PlaybackInfoUpdate
(
playbackInfo
,
/* previousPlaybackInfo= */
this
.
playbackInfo
,
listeners
,
trackSelector
,
positionDiscontinuity
,
positionDiscontinuityReason
,
timelineChangeReason
,
seekProcessed
,
playWhenReady
,
playWhenReadyChanged
));
// Assign playback info immediately such that all getters return the right values.
this
.
playbackInfo
=
playbackInfo
;
if
(
isRunningRecursiveListenerNotification
)
{
return
;
}
if
(
seekProcessed
)
{
for
(
Player
.
EventListener
listener
:
listeners
)
{
listener
.
onSeekProcessed
();
}
while
(!
pendingPlaybackInfoUpdates
.
isEmpty
())
{
pendingPlaybackInfoUpdates
.
peekFirst
().
notifyListeners
();
pendingPlaybackInfoUpdates
.
removeFirst
();
}
}
...
...
@@ -703,4 +695,85 @@ import java.util.concurrent.CopyOnWriteArraySet;
private
boolean
shouldMaskPosition
()
{
return
playbackInfo
.
timeline
.
isEmpty
()
||
pendingOperationAcks
>
0
;
}
private
static
final
class
PlaybackInfoUpdate
{
private
final
PlaybackInfo
playbackInfo
;
private
final
Set
<
Player
.
EventListener
>
listeners
;
private
final
TrackSelector
trackSelector
;
private
final
boolean
positionDiscontinuity
;
private
final
@Player
.
DiscontinuityReason
int
positionDiscontinuityReason
;
private
final
@Player
.
TimelineChangeReason
int
timelineChangeReason
;
private
final
boolean
seekProcessed
;
private
final
boolean
playWhenReady
;
private
final
boolean
playbackStateOrPlayWhenReadyChanged
;
private
final
boolean
timelineOrManifestChanged
;
private
final
boolean
isLoadingChanged
;
private
final
boolean
trackSelectorResultChanged
;
public
PlaybackInfoUpdate
(
PlaybackInfo
playbackInfo
,
PlaybackInfo
previousPlaybackInfo
,
Set
<
Player
.
EventListener
>
listeners
,
TrackSelector
trackSelector
,
boolean
positionDiscontinuity
,
@Player
.
DiscontinuityReason
int
positionDiscontinuityReason
,
@Player
.
TimelineChangeReason
int
timelineChangeReason
,
boolean
seekProcessed
,
boolean
playWhenReady
,
boolean
playWhenReadyChanged
)
{
this
.
playbackInfo
=
playbackInfo
;
this
.
listeners
=
listeners
;
this
.
trackSelector
=
trackSelector
;
this
.
positionDiscontinuity
=
positionDiscontinuity
;
this
.
positionDiscontinuityReason
=
positionDiscontinuityReason
;
this
.
timelineChangeReason
=
timelineChangeReason
;
this
.
seekProcessed
=
seekProcessed
;
this
.
playWhenReady
=
playWhenReady
;
playbackStateOrPlayWhenReadyChanged
=
playWhenReadyChanged
||
previousPlaybackInfo
.
playbackState
!=
playbackInfo
.
playbackState
;
timelineOrManifestChanged
=
previousPlaybackInfo
.
timeline
!=
playbackInfo
.
timeline
||
previousPlaybackInfo
.
manifest
!=
playbackInfo
.
manifest
;
isLoadingChanged
=
previousPlaybackInfo
.
isLoading
!=
playbackInfo
.
isLoading
;
trackSelectorResultChanged
=
previousPlaybackInfo
.
trackSelectorResult
!=
playbackInfo
.
trackSelectorResult
;
}
public
void
notifyListeners
()
{
if
(
timelineOrManifestChanged
||
timelineChangeReason
==
TIMELINE_CHANGE_REASON_PREPARED
)
{
for
(
Player
.
EventListener
listener
:
listeners
)
{
listener
.
onTimelineChanged
(
playbackInfo
.
timeline
,
playbackInfo
.
manifest
,
timelineChangeReason
);
}
}
if
(
positionDiscontinuity
)
{
for
(
Player
.
EventListener
listener
:
listeners
)
{
listener
.
onPositionDiscontinuity
(
positionDiscontinuityReason
);
}
}
if
(
trackSelectorResultChanged
)
{
trackSelector
.
onSelectionActivated
(
playbackInfo
.
trackSelectorResult
.
info
);
for
(
Player
.
EventListener
listener
:
listeners
)
{
listener
.
onTracksChanged
(
playbackInfo
.
trackGroups
,
playbackInfo
.
trackSelectorResult
.
selections
);
}
}
if
(
isLoadingChanged
)
{
for
(
Player
.
EventListener
listener
:
listeners
)
{
listener
.
onLoadingChanged
(
playbackInfo
.
isLoading
);
}
}
if
(
playbackStateOrPlayWhenReadyChanged
)
{
for
(
Player
.
EventListener
listener
:
listeners
)
{
listener
.
onPlayerStateChanged
(
playWhenReady
,
playbackInfo
.
playbackState
);
}
}
if
(
seekProcessed
)
{
for
(
Player
.
EventListener
listener
:
listeners
)
{
listener
.
onSeekProcessed
();
}
}
}
}
}
library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java
View file @
f7ed789f
...
...
@@ -1543,6 +1543,7 @@ import java.util.Collections;
}
}
@SuppressWarnings
(
"ParameterNotNullable"
)
private
void
updatePlayingPeriodRenderers
(
@Nullable
MediaPeriodHolder
oldPlayingPeriodHolder
)
throws
ExoPlaybackException
{
MediaPeriodHolder
newPlayingPeriodHolder
=
queue
.
getPlayingPeriod
();
...
...
library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java
View file @
f7ed789f
...
...
@@ -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.8.
1
"
;
public
static
final
String
VERSION
=
"2.8.
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.8.
1
"
;
public
static
final
String
VERSION_SLASHY
=
"ExoPlayerLib/2.8.
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
=
200800
1
;
public
static
final
int
VERSION_INT
=
200800
2
;
/**
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}
...
...
library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java
View file @
f7ed789f
...
...
@@ -46,6 +46,7 @@ import java.util.Collections;
import
java.util.List
;
import
java.util.Set
;
import
java.util.concurrent.CopyOnWriteArraySet
;
import
org.checkerframework.checker.nullness.qual.MonotonicNonNull
;
/**
* Data collector which is able to forward analytics events to {@link AnalyticsListener}s by
...
...
@@ -66,29 +67,34 @@ public class AnalyticsCollector
/**
* Creates an analytics collector for the specified player.
*
* @param player The {@link Player} for which data will be collected.
* @param player The {@link Player} for which data will be collected. Can be null, if the player
* is set by calling {@link AnalyticsCollector#setPlayer(Player)} before using the analytics
* collector.
* @param clock A {@link Clock} used to generate timestamps.
* @return An analytics collector.
*/
public
AnalyticsCollector
createAnalyticsCollector
(
Player
player
,
Clock
clock
)
{
public
AnalyticsCollector
createAnalyticsCollector
(
@Nullable
Player
player
,
Clock
clock
)
{
return
new
AnalyticsCollector
(
player
,
clock
);
}
}
private
final
CopyOnWriteArraySet
<
AnalyticsListener
>
listeners
;
private
final
Player
player
;
private
final
Clock
clock
;
private
final
Window
window
;
private
final
MediaPeriodQueueTracker
mediaPeriodQueueTracker
;
private
@MonotonicNonNull
Player
player
;
/**
* Creates an analytics collector for the specified player.
*
* @param player The {@link Player} for which data will be collected.
* @param player The {@link Player} for which data will be collected. Can be null, if the player
* is set by calling {@link AnalyticsCollector#setPlayer(Player)} before using the analytics
* collector.
* @param clock A {@link Clock} used to generate timestamps.
*/
protected
AnalyticsCollector
(
Player
player
,
Clock
clock
)
{
this
.
player
=
Assertions
.
checkNotNull
(
player
)
;
protected
AnalyticsCollector
(
@Nullable
Player
player
,
Clock
clock
)
{
this
.
player
=
player
;
this
.
clock
=
Assertions
.
checkNotNull
(
clock
);
listeners
=
new
CopyOnWriteArraySet
<>();
mediaPeriodQueueTracker
=
new
MediaPeriodQueueTracker
();
...
...
@@ -113,6 +119,17 @@ public class AnalyticsCollector
listeners
.
remove
(
listener
);
}
/**
* Sets the player for which data will be collected. Must only be called if no player has been set
* yet.
*
* @param player The {@link Player} for which data will be collected.
*/
public
void
setPlayer
(
Player
player
)
{
Assertions
.
checkState
(
this
.
player
==
null
);
this
.
player
=
Assertions
.
checkNotNull
(
player
);
}
// External events.
/**
...
...
@@ -541,6 +558,7 @@ public class AnalyticsCollector
/** Returns a new {@link EventTime} for the specified window index and media period id. */
protected
EventTime
generateEventTime
(
int
windowIndex
,
@Nullable
MediaPeriodId
mediaPeriodId
)
{
Assertions
.
checkNotNull
(
player
);
long
realtimeMs
=
clock
.
elapsedRealtime
();
Timeline
timeline
=
player
.
getCurrentTimeline
();
long
eventPositionMs
;
...
...
@@ -579,7 +597,7 @@ public class AnalyticsCollector
private
EventTime
generateEventTime
(
@Nullable
WindowAndMediaPeriodId
mediaPeriod
)
{
if
(
mediaPeriod
==
null
)
{
int
windowIndex
=
player
.
getCurrentWindowIndex
();
int
windowIndex
=
Assertions
.
checkNotNull
(
player
)
.
getCurrentWindowIndex
();
MediaPeriodId
mediaPeriodId
=
mediaPeriodQueueTracker
.
tryResolveWindowIndex
(
windowIndex
);
return
generateEventTime
(
windowIndex
,
mediaPeriodId
);
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleDecoder.java
View file @
f7ed789f
...
...
@@ -17,7 +17,7 @@ package com.google.android.exoplayer2.decoder;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.util.Assertions
;
import
java.util.
LinkedList
;
import
java.util.
ArrayDeque
;
/**
* Base class for {@link Decoder}s that use their own decode thread.
...
...
@@ -28,8 +28,8 @@ public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends Outp
private
final
Thread
decodeThread
;
private
final
Object
lock
;
private
final
LinkedList
<
I
>
queuedInputBuffers
;
private
final
LinkedList
<
O
>
queuedOutputBuffers
;
private
final
ArrayDeque
<
I
>
queuedInputBuffers
;
private
final
ArrayDeque
<
O
>
queuedOutputBuffers
;
private
final
I
[]
availableInputBuffers
;
private
final
O
[]
availableOutputBuffers
;
...
...
@@ -48,8 +48,8 @@ public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends Outp
*/
protected
SimpleDecoder
(
I
[]
inputBuffers
,
O
[]
outputBuffers
)
{
lock
=
new
Object
();
queuedInputBuffers
=
new
LinkedList
<>();
queuedOutputBuffers
=
new
LinkedList
<>();
queuedInputBuffers
=
new
ArrayDeque
<>();
queuedOutputBuffers
=
new
ArrayDeque
<>();
availableInputBuffers
=
inputBuffers
;
availableInputBufferCount
=
inputBuffers
.
length
;
for
(
int
i
=
0
;
i
<
availableInputBufferCount
;
i
++)
{
...
...
library/core/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java
View file @
f7ed789f
...
...
@@ -108,7 +108,8 @@ public final class HttpMediaDrmCallback implements MediaDrmCallback {
@Override
public
byte
[]
executeProvisionRequest
(
UUID
uuid
,
ProvisionRequest
request
)
throws
IOException
{
String
url
=
request
.
getDefaultUrl
()
+
"&signedRequest="
+
new
String
(
request
.
getData
());
String
url
=
request
.
getDefaultUrl
()
+
"&signedRequest="
+
Util
.
fromUtf8Bytes
(
request
.
getData
());
return
executePost
(
dataSourceFactory
,
url
,
new
byte
[
0
],
null
);
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReader.java
View file @
f7ed789f
...
...
@@ -24,7 +24,7 @@ import java.io.EOFException;
import
java.io.IOException
;
import
java.lang.annotation.Retention
;
import
java.lang.annotation.RetentionPolicy
;
import
java.util.
Stack
;
import
java.util.
ArrayDeque
;
/**
* Default implementation of {@link EbmlReader}.
...
...
@@ -46,15 +46,21 @@ import java.util.Stack;
private
static
final
int
VALID_FLOAT32_ELEMENT_SIZE_BYTES
=
4
;
private
static
final
int
VALID_FLOAT64_ELEMENT_SIZE_BYTES
=
8
;
private
final
byte
[]
scratch
=
new
byte
[
8
]
;
private
final
Stack
<
MasterElement
>
masterElementsStack
=
new
Stack
<>()
;
private
final
VarintReader
varintReader
=
new
VarintReader
()
;
private
final
byte
[]
scratch
;
private
final
ArrayDeque
<
MasterElement
>
masterElementsStack
;
private
final
VarintReader
varintReader
;
private
EbmlReaderOutput
output
;
private
@ElementState
int
elementState
;
private
int
elementId
;
private
long
elementContentSize
;
public
DefaultEbmlReader
()
{
scratch
=
new
byte
[
8
];
masterElementsStack
=
new
ArrayDeque
<>();
varintReader
=
new
VarintReader
();
}
@Override
public
void
init
(
EbmlReaderOutput
eventHandler
)
{
this
.
output
=
eventHandler
;
...
...
@@ -100,7 +106,7 @@ import java.util.Stack;
case
EbmlReaderOutput
.
TYPE_MASTER
:
long
elementContentPosition
=
input
.
getPosition
();
long
elementEndPosition
=
elementContentPosition
+
elementContentSize
;
masterElementsStack
.
add
(
new
MasterElement
(
elementId
,
elementEndPosition
));
masterElementsStack
.
push
(
new
MasterElement
(
elementId
,
elementEndPosition
));
output
.
startMasterElement
(
elementId
,
elementContentPosition
,
elementContentSize
);
elementState
=
ELEMENT_STATE_READ_ID
;
return
true
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/Sniffer.java
View file @
f7ed789f
...
...
@@ -78,8 +78,9 @@ import java.io.IOException;
return
false
;
}
if
(
size
!=
0
)
{
input
.
advancePeekPosition
((
int
)
size
);
peekLength
+=
size
;
int
sizeInt
=
(
int
)
size
;
input
.
advancePeekPosition
(
sizeInt
);
peekLength
+=
sizeInt
;
}
}
return
peekLength
==
headerStart
+
headerSize
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FixedSampleSizeRechunker.java
View file @
f7ed789f
...
...
@@ -108,4 +108,7 @@ import com.google.android.exoplayer2.util.Util;
return
new
Results
(
offsets
,
sizes
,
maximumSize
,
timestamps
,
flags
,
duration
);
}
private
FixedSampleSizeRechunker
()
{
// Prevent instantiation.
}
}
library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java
View file @
f7ed789f
...
...
@@ -50,7 +50,6 @@ import java.util.ArrayList;
import
java.util.Arrays
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.Stack
;
import
java.util.UUID
;
/**
...
...
@@ -141,7 +140,7 @@ public final class FragmentedMp4Extractor implements Extractor {
// Parser state.
private
final
ParsableByteArray
atomHeader
;
private
final
byte
[]
extendedTypeScratch
;
private
final
Stack
<
ContainerAtom
>
containerAtoms
;
private
final
ArrayDeque
<
ContainerAtom
>
containerAtoms
;
private
final
ArrayDeque
<
MetadataSampleInfo
>
pendingMetadataSampleInfos
;
private
final
@Nullable
TrackOutput
additionalEmsgTrackOutput
;
...
...
@@ -257,7 +256,7 @@ public final class FragmentedMp4Extractor implements Extractor {
nalPrefix
=
new
ParsableByteArray
(
5
);
nalBuffer
=
new
ParsableByteArray
();
extendedTypeScratch
=
new
byte
[
16
];
containerAtoms
=
new
Stack
<>();
containerAtoms
=
new
ArrayDeque
<>();
pendingMetadataSampleInfos
=
new
ArrayDeque
<>();
trackBundles
=
new
SparseArray
<>();
durationUs
=
C
.
TIME_UNSET
;
...
...
@@ -390,7 +389,7 @@ public final class FragmentedMp4Extractor implements Extractor {
if
(
shouldParseContainerAtom
(
atomType
))
{
long
endPosition
=
input
.
getPosition
()
+
atomSize
-
Atom
.
HEADER_SIZE
;
containerAtoms
.
add
(
new
ContainerAtom
(
atomType
,
endPosition
));
containerAtoms
.
push
(
new
ContainerAtom
(
atomType
,
endPosition
));
if
(
atomSize
==
atomHeaderBytesRead
)
{
processAtomEnded
(
endPosition
);
}
else
{
...
...
library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java
View file @
f7ed789f
...
...
@@ -37,9 +37,9 @@ import com.google.android.exoplayer2.util.Util;
import
java.io.IOException
;
import
java.lang.annotation.Retention
;
import
java.lang.annotation.RetentionPolicy
;
import
java.util.ArrayDeque
;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.Stack
;
/**
* Extracts data from the MP4 container format.
...
...
@@ -101,7 +101,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
private
final
ParsableByteArray
nalLength
;
private
final
ParsableByteArray
atomHeader
;
private
final
Stack
<
ContainerAtom
>
containerAtoms
;
private
final
ArrayDeque
<
ContainerAtom
>
containerAtoms
;
@State
private
int
parserState
;
private
int
atomType
;
...
...
@@ -137,7 +137,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
public
Mp4Extractor
(
@Flags
int
flags
)
{
this
.
flags
=
flags
;
atomHeader
=
new
ParsableByteArray
(
Atom
.
LONG_HEADER_SIZE
);
containerAtoms
=
new
Stack
<>();
containerAtoms
=
new
ArrayDeque
<>();
nalStartCode
=
new
ParsableByteArray
(
NalUnitUtil
.
NAL_START_CODE
);
nalLength
=
new
ParsableByteArray
(
4
);
sampleTrackIndex
=
C
.
INDEX_UNSET
;
...
...
@@ -303,7 +303,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
if
(
shouldParseContainerAtom
(
atomType
))
{
long
endPosition
=
input
.
getPosition
()
+
atomSize
-
atomHeaderBytesRead
;
containerAtoms
.
add
(
new
ContainerAtom
(
atomType
,
endPosition
));
containerAtoms
.
push
(
new
ContainerAtom
(
atomType
,
endPosition
));
if
(
atomSize
==
atomHeaderBytesRead
)
{
processAtomEnded
(
endPosition
);
}
else
{
...
...
library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/PsshAtomUtil.java
View file @
f7ed789f
...
...
@@ -49,6 +49,7 @@ public final class PsshAtomUtil {
* @param data The scheme specific data.
* @return The PSSH atom.
*/
@SuppressWarnings
(
"ParameterNotNullable"
)
public
static
byte
[]
buildPsshAtom
(
UUID
systemId
,
@Nullable
UUID
[]
keyIds
,
@Nullable
byte
[]
data
)
{
boolean
buildV1Atom
=
keyIds
!=
null
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/OpusReader.java
View file @
f7ed789f
...
...
@@ -130,6 +130,6 @@ import java.util.List;
}
else
{
length
=
10000
<<
length
;
}
return
frames
*
length
;
return
(
long
)
frames
*
length
;
}
}
library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisUtil.java
View file @
f7ed789f
...
...
@@ -357,12 +357,12 @@ import java.util.Arrays;
for
(
int
i
=
0
;
i
<
lengthMap
.
length
;
i
++)
{
if
(
isSparse
)
{
if
(
bitArray
.
readBit
())
{
lengthMap
[
i
]
=
bitArray
.
readBits
(
5
)
+
1
;
lengthMap
[
i
]
=
(
long
)
(
bitArray
.
readBits
(
5
)
+
1
)
;
}
else
{
// entry unused
lengthMap
[
i
]
=
0
;
}
}
else
{
// not sparse
lengthMap
[
i
]
=
bitArray
.
readBits
(
5
)
+
1
;
lengthMap
[
i
]
=
(
long
)
(
bitArray
.
readBits
(
5
)
+
1
)
;
}
}
}
else
{
...
...
@@ -392,7 +392,7 @@ import java.util.Arrays;
lookupValuesCount
=
0
;
}
}
else
{
lookupValuesCount
=
entries
*
dimensions
;
lookupValuesCount
=
(
long
)
entries
*
dimensions
;
}
// discard (no decoding required yet)
bitArray
.
skipBits
((
int
)
(
lookupValuesCount
*
valueBits
));
...
...
@@ -407,6 +407,10 @@ import java.util.Arrays;
return
(
long
)
Math
.
floor
(
Math
.
pow
(
entries
,
1
.
d
/
dimension
));
}
private
VorbisUtil
()
{
// Prevent instantiation.
}
public
static
final
class
CodeBook
{
public
final
int
dimensions
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java
View file @
f7ed789f
...
...
@@ -25,7 +25,7 @@ import com.google.android.exoplayer2.util.Util;
import
java.io.IOException
;
/** Reads a {@code WavHeader} from an input stream; supports resuming from input failures. */
/*
package
*/
final
class
WavHeaderReader
{
/*
package
*/
final
class
WavHeaderReader
{
private
static
final
String
TAG
=
"WavHeaderReader"
;
...
...
@@ -158,6 +158,10 @@ import java.io.IOException;
wavHeader
.
setDataBounds
(
input
.
getPosition
(),
chunkHeader
.
size
);
}
private
WavHeaderReader
()
{
// Prevent instantiation.
}
/** Container for a WAV chunk header. */
private
static
final
class
ChunkHeader
{
...
...
library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java
View file @
f7ed789f
...
...
@@ -262,12 +262,23 @@ public final class DownloadManager {
return
task
.
id
;
}
/** Returns the
current
number of tasks. */
/** Returns the number of tasks. */
public
int
getTaskCount
()
{
Assertions
.
checkState
(!
released
);
return
tasks
.
size
();
}
/** Returns the number of download tasks. */
public
int
getDownloadCount
()
{
int
count
=
0
;
for
(
int
i
=
0
;
i
<
tasks
.
size
();
i
++)
{
if
(!
tasks
.
get
(
i
).
action
.
isRemoveAction
)
{
count
++;
}
}
return
count
;
}
/** Returns the state of a task, or null if no such task exists */
public
@Nullable
TaskState
getTaskState
(
int
taskId
)
{
Assertions
.
checkState
(!
released
);
...
...
library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java
View file @
f7ed789f
...
...
@@ -160,9 +160,9 @@ public abstract class DownloadService extends Service {
* Starts the service, adding an action to be executed.
*
* @param context A {@link Context}.
* @param clazz The concrete download service
being targeted by the intent
.
* @param clazz The concrete download service
to be started
.
* @param downloadAction The action to be executed.
* @param foreground Whether th
is intent will be used to start the service
in the foreground.
* @param foreground Whether th
e service is started
in the foreground.
*/
public
static
void
startWithAction
(
Context
context
,
...
...
@@ -177,6 +177,33 @@ public abstract class DownloadService extends Service {
}
}
/**
* Starts the service without adding a new action. If there are any not finished actions and the
* requirements are met, the service resumes executing actions. Otherwise it stops immediately.
*
* @param context A {@link Context}.
* @param clazz The concrete download service to be started.
* @see #startForeground(Context, Class)
*/
public
static
void
start
(
Context
context
,
Class
<?
extends
DownloadService
>
clazz
)
{
context
.
startService
(
new
Intent
(
context
,
clazz
).
setAction
(
ACTION_INIT
));
}
/**
* Starts the service in the foreground without adding a new action. If there are any not finished
* actions and the requirements are met, the service resumes executing actions. Otherwise it stops
* immediately.
*
* @param context A {@link Context}.
* @param clazz The concrete download service to be started.
* @see #start(Context, Class)
*/
public
static
void
startForeground
(
Context
context
,
Class
<?
extends
DownloadService
>
clazz
)
{
Intent
intent
=
new
Intent
(
context
,
clazz
).
setAction
(
ACTION_INIT
).
putExtra
(
KEY_FOREGROUND
,
true
);
Util
.
startForegroundService
(
context
,
intent
);
}
@Override
public
void
onCreate
()
{
logd
(
"onCreate"
);
...
...
@@ -187,17 +214,6 @@ public abstract class DownloadService extends Service {
downloadManager
=
getDownloadManager
();
downloadManagerListener
=
new
DownloadManagerListener
();
downloadManager
.
addListener
(
downloadManagerListener
);
RequirementsHelper
requirementsHelper
;
synchronized
(
requirementsHelpers
)
{
Class
<?
extends
DownloadService
>
clazz
=
getClass
();
requirementsHelper
=
requirementsHelpers
.
get
(
clazz
);
if
(
requirementsHelper
==
null
)
{
requirementsHelper
=
new
RequirementsHelper
(
this
,
getRequirements
(),
getScheduler
(),
clazz
);
requirementsHelpers
.
put
(
clazz
,
requirementsHelper
);
}
}
requirementsHelper
.
start
();
}
@Override
...
...
@@ -237,6 +253,7 @@ public abstract class DownloadService extends Service {
Log
.
e
(
TAG
,
"Ignoring unrecognized action: "
+
intentAction
);
break
;
}
maybeStartWatchingRequirements
();
if
(
downloadManager
.
isIdle
())
{
stop
();
}
...
...
@@ -248,14 +265,7 @@ public abstract class DownloadService extends Service {
logd
(
"onDestroy"
);
foregroundNotificationUpdater
.
stopPeriodicUpdates
();
downloadManager
.
removeListener
(
downloadManagerListener
);
if
(
downloadManager
.
getTaskCount
()
==
0
)
{
synchronized
(
requirementsHelpers
)
{
RequirementsHelper
requirementsHelper
=
requirementsHelpers
.
remove
(
getClass
());
if
(
requirementsHelper
!=
null
)
{
requirementsHelper
.
stop
();
}
}
}
maybeStopWatchingRequirements
();
}
@Nullable
...
...
@@ -312,6 +322,31 @@ public abstract class DownloadService extends Service {
// Do nothing.
}
private
void
maybeStartWatchingRequirements
()
{
if
(
downloadManager
.
getDownloadCount
()
==
0
)
{
return
;
}
Class
<?
extends
DownloadService
>
clazz
=
getClass
();
RequirementsHelper
requirementsHelper
=
requirementsHelpers
.
get
(
clazz
);
if
(
requirementsHelper
==
null
)
{
requirementsHelper
=
new
RequirementsHelper
(
this
,
getRequirements
(),
getScheduler
(),
clazz
);
requirementsHelpers
.
put
(
clazz
,
requirementsHelper
);
requirementsHelper
.
start
();
logd
(
"started watching requirements"
);
}
}
private
void
maybeStopWatchingRequirements
()
{
if
(
downloadManager
.
getDownloadCount
()
>
0
)
{
return
;
}
RequirementsHelper
requirementsHelper
=
requirementsHelpers
.
remove
(
getClass
());
if
(
requirementsHelper
!=
null
)
{
requirementsHelper
.
stop
();
logd
(
"stopped watching requirements"
);
}
}
private
void
stop
()
{
foregroundNotificationUpdater
.
stopPeriodicUpdates
();
// Make sure startForeground is called before stopping. Workaround for [Internal: b/69424260].
...
...
@@ -331,7 +366,7 @@ public abstract class DownloadService extends Service {
private
final
class
DownloadManagerListener
implements
DownloadManager
.
Listener
{
@Override
public
void
onInitialized
(
DownloadManager
downloadManager
)
{
// Do nothing.
maybeStartWatchingRequirements
();
}
@Override
...
...
library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloader.java
View file @
f7ed789f
...
...
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
.
offline
;
package
com
.
google
.
android
.
exoplayer2
.
offline
;
import
android.net.Uri
;
import
com.google.android.exoplayer2.C
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java
View file @
f7ed789f
...
...
@@ -201,6 +201,8 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M, K>, K>
throws
InterruptedException
,
IOException
;
/** Initializes the download, returning a list of {@link Segment}s that need to be downloaded. */
// Writes to downloadedSegments and downloadedBytes are safe. See the comment on download().
@SuppressWarnings
(
"NonAtomicVolatileUpdate"
)
private
List
<
Segment
>
initDownload
()
throws
IOException
,
InterruptedException
{
M
manifest
=
getManifest
(
dataSource
,
manifestUri
);
if
(!
streamKeys
.
isEmpty
())
{
...
...
library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java
View file @
f7ed789f
...
...
@@ -19,7 +19,6 @@ import android.os.Handler;
import
android.os.Looper
;
import
android.support.annotation.NonNull
;
import
android.support.annotation.Nullable
;
import
android.util.SparseIntArray
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.ExoPlaybackException
;
import
com.google.android.exoplayer2.ExoPlayer
;
...
...
@@ -34,6 +33,7 @@ import java.util.ArrayList;
import
java.util.Arrays
;
import
java.util.Collection
;
import
java.util.Collections
;
import
java.util.HashMap
;
import
java.util.IdentityHashMap
;
import
java.util.List
;
import
java.util.Map
;
...
...
@@ -656,7 +656,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
/* package */
static
final
class
MediaSourceHolder
implements
Comparable
<
MediaSourceHolder
>
{
public
final
MediaSource
mediaSource
;
public
final
in
t
uid
;
public
final
Objec
t
uid
;
public
DeferredTimeline
timeline
;
public
int
childIndex
;
...
...
@@ -668,9 +668,9 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
public
MediaSourceHolder
(
MediaSource
mediaSource
)
{
this
.
mediaSource
=
mediaSource
;
this
.
uid
=
System
.
identityHashCode
(
this
);
this
.
timeline
=
new
DeferredTimeline
();
this
.
activeMediaPeriods
=
new
ArrayList
<>();
this
.
uid
=
new
Object
();
}
public
void
reset
(
int
childIndex
,
int
firstWindowIndexInChild
,
int
firstPeriodIndexInChild
)
{
...
...
@@ -728,8 +728,8 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
private
final
int
[]
firstPeriodInChildIndices
;
private
final
int
[]
firstWindowInChildIndices
;
private
final
Timeline
[]
timelines
;
private
final
in
t
[]
uids
;
private
final
SparseIntArray
childIndexByUid
;
private
final
Objec
t
[]
uids
;
private
final
HashMap
<
Object
,
Integer
>
childIndexByUid
;
public
ConcatenatedTimeline
(
Collection
<
MediaSourceHolder
>
mediaSourceHolders
,
...
...
@@ -744,8 +744,8 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
firstPeriodInChildIndices
=
new
int
[
childCount
];
firstWindowInChildIndices
=
new
int
[
childCount
];
timelines
=
new
Timeline
[
childCount
];
uids
=
new
in
t
[
childCount
];
childIndexByUid
=
new
SparseIntArray
();
uids
=
new
Objec
t
[
childCount
];
childIndexByUid
=
new
HashMap
<>
();
int
index
=
0
;
for
(
MediaSourceHolder
mediaSourceHolder
:
mediaSourceHolders
)
{
timelines
[
index
]
=
mediaSourceHolder
.
timeline
;
...
...
@@ -768,11 +768,8 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
@Override
protected
int
getChildIndexByChildUid
(
Object
childUid
)
{
if
(!(
childUid
instanceof
Integer
))
{
return
C
.
INDEX_UNSET
;
}
int
index
=
childIndexByUid
.
get
((
int
)
childUid
,
-
1
);
return
index
==
-
1
?
C
.
INDEX_UNSET
:
index
;
Integer
index
=
childIndexByUid
.
get
(
childUid
);
return
index
==
null
?
C
.
INDEX_UNSET
:
index
;
}
@Override
...
...
@@ -804,7 +801,6 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
public
int
getPeriodCount
()
{
return
periodCount
;
}
}
/**
...
...
library/core/src/main/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSource.java
View file @
f7ed789f
...
...
@@ -19,19 +19,25 @@ package com.google.android.exoplayer2.source;
@Deprecated
public
final
class
DynamicConcatenatingMediaSource
extends
ConcatenatingMediaSource
{
/** @deprecated Use {@link ConcatenatingMediaSource#ConcatenatingMediaSource()} instead. */
/**
* @deprecated Use {@link ConcatenatingMediaSource#ConcatenatingMediaSource(MediaSource...)}
* instead.
*/
@Deprecated
public
DynamicConcatenatingMediaSource
()
{}
/** @deprecated Use {@link ConcatenatingMediaSource#ConcatenatingMediaSource(boolean)} instead. */
/**
* @deprecated Use {@link ConcatenatingMediaSource#ConcatenatingMediaSource(boolean,
* MediaSource...)} instead.
*/
@Deprecated
public
DynamicConcatenatingMediaSource
(
boolean
isAtomic
)
{
super
(
isAtomic
);
}
/**
* @deprecated Use {@link ConcatenatingMediaSource#ConcatenatingMediaSource(boolean,
*
ShuffleOrder
)} instead.
* @deprecated Use {@link ConcatenatingMediaSource#ConcatenatingMediaSource(boolean,
ShuffleOrder,
*
MediaSource...
)} instead.
*/
@Deprecated
public
DynamicConcatenatingMediaSource
(
boolean
isAtomic
,
ShuffleOrder
shuffleOrder
)
{
...
...
library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java
View file @
f7ed789f
...
...
@@ -90,7 +90,7 @@ import java.util.Arrays;
private
final
Runnable
onContinueLoadingRequestedRunnable
;
private
final
Handler
handler
;
private
Callback
callback
;
private
@Nullable
Callback
callback
;
private
SeekMap
seekMap
;
private
SampleQueue
[]
sampleQueues
;
private
int
[]
sampleQueueTrackIds
;
...
...
@@ -190,6 +190,7 @@ import java.util.Arrays;
}
loader
.
release
(
this
);
handler
.
removeCallbacksAndMessages
(
null
);
callback
=
null
;
released
=
true
;
eventDispatcher
.
mediaPeriodReleased
();
}
...
...
@@ -833,11 +834,6 @@ import java.util.Arrays;
}
@Override
public
boolean
isLoadCanceled
()
{
return
loadCanceled
;
}
@Override
public
void
load
()
throws
IOException
,
InterruptedException
{
int
result
=
Extractor
.
RESULT_CONTINUE
;
while
(
result
==
Extractor
.
RESULT_CONTINUE
&&
!
loadCanceled
)
{
...
...
library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java
View file @
f7ed789f
...
...
@@ -349,11 +349,6 @@ import java.util.Arrays;
}
@Override
public
boolean
isLoadCanceled
()
{
return
false
;
}
@Override
public
void
load
()
throws
IOException
,
InterruptedException
{
// We always load from the beginning, so reset the sampleSize to 0.
sampleSize
=
0
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java
View file @
f7ed789f
...
...
@@ -72,11 +72,14 @@ public final class TrackGroup implements Parcelable {
}
/**
* Returns the index of the track with the given format in the group.
* Returns the index of the track with the given format in the group. The format is located by
* identity so, for example, {@code group.indexOf(group.getFormat(index)) == index} even if
* multiple tracks have formats that contain the same values.
*
* @param format The format.
* @return The index of the track, or {@link C#INDEX_UNSET} if no such track exists.
*/
@SuppressWarnings
(
"ReferenceEquality"
)
public
int
indexOf
(
Format
format
)
{
for
(
int
i
=
0
;
i
<
formats
.
length
;
i
++)
{
if
(
format
==
formats
[
i
])
{
...
...
library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunk.java
View file @
f7ed789f
...
...
@@ -44,7 +44,7 @@ public abstract class BaseMediaChunk extends MediaChunk {
* @param endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param seekTimeUs The media time from which output will begin, or {@link C#TIME_UNSET} if the
* whole chunk should be output.
* @param chunkIndex The index of the chunk.
* @param chunkIndex The index of the chunk
, or {@link C#INDEX_UNSET} if it is not known
.
*/
public
BaseMediaChunk
(
DataSource
dataSource
,
...
...
library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunkOutput.java
View file @
f7ed789f
...
...
@@ -21,10 +21,8 @@ import com.google.android.exoplayer2.extractor.TrackOutput;
import
com.google.android.exoplayer2.source.SampleQueue
;
import
com.google.android.exoplayer2.source.chunk.ChunkExtractorWrapper.TrackOutputProvider
;
/**
* An output for {@link BaseMediaChunk}s.
*/
/* package */
final
class
BaseMediaChunkOutput
implements
TrackOutputProvider
{
/** An output for {@link BaseMediaChunk}s. */
public
final
class
BaseMediaChunkOutput
implements
TrackOutputProvider
{
private
static
final
String
TAG
=
"BaseMediaChunkOutput"
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ContainerMediaChunk.java
View file @
f7ed789f
...
...
@@ -49,7 +49,7 @@ public class ContainerMediaChunk extends BaseMediaChunk {
* @param endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param seekTimeUs The media time from which output will begin, or {@link C#TIME_UNSET} if the
* whole chunk should be output.
* @param chunkIndex The index of the chunk.
* @param chunkIndex The index of the chunk
, or {@link C#INDEX_UNSET} if it is not known
.
* @param chunkCount The number of chunks in the underlying media that are spanned by this
* instance. Normally equal to one, but may be larger if multiple chunks as defined by the
* underlying media are being merged into a single load.
...
...
@@ -106,11 +106,6 @@ public class ContainerMediaChunk extends BaseMediaChunk {
loadCanceled
=
true
;
}
@Override
public
final
boolean
isLoadCanceled
()
{
return
loadCanceled
;
}
@SuppressWarnings
(
"NonAtomicVolatileUpdate"
)
@Override
public
final
void
load
()
throws
IOException
,
InterruptedException
{
...
...
library/core/src/main/java/com/google/android/exoplayer2/source/chunk/DataChunk.java
View file @
f7ed789f
...
...
@@ -76,11 +76,6 @@ public abstract class DataChunk extends Chunk {
}
@Override
public
final
boolean
isLoadCanceled
()
{
return
loadCanceled
;
}
@Override
public
final
void
load
()
throws
IOException
,
InterruptedException
{
try
{
dataSource
.
open
(
dataSpec
);
...
...
library/core/src/main/java/com/google/android/exoplayer2/source/chunk/InitializationChunk.java
View file @
f7ed789f
...
...
@@ -69,11 +69,6 @@ public final class InitializationChunk extends Chunk {
loadCanceled
=
true
;
}
@Override
public
boolean
isLoadCanceled
()
{
return
loadCanceled
;
}
@SuppressWarnings
(
"NonAtomicVolatileUpdate"
)
@Override
public
void
load
()
throws
IOException
,
InterruptedException
{
...
...
library/core/src/main/java/com/google/android/exoplayer2/source/chunk/MediaChunk.java
View file @
f7ed789f
...
...
@@ -26,7 +26,7 @@ import com.google.android.exoplayer2.util.Assertions;
*/
public
abstract
class
MediaChunk
extends
Chunk
{
/** The chunk index. */
/** The chunk index
, or {@link C#INDEX_UNSET} if it is not known
. */
public
final
long
chunkIndex
;
/**
...
...
@@ -37,7 +37,7 @@ public abstract class MediaChunk extends Chunk {
* @param trackSelectionData See {@link #trackSelectionData}.
* @param startTimeUs The start time of the media contained by the chunk, in microseconds.
* @param endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param chunkIndex The index of the chunk.
* @param chunkIndex The index of the chunk
, or {@link C#INDEX_UNSET} if it is not known
.
*/
public
MediaChunk
(
DataSource
dataSource
,
...
...
@@ -54,9 +54,9 @@ public abstract class MediaChunk extends Chunk {
this
.
chunkIndex
=
chunkIndex
;
}
/** Returns the next chunk index. */
/** Returns the next chunk index
or {@link C#INDEX_UNSET} if it is not known
. */
public
long
getNextChunkIndex
()
{
return
chunkIndex
+
1
;
return
chunkIndex
!=
C
.
INDEX_UNSET
?
chunkIndex
+
1
:
C
.
INDEX_UNSET
;
}
/**
...
...
library/core/src/main/java/com/google/android/exoplayer2/source/chunk/SingleSampleMediaChunk.java
View file @
f7ed789f
...
...
@@ -34,7 +34,6 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk {
private
final
Format
sampleFormat
;
private
volatile
int
bytesLoaded
;
private
volatile
boolean
loadCanceled
;
private
volatile
boolean
loadCompleted
;
/**
...
...
@@ -45,7 +44,7 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk {
* @param trackSelectionData See {@link #trackSelectionData}.
* @param startTimeUs The start time of the media contained by the chunk, in microseconds.
* @param endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param chunkIndex The index of the chunk.
* @param chunkIndex The index of the chunk
, or {@link C#INDEX_UNSET} if it is not known
.
* @param trackType The type of the chunk. Typically one of the {@link C} {@code TRACK_TYPE_*}
* constants.
* @param sampleFormat The {@link Format} of the sample in the chunk.
...
...
@@ -90,12 +89,7 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk {
@Override
public
void
cancelLoad
()
{
loadCanceled
=
true
;
}
@Override
public
boolean
isLoadCanceled
()
{
return
loadCanceled
;
// Do nothing.
}
@SuppressWarnings
(
"NonAtomicVolatileUpdate"
)
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java
View file @
f7ed789f
...
...
@@ -374,6 +374,9 @@ public final class Cea608Decoder extends CeaDecoder {
private
void
handleMidrowCtrl
(
byte
cc2
)
{
// TODO: support the extended styles (i.e. backgrounds and transparencies)
// A midrow control code advances the cursor.
currentCueBuilder
.
append
(
' '
);
// cc2 - 0|0|1|0|ATRBT|U
// ATRBT is the 3-byte encoded attribute, and U is the underline toggle
boolean
isUnderlined
=
(
cc2
&
0x01
)
==
0x01
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java
View file @
f7ed789f
...
...
@@ -38,7 +38,6 @@ import com.google.android.exoplayer2.util.ParsableBitArray;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.LinkedList
;
import
java.util.List
;
/**
...
...
@@ -196,7 +195,10 @@ public final class Cea708Decoder extends CeaDecoder {
@Override
protected
void
decode
(
SubtitleInputBuffer
inputBuffer
)
{
ccData
.
reset
(
inputBuffer
.
data
.
array
(),
inputBuffer
.
data
.
limit
());
// Subtitle input buffers are non-direct and the position is zero, so calling array() is safe.
@SuppressWarnings
(
"ByteBufferBackingArray"
)
byte
[]
inputBufferData
=
inputBuffer
.
data
.
array
();
ccData
.
reset
(
inputBufferData
,
inputBuffer
.
data
.
limit
());
while
(
ccData
.
bytesLeft
()
>=
3
)
{
int
ccTypeAndValid
=
(
ccData
.
readUnsignedByte
()
&
0x07
);
...
...
@@ -879,7 +881,7 @@ public final class Cea708Decoder extends CeaDecoder {
private
int
row
;
public
CueBuilder
()
{
rolledUpCaptions
=
new
Linked
List
<>();
rolledUpCaptions
=
new
Array
List
<>();
captionStringBuilder
=
new
SpannableStringBuilder
();
reset
();
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaDecoder.java
View file @
f7ed789f
...
...
@@ -24,7 +24,7 @@ import com.google.android.exoplayer2.text.SubtitleDecoderException;
import
com.google.android.exoplayer2.text.SubtitleInputBuffer
;
import
com.google.android.exoplayer2.text.SubtitleOutputBuffer
;
import
com.google.android.exoplayer2.util.Assertions
;
import
java.util.
LinkedList
;
import
java.util.
ArrayDeque
;
import
java.util.PriorityQueue
;
/**
...
...
@@ -35,8 +35,8 @@ import java.util.PriorityQueue;
private
static
final
int
NUM_INPUT_BUFFERS
=
10
;
private
static
final
int
NUM_OUTPUT_BUFFERS
=
2
;
private
final
LinkedList
<
CeaInputBuffer
>
availableInputBuffers
;
private
final
LinkedList
<
SubtitleOutputBuffer
>
availableOutputBuffers
;
private
final
ArrayDeque
<
CeaInputBuffer
>
availableInputBuffers
;
private
final
ArrayDeque
<
SubtitleOutputBuffer
>
availableOutputBuffers
;
private
final
PriorityQueue
<
CeaInputBuffer
>
queuedInputBuffers
;
private
CeaInputBuffer
dequeuedInputBuffer
;
...
...
@@ -44,11 +44,11 @@ import java.util.PriorityQueue;
private
long
queuedInputBufferCount
;
public
CeaDecoder
()
{
availableInputBuffers
=
new
LinkedList
<>();
availableInputBuffers
=
new
ArrayDeque
<>();
for
(
int
i
=
0
;
i
<
NUM_INPUT_BUFFERS
;
i
++)
{
availableInputBuffers
.
add
(
new
CeaInputBuffer
());
}
availableOutputBuffers
=
new
LinkedList
<>();
availableOutputBuffers
=
new
ArrayDeque
<>();
for
(
int
i
=
0
;
i
<
NUM_OUTPUT_BUFFERS
;
i
++)
{
availableOutputBuffers
.
add
(
new
CeaOutputBuffer
());
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java
View file @
f7ed789f
...
...
@@ -62,7 +62,7 @@ public final class SsaDecoder extends SimpleSubtitleDecoder {
super
(
"SsaDecoder"
);
if
(
initializationData
!=
null
&&
!
initializationData
.
isEmpty
())
{
haveInitializationData
=
true
;
String
formatLine
=
new
String
(
initializationData
.
get
(
0
));
String
formatLine
=
Util
.
fromUtf8Bytes
(
initializationData
.
get
(
0
));
Assertions
.
checkArgument
(
formatLine
.
startsWith
(
FORMAT_LINE_PREFIX
));
parseFormatLine
(
formatLine
);
parseHeader
(
new
ParsableByteArray
(
initializationData
.
get
(
1
)));
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java
View file @
f7ed789f
...
...
@@ -26,8 +26,8 @@ import com.google.android.exoplayer2.util.Util;
import
com.google.android.exoplayer2.util.XmlPullParserUtil
;
import
java.io.ByteArrayInputStream
;
import
java.io.IOException
;
import
java.util.ArrayDeque
;
import
java.util.HashMap
;
import
java.util.LinkedList
;
import
java.util.Map
;
import
java.util.regex.Matcher
;
import
java.util.regex.Pattern
;
...
...
@@ -109,13 +109,13 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
ByteArrayInputStream
inputStream
=
new
ByteArrayInputStream
(
bytes
,
0
,
length
);
xmlParser
.
setInput
(
inputStream
,
null
);
TtmlSubtitle
ttmlSubtitle
=
null
;
LinkedList
<
TtmlNode
>
nodeStack
=
new
LinkedList
<>();
ArrayDeque
<
TtmlNode
>
nodeStack
=
new
ArrayDeque
<>();
int
unsupportedNodeDepth
=
0
;
int
eventType
=
xmlParser
.
getEventType
();
FrameAndTickRate
frameAndTickRate
=
DEFAULT_FRAME_AND_TICK_RATE
;
CellResolution
cellResolution
=
DEFAULT_CELL_RESOLUTION
;
while
(
eventType
!=
XmlPullParser
.
END_DOCUMENT
)
{
TtmlNode
parent
=
nodeStack
.
peek
Last
();
TtmlNode
parent
=
nodeStack
.
peek
();
if
(
unsupportedNodeDepth
==
0
)
{
String
name
=
xmlParser
.
getName
();
if
(
eventType
==
XmlPullParser
.
START_TAG
)
{
...
...
@@ -131,7 +131,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
}
else
{
try
{
TtmlNode
node
=
parseNode
(
xmlParser
,
parent
,
regionMap
,
frameAndTickRate
);
nodeStack
.
addLast
(
node
);
nodeStack
.
push
(
node
);
if
(
parent
!=
null
)
{
parent
.
addChild
(
node
);
}
...
...
@@ -145,9 +145,9 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
parent
.
addChild
(
TtmlNode
.
buildTextNode
(
xmlParser
.
getText
()));
}
else
if
(
eventType
==
XmlPullParser
.
END_TAG
)
{
if
(
xmlParser
.
getName
().
equals
(
TtmlNode
.
TAG_TT
))
{
ttmlSubtitle
=
new
TtmlSubtitle
(
nodeStack
.
getLast
(),
globalStyles
,
regionMap
);
ttmlSubtitle
=
new
TtmlSubtitle
(
nodeStack
.
peek
(),
globalStyles
,
regionMap
);
}
nodeStack
.
removeLast
();
nodeStack
.
pop
();
}
}
else
{
if
(
eventType
==
XmlPullParser
.
START_TAG
)
{
...
...
@@ -178,7 +178,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
float
frameRateMultiplier
=
1
;
String
frameRateMultiplierString
=
xmlParser
.
getAttributeValue
(
TTP
,
"frameRateMultiplier"
);
if
(
frameRateMultiplierString
!=
null
)
{
String
[]
parts
=
frameRateMultiplierString
.
split
(
" "
);
String
[]
parts
=
Util
.
split
(
frameRateMultiplierString
,
" "
);
if
(
parts
.
length
!=
2
)
{
throw
new
SubtitleDecoderException
(
"frameRateMultiplier doesn't have 2 parts"
);
}
...
...
@@ -354,7 +354,8 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
}
private
String
[]
parseStyleIds
(
String
parentStyleIds
)
{
return
parentStyleIds
.
split
(
"\\s+"
);
parentStyleIds
=
parentStyleIds
.
trim
();
return
parentStyleIds
.
isEmpty
()
?
new
String
[
0
]
:
Util
.
split
(
parentStyleIds
,
"\\s+"
);
}
private
TtmlStyle
parseStyleAttributes
(
XmlPullParser
parser
,
TtmlStyle
style
)
{
...
...
@@ -531,7 +532,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
private
static
void
parseFontSize
(
String
expression
,
TtmlStyle
out
)
throws
SubtitleDecoderException
{
String
[]
expressions
=
expression
.
split
(
"\\s+"
);
String
[]
expressions
=
Util
.
split
(
expression
,
"\\s+"
);
Matcher
matcher
;
if
(
expressions
.
length
==
1
)
{
matcher
=
FONT_SIZE
.
matcher
(
expression
);
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java
View file @
f7ed789f
...
...
@@ -92,7 +92,8 @@ public final class Tx3gDecoder extends SimpleSubtitleDecoder {
|
((
initializationBytes
[
27
]
&
0xFF
)
<<
16
)
|
((
initializationBytes
[
28
]
&
0xFF
)
<<
8
)
|
(
initializationBytes
[
29
]
&
0xFF
);
String
fontFamily
=
new
String
(
initializationBytes
,
43
,
initializationBytes
.
length
-
43
);
String
fontFamily
=
Util
.
fromUtf8Bytes
(
initializationBytes
,
43
,
initializationBytes
.
length
-
43
);
defaultFontFamily
=
TX3G_SERIF
.
equals
(
fontFamily
)
?
C
.
SERIF_NAME
:
C
.
SANS_SERIF_NAME
;
//font size (initializationBytes[25]) is 5% of video height
calculatedVideoTrackHeight
=
20
*
initializationBytes
[
25
];
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/CssParser.java
View file @
f7ed789f
...
...
@@ -18,6 +18,7 @@ package com.google.android.exoplayer2.text.webvtt;
import
android.text.TextUtils
;
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.regex.Matcher
;
import
java.util.regex.Pattern
;
...
...
@@ -314,7 +315,7 @@ import java.util.regex.Pattern;
}
selector
=
selector
.
substring
(
0
,
voiceStartIndex
);
}
String
[]
classDivision
=
selector
.
split
(
"\\."
);
String
[]
classDivision
=
Util
.
split
(
selector
,
"\\."
);
String
tagAndIdDivision
=
classDivision
[
0
];
int
idPrefixIndex
=
tagAndIdDivision
.
indexOf
(
'#'
);
if
(
idPrefixIndex
!=
-
1
)
{
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoder.java
View file @
f7ed789f
...
...
@@ -78,7 +78,8 @@ public final class Mp4WebvttDecoder extends SimpleSubtitleDecoder {
int
boxType
=
sampleData
.
readInt
();
remainingCueBoxBytes
-=
BOX_HEADER_SIZE
;
int
payloadLength
=
boxSize
-
BOX_HEADER_SIZE
;
String
boxPayload
=
new
String
(
sampleData
.
data
,
sampleData
.
getPosition
(),
payloadLength
);
String
boxPayload
=
Util
.
fromUtf8Bytes
(
sampleData
.
data
,
sampleData
.
getPosition
(),
payloadLength
);
sampleData
.
skipBytes
(
payloadLength
);
remainingCueBoxBytes
-=
payloadLength
;
if
(
boxType
==
TYPE_sttg
)
{
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java
View file @
f7ed789f
...
...
@@ -34,11 +34,12 @@ import android.text.style.UnderlineSpan;
import
android.util.Log
;
import
com.google.android.exoplayer2.text.Cue
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
com.google.android.exoplayer2.util.Util
;
import
java.util.ArrayDeque
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.Stack
;
import
java.util.regex.Matcher
;
import
java.util.regex.Pattern
;
...
...
@@ -157,7 +158,7 @@ public final class WebvttCueParser {
/* package */
static
void
parseCueText
(
String
id
,
String
markup
,
WebvttCue
.
Builder
builder
,
List
<
WebvttCssStyle
>
styles
)
{
SpannableStringBuilder
spannedText
=
new
SpannableStringBuilder
();
Stack
<
StartTag
>
startTagStack
=
new
Stack
<>();
ArrayDeque
<
StartTag
>
startTagStack
=
new
ArrayDeque
<>();
List
<
StyleMatch
>
scratchStyleMatches
=
new
ArrayList
<>();
int
pos
=
0
;
while
(
pos
<
markup
.
length
())
{
...
...
@@ -456,7 +457,7 @@ public final class WebvttCueParser {
if
(
tagExpression
.
isEmpty
())
{
return
null
;
}
return
tagExpression
.
split
(
"[ \\.]"
)[
0
];
return
Util
.
splitAtFirst
(
tagExpression
,
"[ \\.]"
)[
0
];
}
private
static
void
getApplicableStyles
(
List
<
WebvttCssStyle
>
declaredStyles
,
String
id
,
...
...
@@ -518,7 +519,7 @@ public final class WebvttCueParser {
voice
=
fullTagExpression
.
substring
(
voiceStartIndex
).
trim
();
fullTagExpression
=
fullTagExpression
.
substring
(
0
,
voiceStartIndex
);
}
String
[]
nameAndClasses
=
fullTagExpression
.
split
(
"\\."
);
String
[]
nameAndClasses
=
Util
.
split
(
fullTagExpression
,
"\\."
);
String
name
=
nameAndClasses
[
0
];
String
[]
classes
;
if
(
nameAndClasses
.
length
>
1
)
{
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttParserUtil.java
View file @
f7ed789f
...
...
@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.text.webvtt;
import
com.google.android.exoplayer2.text.SubtitleDecoderException
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
com.google.android.exoplayer2.util.Util
;
import
java.util.regex.Matcher
;
import
java.util.regex.Pattern
;
...
...
@@ -53,8 +54,8 @@ public final class WebvttParserUtil {
*/
public
static
long
parseTimestampUs
(
String
timestamp
)
throws
NumberFormatException
{
long
value
=
0
;
String
[]
parts
=
timestamp
.
split
(
"\\."
,
2
);
String
[]
subparts
=
parts
[
0
].
split
(
":"
);
String
[]
parts
=
Util
.
splitAtFirst
(
timestamp
,
"\\."
);
String
[]
subparts
=
Util
.
split
(
parts
[
0
],
":"
);
for
(
String
subpart
:
subparts
)
{
value
=
(
value
*
60
)
+
Long
.
parseLong
(
subpart
);
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java
View file @
f7ed789f
...
...
@@ -15,6 +15,7 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
trackselection
;
import
android.support.annotation.Nullable
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.Format
;
import
com.google.android.exoplayer2.source.TrackGroup
;
...
...
@@ -242,9 +243,11 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
this
.
minTimeBetweenBufferReevaluationMs
=
minTimeBetweenBufferReevaluationMs
;
this
.
clock
=
clock
;
playbackSpeed
=
1
f
;
selectedIndex
=
determineIdealSelectedIndex
(
Long
.
MIN_VALUE
);
reason
=
C
.
SELECTION_REASON_INITIAL
;
lastBufferEvaluationMs
=
C
.
TIME_UNSET
;
@SuppressWarnings
(
"nullness:method.invocation.invalid"
)
int
selectedIndex
=
determineIdealSelectedIndex
(
Long
.
MIN_VALUE
);
this
.
selectedIndex
=
selectedIndex
;
}
@Override
...
...
@@ -301,7 +304,7 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
}
@Override
public
Object
getSelectionData
()
{
public
@Nullable
Object
getSelectionData
()
{
return
null
;
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/trackselection/BaseTrackSelection.java
View file @
f7ed789f
...
...
@@ -110,6 +110,7 @@ public abstract class BaseTrackSelection implements TrackSelection {
}
@Override
@SuppressWarnings
(
"ReferenceEquality"
)
public
final
int
indexOf
(
Format
format
)
{
for
(
int
i
=
0
;
i
<
length
;
i
++)
{
if
(
formats
[
i
]
==
format
)
{
...
...
@@ -183,7 +184,9 @@ public abstract class BaseTrackSelection implements TrackSelection {
return
hashCode
;
}
// Track groups are compared by identity not value, as distinct groups may have the same value.
@Override
@SuppressWarnings
(
"ReferenceEquality"
)
public
boolean
equals
(
@Nullable
Object
obj
)
{
if
(
this
==
obj
)
{
return
true
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java
View file @
f7ed789f
...
...
@@ -19,7 +19,6 @@ import android.content.Context;
import
android.graphics.Point
;
import
android.os.Parcel
;
import
android.os.Parcelable
;
import
android.support.annotation.NonNull
;
import
android.support.annotation.Nullable
;
import
android.text.TextUtils
;
import
android.util.Pair
;
...
...
@@ -161,8 +160,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
private
final
SparseArray
<
Map
<
TrackGroupArray
,
SelectionOverride
>>
selectionOverrides
;
private
final
SparseBooleanArray
rendererDisabledFlags
;
private
String
preferredAudioLanguage
;
private
String
preferredTextLanguage
;
private
@Nullable
String
preferredAudioLanguage
;
private
@Nullable
String
preferredTextLanguage
;
private
boolean
selectUndeterminedTextLanguage
;
private
int
disabledTextTrackSelectionFlags
;
private
boolean
forceLowestBitrate
;
...
...
@@ -572,14 +571,14 @@ public class DefaultTrackSelector extends MappingTrackSelector {
* The preferred language for audio, as well as for forced text tracks, as an ISO 639-2/T tag.
* {@code null} selects the default track, or the first track if there's no default.
*/
public
final
String
preferredAudioLanguage
;
public
final
@Nullable
String
preferredAudioLanguage
;
// Text
/**
* The preferred language for text tracks as an ISO 639-2/T tag. {@code null} selects the
* default track if there is one, or no track otherwise.
*/
public
final
String
preferredTextLanguage
;
public
final
@Nullable
String
preferredTextLanguage
;
/**
* Whether a text track with undetermined language should be selected if no track with
* {@link #preferredTextLanguage} is available, or if {@link #preferredTextLanguage} is unset.
...
...
@@ -673,8 +672,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
/* package */
Parameters
(
SparseArray
<
Map
<
TrackGroupArray
,
SelectionOverride
>>
selectionOverrides
,
SparseBooleanArray
rendererDisabledFlags
,
String
preferredAudioLanguage
,
String
preferredTextLanguage
,
@Nullable
String
preferredAudioLanguage
,
@Nullable
String
preferredTextLanguage
,
boolean
selectUndeterminedTextLanguage
,
int
disabledTextTrackSelectionFlags
,
boolean
forceLowestBitrate
,
...
...
@@ -759,7 +758,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
* @param groups The {@link TrackGroupArray}.
* @return The override, or null if no override exists.
*/
public
final
SelectionOverride
getSelectionOverride
(
int
rendererIndex
,
TrackGroupArray
groups
)
{
public
final
@Nullable
SelectionOverride
getSelectionOverride
(
int
rendererIndex
,
TrackGroupArray
groups
)
{
Map
<
TrackGroupArray
,
SelectionOverride
>
overrides
=
selectionOverrides
.
get
(
rendererIndex
);
return
overrides
!=
null
?
overrides
.
get
(
groups
)
:
null
;
}
...
...
@@ -816,8 +816,9 @@ public class DefaultTrackSelector extends MappingTrackSelector {
result
=
31
*
result
+
viewportHeight
;
result
=
31
*
result
+
maxVideoBitrate
;
result
=
31
*
result
+
tunnelingAudioSessionId
;
result
=
31
*
result
+
preferredAudioLanguage
.
hashCode
();
result
=
31
*
result
+
preferredTextLanguage
.
hashCode
();
result
=
31
*
result
+
(
preferredAudioLanguage
==
null
?
0
:
preferredAudioLanguage
.
hashCode
());
result
=
31
*
result
+
(
preferredTextLanguage
==
null
?
0
:
preferredTextLanguage
.
hashCode
());
return
result
;
}
...
...
@@ -1042,7 +1043,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
private
static
final
int
[]
NO_TRACKS
=
new
int
[
0
];
private
static
final
int
WITHIN_RENDERER_CAPABILITIES_BONUS
=
1000
;
private
final
TrackSelection
.
Factory
adaptiveTrackSelectionFactory
;
private
final
@Nullable
TrackSelection
.
Factory
adaptiveTrackSelectionFactory
;
private
final
AtomicReference
<
Parameters
>
parametersReference
;
/**
...
...
@@ -1069,7 +1070,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
* @param adaptiveTrackSelectionFactory A factory for adaptive {@link TrackSelection}s, or null if
* the selector should not support adaptive tracks.
*/
public
DefaultTrackSelector
(
TrackSelection
.
Factory
adaptiveTrackSelectionFactory
)
{
public
DefaultTrackSelector
(
@Nullable
TrackSelection
.
Factory
adaptiveTrackSelectionFactory
)
{
this
.
adaptiveTrackSelectionFactory
=
adaptiveTrackSelectionFactory
;
parametersReference
=
new
AtomicReference
<>(
Parameters
.
DEFAULT
);
}
...
...
@@ -1139,7 +1140,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
/** @deprecated Use {@link Parameters#getSelectionOverride(int, TrackGroupArray)}. */
@Deprecated
public
final
SelectionOverride
getSelectionOverride
(
int
rendererIndex
,
TrackGroupArray
groups
)
{
public
final
@Nullable
SelectionOverride
getSelectionOverride
(
int
rendererIndex
,
TrackGroupArray
groups
)
{
return
getParameters
().
getSelectionOverride
(
rendererIndex
,
groups
);
}
...
...
@@ -1170,11 +1172,12 @@ public class DefaultTrackSelector extends MappingTrackSelector {
// MappingTrackSelector implementation.
@Override
protected
final
Pair
<
RendererConfiguration
[],
TrackSelection
[]>
selectTracks
(
MappedTrackInfo
mappedTrackInfo
,
int
[][][]
rendererFormatSupports
,
int
[]
rendererMixedMimeTypeAdaptationSupports
)
throws
ExoPlaybackException
{
protected
final
Pair
<
RendererConfiguration
[],
TrackSelection
[]>
selectTracks
(
MappedTrackInfo
mappedTrackInfo
,
int
[][][]
rendererFormatSupports
,
int
[]
rendererMixedMimeTypeAdaptationSupports
)
throws
ExoPlaybackException
{
Parameters
params
=
parametersReference
.
get
();
int
rendererCount
=
mappedTrackInfo
.
getRendererCount
();
TrackSelection
[]
rendererTrackSelections
=
...
...
@@ -1200,8 +1203,9 @@ public class DefaultTrackSelector extends MappingTrackSelector {
rendererTrackGroups
.
get
(
override
.
groupIndex
),
override
.
tracks
[
0
]);
}
else
{
rendererTrackSelections
[
i
]
=
adaptiveTrackSelectionFactory
.
createTrackSelection
(
rendererTrackGroups
.
get
(
override
.
groupIndex
),
override
.
tracks
);
Assertions
.
checkNotNull
(
adaptiveTrackSelectionFactory
)
.
createTrackSelection
(
rendererTrackGroups
.
get
(
override
.
groupIndex
),
override
.
tracks
);
}
}
}
...
...
@@ -1209,7 +1213,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
// Initialize the renderer configurations to the default configuration for all renderers with
// selections, and null otherwise.
RendererConfiguration
[]
rendererConfigurations
=
new
RendererConfiguration
[
rendererCount
];
RendererConfiguration
[]
rendererConfigurations
=
new
RendererConfiguration
[
rendererCount
];
for
(
int
i
=
0
;
i
<
rendererCount
;
i
++)
{
boolean
forceRendererDisabled
=
params
.
getRendererDisabled
(
i
);
boolean
rendererEnabled
=
...
...
@@ -1331,12 +1336,12 @@ public class DefaultTrackSelector extends MappingTrackSelector {
* @return The {@link TrackSelection} for the renderer, or null if no selection was made.
* @throws ExoPlaybackException If an error occurs while selecting the tracks.
*/
protected
TrackSelection
selectVideoTrack
(
protected
@Nullable
TrackSelection
selectVideoTrack
(
TrackGroupArray
groups
,
int
[][]
formatSupports
,
int
mixedMimeTypeAdaptationSupports
,
Parameters
params
,
TrackSelection
.
Factory
adaptiveTrackSelectionFactory
)
@Nullable
TrackSelection
.
Factory
adaptiveTrackSelectionFactory
)
throws
ExoPlaybackException
{
TrackSelection
selection
=
null
;
if
(!
params
.
forceLowestBitrate
&&
adaptiveTrackSelectionFactory
!=
null
)
{
...
...
@@ -1354,7 +1359,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
return
selection
;
}
private
static
TrackSelection
selectAdaptiveVideoTrack
(
private
static
@Nullable
TrackSelection
selectAdaptiveVideoTrack
(
TrackGroupArray
groups
,
int
[][]
formatSupport
,
int
mixedMimeTypeAdaptationSupports
,
...
...
@@ -1374,7 +1379,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
params
.
maxVideoBitrate
,
params
.
viewportWidth
,
params
.
viewportHeight
,
params
.
viewportOrientationMayChange
);
if
(
adaptiveTracks
.
length
>
0
)
{
return
adaptiveTrackSelectionFactory
.
createTrackSelection
(
group
,
adaptiveTracks
);
return
Assertions
.
checkNotNull
(
adaptiveTrackSelectionFactory
)
.
createTrackSelection
(
group
,
adaptiveTracks
);
}
}
return
null
;
...
...
@@ -1421,9 +1427,15 @@ public class DefaultTrackSelector extends MappingTrackSelector {
return
selectedTrackIndices
.
size
()
<
2
?
NO_TRACKS
:
Util
.
toArray
(
selectedTrackIndices
);
}
private
static
int
getAdaptiveVideoTrackCountForMimeType
(
TrackGroup
group
,
int
[]
formatSupport
,
int
requiredAdaptiveSupport
,
String
mimeType
,
int
maxVideoWidth
,
int
maxVideoHeight
,
int
maxVideoBitrate
,
List
<
Integer
>
selectedTrackIndices
)
{
private
static
int
getAdaptiveVideoTrackCountForMimeType
(
TrackGroup
group
,
int
[]
formatSupport
,
int
requiredAdaptiveSupport
,
@Nullable
String
mimeType
,
int
maxVideoWidth
,
int
maxVideoHeight
,
int
maxVideoBitrate
,
List
<
Integer
>
selectedTrackIndices
)
{
int
adaptiveTrackCount
=
0
;
for
(
int
i
=
0
;
i
<
selectedTrackIndices
.
size
();
i
++)
{
int
trackIndex
=
selectedTrackIndices
.
get
(
i
);
...
...
@@ -1436,9 +1448,15 @@ public class DefaultTrackSelector extends MappingTrackSelector {
return
adaptiveTrackCount
;
}
private
static
void
filterAdaptiveVideoTrackCountForMimeType
(
TrackGroup
group
,
int
[]
formatSupport
,
int
requiredAdaptiveSupport
,
String
mimeType
,
int
maxVideoWidth
,
int
maxVideoHeight
,
int
maxVideoBitrate
,
List
<
Integer
>
selectedTrackIndices
)
{
private
static
void
filterAdaptiveVideoTrackCountForMimeType
(
TrackGroup
group
,
int
[]
formatSupport
,
int
requiredAdaptiveSupport
,
@Nullable
String
mimeType
,
int
maxVideoWidth
,
int
maxVideoHeight
,
int
maxVideoBitrate
,
List
<
Integer
>
selectedTrackIndices
)
{
for
(
int
i
=
selectedTrackIndices
.
size
()
-
1
;
i
>=
0
;
i
--)
{
int
trackIndex
=
selectedTrackIndices
.
get
(
i
);
if
(!
isSupportedAdaptiveVideoTrack
(
group
.
getFormat
(
trackIndex
),
mimeType
,
...
...
@@ -1449,8 +1467,13 @@ public class DefaultTrackSelector extends MappingTrackSelector {
}
}
private
static
boolean
isSupportedAdaptiveVideoTrack
(
Format
format
,
String
mimeType
,
int
formatSupport
,
int
requiredAdaptiveSupport
,
int
maxVideoWidth
,
int
maxVideoHeight
,
private
static
boolean
isSupportedAdaptiveVideoTrack
(
Format
format
,
@Nullable
String
mimeType
,
int
formatSupport
,
int
requiredAdaptiveSupport
,
int
maxVideoWidth
,
int
maxVideoHeight
,
int
maxVideoBitrate
)
{
return
isSupported
(
formatSupport
,
false
)
&&
((
formatSupport
&
requiredAdaptiveSupport
)
!=
0
)
&&
(
mimeType
==
null
||
Util
.
areEqual
(
format
.
sampleMimeType
,
mimeType
))
...
...
@@ -1459,7 +1482,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
&&
(
format
.
bitrate
==
Format
.
NO_VALUE
||
format
.
bitrate
<=
maxVideoBitrate
);
}
private
static
TrackSelection
selectFixedVideoTrack
(
private
static
@Nullable
TrackSelection
selectFixedVideoTrack
(
TrackGroupArray
groups
,
int
[][]
formatSupports
,
Parameters
params
)
{
TrackGroup
selectedGroup
=
null
;
int
selectedTrackIndex
=
0
;
...
...
@@ -1537,12 +1560,12 @@ public class DefaultTrackSelector extends MappingTrackSelector {
* @return The {@link TrackSelection} for the renderer, or null if no selection was made.
* @throws ExoPlaybackException If an error occurs while selecting the tracks.
*/
protected
TrackSelection
selectAudioTrack
(
protected
@Nullable
TrackSelection
selectAudioTrack
(
TrackGroupArray
groups
,
int
[][]
formatSupports
,
int
mixedMimeTypeAdaptationSupports
,
Parameters
params
,
TrackSelection
.
Factory
adaptiveTrackSelectionFactory
)
@Nullable
TrackSelection
.
Factory
adaptiveTrackSelectionFactory
)
throws
ExoPlaybackException
{
int
selectedTrackIndex
=
C
.
INDEX_UNSET
;
int
selectedGroupIndex
=
C
.
INDEX_UNSET
;
...
...
@@ -1606,8 +1629,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
int
[]
adaptiveIndices
=
new
int
[
selectedConfigurationTrackCount
];
int
index
=
0
;
for
(
int
i
=
0
;
i
<
group
.
length
;
i
++)
{
if
(
isSupportedAdaptiveAudioTrack
(
group
.
getFormat
(
i
),
formatSupport
[
i
],
selectedConfiguration
))
{
if
(
isSupportedAdaptiveAudioTrack
(
group
.
getFormat
(
i
),
formatSupport
[
i
],
Assertions
.
checkNotNull
(
selectedConfiguration
)
))
{
adaptiveIndices
[
index
++]
=
i
;
}
}
...
...
@@ -1648,7 +1671,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
* @return The {@link TrackSelection} for the renderer, or null if no selection was made.
* @throws ExoPlaybackException If an error occurs while selecting the tracks.
*/
protected
TrackSelection
selectTextTrack
(
protected
@Nullable
TrackSelection
selectTextTrack
(
TrackGroupArray
groups
,
int
[][]
formatSupport
,
Parameters
params
)
throws
ExoPlaybackException
{
TrackGroup
selectedGroup
=
null
;
...
...
@@ -1721,7 +1744,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
* @return The {@link TrackSelection} for the renderer, or null if no selection was made.
* @throws ExoPlaybackException If an error occurs while selecting the tracks.
*/
protected
TrackSelection
selectOtherTrack
(
protected
@Nullable
TrackSelection
selectOtherTrack
(
int
trackType
,
TrackGroupArray
groups
,
int
[][]
formatSupport
,
Parameters
params
)
throws
ExoPlaybackException
{
TrackGroup
selectedGroup
=
null
;
...
...
@@ -1883,15 +1906,15 @@ public class DefaultTrackSelector extends MappingTrackSelector {
}
/**
* Returns whether a {@link Format} specifies a particular language, or {@code false} if
*
{@code
language} is null.
* Returns whether a {@link Format} specifies a particular language, or {@code false} if
{@code
* language} is null.
*
* @param format The {@link Format}.
* @param language The language.
* @return Whether the format specifies the language, or {@code false} if {@code language} is
* null.
*/
protected
static
boolean
formatHasLanguage
(
Format
format
,
String
language
)
{
protected
static
boolean
formatHasLanguage
(
Format
format
,
@Nullable
String
language
)
{
return
language
!=
null
&&
TextUtils
.
equals
(
language
,
Util
.
normalizeLanguageCode
(
format
.
language
));
}
...
...
@@ -1997,7 +2020,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
* negative integer if this score is worse than the other.
*/
@Override
public
int
compareTo
(
@NonNull
AudioTrackScore
other
)
{
public
int
compareTo
(
AudioTrackScore
other
)
{
if
(
this
.
withinRendererCapabilitiesScore
!=
other
.
withinRendererCapabilitiesScore
)
{
return
compareInts
(
this
.
withinRendererCapabilitiesScore
,
other
.
withinRendererCapabilitiesScore
);
...
...
@@ -2066,9 +2089,9 @@ public class DefaultTrackSelector extends MappingTrackSelector {
public
final
int
channelCount
;
public
final
int
sampleRate
;
public
final
String
mimeType
;
public
final
@Nullable
String
mimeType
;
public
AudioConfigurationTuple
(
int
channelCount
,
int
sampleRate
,
String
mimeType
)
{
public
AudioConfigurationTuple
(
int
channelCount
,
int
sampleRate
,
@Nullable
String
mimeType
)
{
this
.
channelCount
=
channelCount
;
this
.
sampleRate
=
sampleRate
;
this
.
mimeType
=
mimeType
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/trackselection/FixedTrackSelection.java
View file @
f7ed789f
...
...
@@ -15,6 +15,7 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
trackselection
;
import
android.support.annotation.Nullable
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.source.TrackGroup
;
import
com.google.android.exoplayer2.util.Assertions
;
...
...
@@ -30,7 +31,7 @@ public final class FixedTrackSelection extends BaseTrackSelection {
public
static
final
class
Factory
implements
TrackSelection
.
Factory
{
private
final
int
reason
;
private
final
Object
data
;
private
final
@Nullable
Object
data
;
public
Factory
()
{
this
.
reason
=
C
.
SELECTION_REASON_UNKNOWN
;
...
...
@@ -41,7 +42,7 @@ public final class FixedTrackSelection extends BaseTrackSelection {
* @param reason A reason for the track selection.
* @param data Optional data associated with the track selection.
*/
public
Factory
(
int
reason
,
Object
data
)
{
public
Factory
(
int
reason
,
@Nullable
Object
data
)
{
this
.
reason
=
reason
;
this
.
data
=
data
;
}
...
...
@@ -51,11 +52,10 @@ public final class FixedTrackSelection extends BaseTrackSelection {
Assertions
.
checkArgument
(
tracks
.
length
==
1
);
return
new
FixedTrackSelection
(
group
,
tracks
[
0
],
reason
,
data
);
}
}
private
final
int
reason
;
private
final
Object
data
;
private
final
@Nullable
Object
data
;
/**
* @param group The {@link TrackGroup}. Must not be null.
...
...
@@ -71,7 +71,7 @@ public final class FixedTrackSelection extends BaseTrackSelection {
* @param reason A reason for the track selection.
* @param data Optional data associated with the track selection.
*/
public
FixedTrackSelection
(
TrackGroup
group
,
int
track
,
int
reason
,
Object
data
)
{
public
FixedTrackSelection
(
TrackGroup
group
,
int
track
,
int
reason
,
@Nullable
Object
data
)
{
super
(
group
,
track
);
this
.
reason
=
reason
;
this
.
data
=
data
;
...
...
@@ -94,7 +94,7 @@ public final class FixedTrackSelection extends BaseTrackSelection {
}
@Override
public
Object
getSelectionData
()
{
public
@Nullable
Object
getSelectionData
()
{
return
data
;
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java
View file @
f7ed789f
...
...
@@ -16,6 +16,7 @@
package
com
.
google
.
android
.
exoplayer2
.
trackselection
;
import
android.support.annotation.IntDef
;
import
android.support.annotation.Nullable
;
import
android.util.Pair
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.ExoPlaybackException
;
...
...
@@ -301,13 +302,13 @@ public abstract class MappingTrackSelector extends TrackSelector {
}
private
MappedTrackInfo
currentMappedTrackInfo
;
private
@Nullable
MappedTrackInfo
currentMappedTrackInfo
;
/**
* Returns the mapping information for the currently active track selection, or null if no
* selection is currently active.
*/
public
final
MappedTrackInfo
getCurrentMappedTrackInfo
()
{
public
final
@Nullable
MappedTrackInfo
getCurrentMappedTrackInfo
()
{
return
currentMappedTrackInfo
;
}
...
...
@@ -357,9 +358,11 @@ public abstract class MappingTrackSelector extends TrackSelector {
int
[]
rendererTrackTypes
=
new
int
[
rendererCapabilities
.
length
];
for
(
int
i
=
0
;
i
<
rendererCapabilities
.
length
;
i
++)
{
int
rendererTrackGroupCount
=
rendererTrackGroupCounts
[
i
];
rendererTrackGroupArrays
[
i
]
=
new
TrackGroupArray
(
Arrays
.
copyOf
(
rendererTrackGroups
[
i
],
rendererTrackGroupCount
));
rendererFormatSupports
[
i
]
=
Arrays
.
copyOf
(
rendererFormatSupports
[
i
],
rendererTrackGroupCount
);
rendererTrackGroupArrays
[
i
]
=
new
TrackGroupArray
(
Util
.
nullSafeArrayCopy
(
rendererTrackGroups
[
i
],
rendererTrackGroupCount
));
rendererFormatSupports
[
i
]
=
Util
.
nullSafeArrayCopy
(
rendererFormatSupports
[
i
],
rendererTrackGroupCount
);
rendererTrackTypes
[
i
]
=
rendererCapabilities
[
i
].
getTrackType
();
}
...
...
@@ -367,7 +370,7 @@ public abstract class MappingTrackSelector extends TrackSelector {
int
unmappedTrackGroupCount
=
rendererTrackGroupCounts
[
rendererCapabilities
.
length
];
TrackGroupArray
unmappedTrackGroupArray
=
new
TrackGroupArray
(
Arrays
.
copyOf
(
Util
.
nullSafeArrayCopy
(
rendererTrackGroups
[
rendererCapabilities
.
length
],
unmappedTrackGroupCount
));
// Package up the track information and selections.
...
...
@@ -399,11 +402,12 @@ public abstract class MappingTrackSelector extends TrackSelector {
* RendererCapabilities#getTrackType()} is {@link C#TRACK_TYPE_NONE}.
* @throws ExoPlaybackException If an error occurs while selecting the tracks.
*/
protected
abstract
Pair
<
RendererConfiguration
[],
TrackSelection
[]>
selectTracks
(
MappedTrackInfo
mappedTrackInfo
,
int
[][][]
rendererFormatSupports
,
int
[]
rendererMixedMimeTypeAdaptationSupport
)
throws
ExoPlaybackException
;
protected
abstract
Pair
<
RendererConfiguration
[],
TrackSelection
[]>
selectTracks
(
MappedTrackInfo
mappedTrackInfo
,
int
[][][]
rendererFormatSupports
,
int
[]
rendererMixedMimeTypeAdaptationSupport
)
throws
ExoPlaybackException
;
/**
* Finds the renderer to which the provided {@link TrackGroup} should be mapped.
...
...
library/core/src/main/java/com/google/android/exoplayer2/trackselection/RandomTrackSelection.java
View file @
f7ed789f
...
...
@@ -16,6 +16,7 @@
package
com
.
google
.
android
.
exoplayer2
.
trackselection
;
import
android.os.SystemClock
;
import
android.support.annotation.Nullable
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.source.TrackGroup
;
import
java.util.Random
;
...
...
@@ -47,7 +48,6 @@ public final class RandomTrackSelection extends BaseTrackSelection {
public
RandomTrackSelection
createTrackSelection
(
TrackGroup
group
,
int
...
tracks
)
{
return
new
RandomTrackSelection
(
group
,
tracks
,
random
);
}
}
private
final
Random
random
;
...
...
@@ -123,7 +123,7 @@ public final class RandomTrackSelection extends BaseTrackSelection {
}
@Override
public
Object
getSelectionData
()
{
public
@Nullable
Object
getSelectionData
()
{
return
null
;
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java
View file @
f7ed789f
...
...
@@ -15,6 +15,7 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
trackselection
;
import
android.support.annotation.Nullable
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.Format
;
import
com.google.android.exoplayer2.source.TrackGroup
;
...
...
@@ -90,7 +91,9 @@ public interface TrackSelection {
int
getIndexInTrackGroup
(
int
index
);
/**
* Returns the index in the selection of the track with the specified format.
* Returns the index in the selection of the track with the specified format. The format is
* located by identity so, for example, {@code selection.indexOf(selection.getFormat(index)) ==
* index} even if multiple selected tracks have formats that contain the same values.
*
* @param format The format.
* @return The index in the selection, or {@link C#INDEX_UNSET} if the track with the specified
...
...
@@ -129,10 +132,8 @@ public interface TrackSelection {
*/
int
getSelectionReason
();
/**
* Returns optional data associated with the current track selection.
*/
Object
getSelectionData
();
/** Returns optional data associated with the current track selection. */
@Nullable
Object
getSelectionData
();
// Adaptation.
...
...
library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionArray.java
View file @
f7ed789f
...
...
@@ -29,9 +29,7 @@ public final class TrackSelectionArray {
// Lazily initialized hashcode.
private
int
hashCode
;
/**
* @param trackSelections The selections. Must not be null, but may contain null elements.
*/
/** @param trackSelections The selections. Must not be null, but may contain null elements. */
public
TrackSelectionArray
(
TrackSelection
...
trackSelections
)
{
this
.
trackSelections
=
trackSelections
;
this
.
length
=
trackSelections
.
length
;
...
...
@@ -43,13 +41,11 @@ public final class TrackSelectionArray {
* @param index The index of the selection.
* @return The selection.
*/
public
TrackSelection
get
(
int
index
)
{
public
@Nullable
TrackSelection
get
(
int
index
)
{
return
trackSelections
[
index
];
}
/**
* Returns the selections in a newly allocated array.
*/
/** Returns the selections in a newly allocated array. */
public
TrackSelection
[]
getAll
()
{
return
trackSelections
.
clone
();
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelector.java
View file @
f7ed789f
...
...
@@ -15,6 +15,7 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
trackselection
;
import
android.support.annotation.Nullable
;
import
com.google.android.exoplayer2.ExoPlaybackException
;
import
com.google.android.exoplayer2.ExoPlayer
;
import
com.google.android.exoplayer2.Renderer
;
...
...
@@ -89,7 +90,7 @@ public abstract class TrackSelector {
}
private
InvalidationListener
listener
;
private
@Nullable
InvalidationListener
listener
;
/**
* Called by the player to initialize the selector.
...
...
library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectorResult.java
View file @
f7ed789f
...
...
@@ -48,7 +48,9 @@ public final class TrackSelectorResult {
* TrackSelector#onSelectionActivated(Object)} should the selection be activated.
*/
public
TrackSelectorResult
(
RendererConfiguration
[]
rendererConfigurations
,
TrackSelection
[]
selections
,
Object
info
)
{
RendererConfiguration
[]
rendererConfigurations
,
TrackSelection
[]
selections
,
Object
info
)
{
this
.
rendererConfigurations
=
rendererConfigurations
;
this
.
selections
=
new
TrackSelectionArray
(
selections
);
this
.
info
=
info
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSchemeDataSource.java
View file @
f7ed789f
...
...
@@ -19,6 +19,7 @@ import android.net.Uri;
import
android.util.Base64
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.ParserException
;
import
com.google.android.exoplayer2.util.Util
;
import
java.io.IOException
;
import
java.net.URLDecoder
;
...
...
@@ -41,8 +42,8 @@ public final class DataSchemeDataSource implements DataSource {
if
(!
SCHEME_DATA
.
equals
(
scheme
))
{
throw
new
ParserException
(
"Unsupported scheme: "
+
scheme
);
}
String
[]
uriParts
=
uri
.
getSchemeSpecificPart
().
split
(
","
);
if
(
uriParts
.
length
>
2
)
{
String
[]
uriParts
=
Util
.
split
(
uri
.
getSchemeSpecificPart
(),
","
);
if
(
uriParts
.
length
!=
2
)
{
throw
new
ParserException
(
"Unexpected URI format: "
+
uri
);
}
String
dataString
=
uriParts
[
1
];
...
...
library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java
View file @
f7ed789f
...
...
@@ -58,11 +58,6 @@ public final class Loader implements LoaderErrorThrower {
void
cancelLoad
();
/**
* Returns whether the load has been canceled.
*/
boolean
isLoadCanceled
();
/**
* Performs the load, returning on completion or cancellation.
*
* @throws IOException If the input could not be loaded.
...
...
@@ -250,15 +245,17 @@ public final class Loader implements LoaderErrorThrower {
private
static
final
int
MSG_IO_EXCEPTION
=
3
;
private
static
final
int
MSG_FATAL_ERROR
=
4
;
private
final
T
loadable
;
private
final
Loader
.
Callback
<
T
>
callback
;
public
final
int
defaultMinRetryCount
;
private
final
T
loadable
;
private
final
long
startTimeMs
;
private
@Nullable
Loader
.
Callback
<
T
>
callback
;
private
IOException
currentError
;
private
int
errorCount
;
private
volatile
Thread
executorThread
;
private
volatile
boolean
canceled
;
private
volatile
boolean
released
;
public
LoadTask
(
Looper
looper
,
T
loadable
,
Loader
.
Callback
<
T
>
callback
,
...
...
@@ -295,6 +292,7 @@ public final class Loader implements LoaderErrorThrower {
sendEmptyMessage
(
MSG_CANCEL
);
}
}
else
{
canceled
=
true
;
loadable
.
cancelLoad
();
if
(
executorThread
!=
null
)
{
executorThread
.
interrupt
();
...
...
@@ -304,6 +302,11 @@ public final class Loader implements LoaderErrorThrower {
finish
();
long
nowMs
=
SystemClock
.
elapsedRealtime
();
callback
.
onLoadCanceled
(
loadable
,
nowMs
,
nowMs
-
startTimeMs
,
true
);
// If loading, this task will be referenced from a GC root (the loading thread) until
// cancellation completes. The time taken for cancellation to complete depends on the
// implementation of the Loadable that the task is loading. We null the callback reference
// here so that it doesn't prevent garbage collection whilst cancellation is ongoing.
callback
=
null
;
}
}
...
...
@@ -311,7 +314,7 @@ public final class Loader implements LoaderErrorThrower {
public
void
run
()
{
try
{
executorThread
=
Thread
.
currentThread
();
if
(!
loadable
.
isLoadCanceled
()
)
{
if
(!
canceled
)
{
TraceUtil
.
beginSection
(
"load:"
+
loadable
.
getClass
().
getSimpleName
());
try
{
loadable
.
load
();
...
...
@@ -328,7 +331,7 @@ public final class Loader implements LoaderErrorThrower {
}
}
catch
(
InterruptedException
e
)
{
// The load was canceled.
Assertions
.
checkState
(
loadable
.
isLoadCanceled
()
);
Assertions
.
checkState
(
canceled
);
if
(!
released
)
{
sendEmptyMessage
(
MSG_END_OF_SOURCE
);
}
...
...
@@ -373,7 +376,7 @@ public final class Loader implements LoaderErrorThrower {
finish
();
long
nowMs
=
SystemClock
.
elapsedRealtime
();
long
durationMs
=
nowMs
-
startTimeMs
;
if
(
loadable
.
isLoadCanceled
()
)
{
if
(
canceled
)
{
callback
.
onLoadCanceled
(
loadable
,
nowMs
,
durationMs
,
false
);
return
;
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.java
View file @
f7ed789f
...
...
@@ -78,7 +78,6 @@ public final class ParsingLoadable<T> implements Loadable {
private
final
Parser
<?
extends
T
>
parser
;
private
volatile
T
result
;
private
volatile
boolean
isCanceled
;
private
volatile
long
bytesLoaded
;
/**
...
...
@@ -128,14 +127,7 @@ public final class ParsingLoadable<T> implements Loadable {
@Override
public
final
void
cancelLoad
()
{
// We don't actually cancel anything, but we need to record the cancellation so that
// isLoadCanceled can return the correct value.
isCanceled
=
true
;
}
@Override
public
final
boolean
isLoadCanceled
()
{
return
isCanceled
;
// Do nothing.
}
@Override
...
...
library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java
View file @
f7ed789f
...
...
@@ -18,7 +18,6 @@ package com.google.android.exoplayer2.upstream.cache;
import
android.net.Uri
;
import
android.support.annotation.IntDef
;
import
android.support.annotation.Nullable
;
import
android.util.Log
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.upstream.DataSink
;
import
com.google.android.exoplayer2.upstream.DataSource
;
...
...
@@ -52,8 +51,6 @@ public final class CacheDataSource implements DataSource {
*/
public
static
final
long
DEFAULT_MAX_CACHE_FILE_SIZE
=
2
*
1024
*
1024
;
private
static
final
String
TAG
=
"CacheDataSource"
;
/**
* Flags controlling the cache's behavior.
*/
...
...
@@ -221,7 +218,7 @@ public final class CacheDataSource implements DataSource {
try
{
key
=
CacheUtil
.
getKey
(
dataSpec
);
uri
=
dataSpec
.
uri
;
actualUri
=
loadRedirectedUriOrReturnGivenUri
(
cache
,
key
,
uri
);
actualUri
=
getRedirectedUriOrDefault
(
cache
,
key
,
/* defaultUri= */
uri
);
flags
=
dataSpec
.
flags
;
readPosition
=
dataSpec
.
position
;
...
...
@@ -272,7 +269,7 @@ public final class CacheDataSource implements DataSource {
bytesRemaining
-=
bytesRead
;
}
}
else
if
(
currentDataSpecLengthUnset
)
{
set
BytesRemainingAndMaybeStoreLength
(
0
);
set
NoBytesRemainingAndMaybeStoreLength
(
);
}
else
if
(
bytesRemaining
>
0
||
bytesRemaining
==
C
.
LENGTH_UNSET
)
{
closeCurrentSource
();
openNextSource
(
false
);
...
...
@@ -281,7 +278,7 @@ public final class CacheDataSource implements DataSource {
return
bytesRead
;
}
catch
(
IOException
e
)
{
if
(
currentDataSpecLengthUnset
&&
isCausedByPositionOutOfRange
(
e
))
{
set
BytesRemainingAndMaybeStoreLength
(
0
);
set
NoBytesRemainingAndMaybeStoreLength
(
);
return
C
.
RESULT_END_OF_INPUT
;
}
handleBeforeThrow
(
e
);
...
...
@@ -402,46 +399,38 @@ public final class CacheDataSource implements DataSource {
currentDataSource
=
nextDataSource
;
currentDataSpecLengthUnset
=
nextDataSpec
.
length
==
C
.
LENGTH_UNSET
;
long
resolvedLength
=
nextDataSource
.
open
(
nextDataSpec
);
// Update bytesRemaining, actualUri and (if writing to cache) the cache metadata.
ContentMetadataMutations
mutations
=
new
ContentMetadataMutations
();
if
(
currentDataSpecLengthUnset
&&
resolvedLength
!=
C
.
LENGTH_UNSET
)
{
setBytesRemainingAndMaybeStoreLength
(
resolvedLength
);
bytesRemaining
=
resolvedLength
;
ContentMetadataInternal
.
setContentLength
(
mutations
,
readPosition
+
bytesRemaining
);
}
// TODO find a way to store length and redirected uri in one metadata mutation.
maybeUpdateActualUriFieldAndRedirectedUriMetadata
();
}
private
void
maybeUpdateActualUriFieldAndRedirectedUriMetadata
()
{
if
(!
isReadingFromUpstream
())
{
return
;
if
(
isReadingFromUpstream
())
{
actualUri
=
currentDataSource
.
getUri
();
boolean
isRedirected
=
!
uri
.
equals
(
actualUri
);
if
(
isRedirected
)
{
ContentMetadataInternal
.
setRedirectedUri
(
mutations
,
actualUri
);
}
else
{
ContentMetadataInternal
.
removeRedirectedUri
(
mutations
);
}
}
if
(
isWritingToCache
())
{
cache
.
applyContentMetadataMutations
(
key
,
mutations
);
}
actualUri
=
currentDataSource
.
getUri
();
maybeUpdateRedirectedUriMetadata
();
}
private
void
maybeUpdateRedirectedUriMetadata
()
{
if
(!
isWritingToCache
())
{
return
;
}
ContentMetadataMutations
mutations
=
new
ContentMetadataMutations
();
boolean
isRedirected
=
!
uri
.
equals
(
actualUri
);
if
(
isRedirected
)
{
ContentMetadataInternal
.
setRedirectedUri
(
mutations
,
actualUri
);
}
else
{
ContentMetadataInternal
.
removeRedirectedUri
(
mutations
);
}
try
{
cache
.
applyContentMetadataMutations
(
key
,
mutations
);
}
catch
(
CacheException
e
)
{
String
message
=
"Couldn't update redirected URI. "
+
"This might cause relative URIs get resolved incorrectly."
;
Log
.
w
(
TAG
,
message
,
e
);
private
void
setNoBytesRemainingAndMaybeStoreLength
()
throws
IOException
{
bytesRemaining
=
0
;
if
(
isWritingToCache
())
{
cache
.
setContentLength
(
key
,
readPosition
);
}
}
private
static
Uri
loadRedirectedUriOrReturnGivenUri
(
Cache
cache
,
String
key
,
Uri
u
ri
)
{
private
static
Uri
getRedirectedUriOrDefault
(
Cache
cache
,
String
key
,
Uri
defaultU
ri
)
{
ContentMetadata
contentMetadata
=
cache
.
getContentMetadata
(
key
);
Uri
redirectedUri
=
ContentMetadataInternal
.
getRedirectedUri
(
contentMetadata
);
return
redirectedUri
==
null
?
u
ri
:
redirectedUri
;
return
redirectedUri
==
null
?
defaultU
ri
:
redirectedUri
;
}
private
static
boolean
isCausedByPositionOutOfRange
(
IOException
e
)
{
...
...
@@ -458,13 +447,6 @@ public final class CacheDataSource implements DataSource {
return
false
;
}
private
void
setBytesRemainingAndMaybeStoreLength
(
long
bytesRemaining
)
throws
IOException
{
this
.
bytesRemaining
=
bytesRemaining
;
if
(
isWritingToCache
())
{
cache
.
setContentLength
(
key
,
readPosition
+
bytesRemaining
);
}
}
private
boolean
isReadingFromUpstream
()
{
return
!
isReadingFromCache
();
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java
View file @
f7ed789f
...
...
@@ -129,11 +129,11 @@ public final class CacheUtil {
cache
,
new
CacheDataSource
(
cache
,
upstream
),
new
byte
[
DEFAULT_BUFFER_SIZE_BYTES
],
null
,
0
,
/* priorityTaskManager= */
null
,
/* priority= */
0
,
counters
,
null
,
false
);
isCanceled
,
/* enableEOFException= */
false
);
}
/**
...
...
library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/ContentMetadataInternal.java
View file @
f7ed789f
...
...
@@ -20,7 +20,7 @@ import android.support.annotation.Nullable;
import
com.google.android.exoplayer2.C
;
/** Helper classes to easily access and modify internal metadata values. */
/*
package
*/
final
class
ContentMetadataInternal
{
/*
package
*/
final
class
ContentMetadataInternal
{
private
static
final
String
PREFIX
=
ContentMetadata
.
INTERNAL_METADATA_NAME_PREFIX
;
private
static
final
String
METADATA_NAME_REDIRECTED_URI
=
PREFIX
+
"redir"
;
...
...
@@ -59,4 +59,8 @@ import com.google.android.exoplayer2.C;
public
static
void
removeRedirectedUri
(
ContentMetadataMutations
mutations
)
{
mutations
.
remove
(
METADATA_NAME_REDIRECTED_URI
);
}
private
ContentMetadataInternal
()
{
// Prevent instantiation.
}
}
library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipher.java
View file @
f7ed789f
...
...
@@ -16,6 +16,7 @@
package
com
.
google
.
android
.
exoplayer2
.
upstream
.
crypto
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.Util
;
import
java.nio.ByteBuffer
;
import
java.security.InvalidAlgorithmParameterException
;
import
java.security.InvalidKeyException
;
...
...
@@ -49,7 +50,9 @@ public final class AesFlushingCipher {
flushedBlock
=
new
byte
[
blockSize
];
long
counter
=
offset
/
blockSize
;
int
startPadding
=
(
int
)
(
offset
%
blockSize
);
cipher
.
init
(
mode
,
new
SecretKeySpec
(
secretKey
,
cipher
.
getAlgorithm
().
split
(
"/"
)[
0
]),
cipher
.
init
(
mode
,
new
SecretKeySpec
(
secretKey
,
Util
.
splitAtFirst
(
cipher
.
getAlgorithm
(),
"/"
)[
0
]),
new
IvParameterSpec
(
getInitializationVector
(
nonce
,
counter
)));
if
(
startPadding
!=
0
)
{
updateInPlace
(
new
byte
[
startPadding
],
0
,
startPadding
);
...
...
library/core/src/main/java/com/google/android/exoplayer2/util/ColorParser.java
View file @
f7ed789f
...
...
@@ -26,7 +26,7 @@ import java.util.regex.Pattern;
*
* @see <a href="https://w3c.github.io/webvtt/#styling">WebVTT CSS Styling</a>
* @see <a href="https://www.w3.org/TR/ttml2/">Timed Text Markup Language 2 (TTML2) - 10.3.5</a>
*
*
/
*/
public
final
class
ColorParser
{
private
static
final
String
RGB
=
"rgb"
;
...
...
@@ -271,4 +271,7 @@ public final class ColorParser {
COLOR_MAP
.
put
(
"yellowgreen"
,
0xFF9ACD32
);
}
private
ColorParser
()
{
// Prevent instantiation.
}
}
library/core/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java
View file @
f7ed789f
...
...
@@ -111,12 +111,20 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL
GLES20
.
glDeleteTextures
(
1
,
textureIdHolder
,
0
);
}
}
finally
{
if
(
display
!=
null
&&
!
display
.
equals
(
EGL14
.
EGL_NO_DISPLAY
))
{
EGL14
.
eglMakeCurrent
(
display
,
EGL14
.
EGL_NO_SURFACE
,
EGL14
.
EGL_NO_SURFACE
,
EGL14
.
EGL_NO_CONTEXT
);
}
if
(
surface
!=
null
&&
!
surface
.
equals
(
EGL14
.
EGL_NO_SURFACE
))
{
EGL14
.
eglDestroySurface
(
display
,
surface
);
}
if
(
context
!=
null
)
{
EGL14
.
eglDestroyContext
(
display
,
context
);
}
// EGL14.eglReleaseThread could crash before Android K (see [internal: b/11327779]).
if
(
Util
.
SDK_INT
>=
19
)
{
EGL14
.
eglReleaseThread
();
}
display
=
null
;
context
=
null
;
surface
=
null
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java
View file @
f7ed789f
...
...
@@ -18,6 +18,7 @@ package com.google.android.exoplayer2.util;
import
android.support.annotation.Nullable
;
import
android.text.TextUtils
;
import
com.google.android.exoplayer2.C
;
import
java.util.ArrayList
;
/**
* Defines common MIME types and helper methods.
...
...
@@ -92,7 +93,29 @@ public final class MimeTypes {
public
static
final
String
APPLICATION_DVBSUBS
=
BASE_TYPE_APPLICATION
+
"/dvbsubs"
;
public
static
final
String
APPLICATION_EXIF
=
BASE_TYPE_APPLICATION
+
"/x-exif"
;
private
MimeTypes
()
{}
private
static
final
ArrayList
<
CustomMimeType
>
customMimeTypes
=
new
ArrayList
<>();
/**
* Registers a custom MIME type. Most applications do not need to call this method, as handling of
* standard MIME types is built in. These built-in MIME types take precedence over any registered
* via this method. If this method is used, it must be called before creating any player(s).
*
* @param mimeType The custom MIME type to register.
* @param codecPrefix The RFC 6381-style codec string prefix associated with the MIME type.
* @param trackType The {@link C}{@code .TRACK_TYPE_*} constant associated with the MIME type.
* This value is ignored if the top-level type of {@code mimeType} is audio, video or text.
*/
public
static
void
registerCustomMimeType
(
String
mimeType
,
String
codecPrefix
,
int
trackType
)
{
CustomMimeType
customMimeType
=
new
CustomMimeType
(
mimeType
,
codecPrefix
,
trackType
);
int
customMimeTypeCount
=
customMimeTypes
.
size
();
for
(
int
i
=
0
;
i
<
customMimeTypeCount
;
i
++)
{
if
(
mimeType
.
equals
(
customMimeTypes
.
get
(
i
).
mimeType
))
{
customMimeTypes
.
remove
(
i
);
break
;
}
}
customMimeTypes
.
add
(
customMimeType
);
}
/**
* Whether the top-level type of {@code mimeType} is audio.
...
...
@@ -144,7 +167,7 @@ public final class MimeTypes {
if
(
codecs
==
null
)
{
return
null
;
}
String
[]
codecList
=
codecs
.
split
(
","
);
String
[]
codecList
=
Util
.
split
(
codecs
,
","
);
for
(
String
codec
:
codecList
)
{
String
mimeType
=
getMediaMimeType
(
codec
);
if
(
mimeType
!=
null
&&
isVideo
(
mimeType
))
{
...
...
@@ -164,7 +187,7 @@ public final class MimeTypes {
if
(
codecs
==
null
)
{
return
null
;
}
String
[]
codecList
=
codecs
.
split
(
","
);
String
[]
codecList
=
Util
.
split
(
codecs
,
","
);
for
(
String
codec
:
codecList
)
{
String
mimeType
=
getMediaMimeType
(
codec
);
if
(
mimeType
!=
null
&&
isAudio
(
mimeType
))
{
...
...
@@ -222,8 +245,9 @@ public final class MimeTypes {
return
MimeTypes
.
AUDIO_OPUS
;
}
else
if
(
codec
.
startsWith
(
"vorbis"
))
{
return
MimeTypes
.
AUDIO_VORBIS
;
}
else
{
return
getCustomMimeTypeForCodec
(
codec
);
}
return
null
;
}
/**
...
...
@@ -236,18 +260,28 @@ public final class MimeTypes {
@Nullable
public
static
String
getMimeTypeFromMp4ObjectType
(
int
objectType
)
{
switch
(
objectType
)
{
case
0x60
:
case
0x61
:
return
MimeTypes
.
VIDEO_MPEG2
;
case
0x20
:
return
MimeTypes
.
VIDEO_MP4V
;
case
0x21
:
return
MimeTypes
.
VIDEO_H264
;
case
0x23
:
return
MimeTypes
.
VIDEO_H265
;
case
0x60
:
case
0x61
:
case
0x62
:
case
0x63
:
case
0x64
:
case
0x65
:
return
MimeTypes
.
VIDEO_MPEG2
;
case
0x6A
:
return
MimeTypes
.
VIDEO_MPEG
;
case
0x69
:
case
0x6B
:
return
MimeTypes
.
AUDIO_MPEG
;
case
0xA3
:
return
MimeTypes
.
VIDEO_VC1
;
case
0xB1
:
return
MimeTypes
.
VIDEO_VP9
;
case
0x40
:
case
0x66
:
case
0x67
:
...
...
@@ -298,7 +332,7 @@ public final class MimeTypes {
||
APPLICATION_CAMERA_MOTION
.
equals
(
mimeType
))
{
return
C
.
TRACK_TYPE_METADATA
;
}
else
{
return
C
.
TRACK_TYPE_UNKNOWN
;
return
getTrackTypeForCustomMimeType
(
mimeType
)
;
}
}
...
...
@@ -355,4 +389,41 @@ public final class MimeTypes {
return
mimeType
.
substring
(
0
,
indexOfSlash
);
}
private
static
@Nullable
String
getCustomMimeTypeForCodec
(
String
codec
)
{
int
customMimeTypeCount
=
customMimeTypes
.
size
();
for
(
int
i
=
0
;
i
<
customMimeTypeCount
;
i
++)
{
CustomMimeType
customMimeType
=
customMimeTypes
.
get
(
i
);
if
(
codec
.
startsWith
(
customMimeType
.
codecPrefix
))
{
return
customMimeType
.
mimeType
;
}
}
return
null
;
}
private
static
int
getTrackTypeForCustomMimeType
(
String
mimeType
)
{
int
customMimeTypeCount
=
customMimeTypes
.
size
();
for
(
int
i
=
0
;
i
<
customMimeTypeCount
;
i
++)
{
CustomMimeType
customMimeType
=
customMimeTypes
.
get
(
i
);
if
(
mimeType
.
equals
(
customMimeType
.
mimeType
))
{
return
customMimeType
.
trackType
;
}
}
return
C
.
TRACK_TYPE_UNKNOWN
;
}
private
MimeTypes
()
{
// Prevent instantiation.
}
private
static
final
class
CustomMimeType
{
public
final
String
mimeType
;
public
final
String
codecPrefix
;
public
final
int
trackType
;
public
CustomMimeType
(
String
mimeType
,
String
codecPrefix
,
int
trackType
)
{
this
.
mimeType
=
mimeType
;
this
.
codecPrefix
=
codecPrefix
;
this
.
trackType
=
trackType
;
}
}
}
library/core/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java
View file @
f7ed789f
...
...
@@ -175,7 +175,7 @@ public final class ParsableBitArray {
bitOffset
-=
8
;
returnValue
|=
(
data
[
byteOffset
++]
&
0xFF
)
<<
bitOffset
;
}
returnValue
|=
(
data
[
byteOffset
]
&
0xFF
)
>>
8
-
bitOffset
;
returnValue
|=
(
data
[
byteOffset
]
&
0xFF
)
>>
(
8
-
bitOffset
)
;
returnValue
&=
0xFFFFFFFF
>>>
(
32
-
numBits
);
if
(
bitOffset
==
8
)
{
bitOffset
=
0
;
...
...
@@ -199,17 +199,18 @@ public final class ParsableBitArray {
int
to
=
offset
+
(
numBits
>>
3
)
/* numBits / 8 */
;
for
(
int
i
=
offset
;
i
<
to
;
i
++)
{
buffer
[
i
]
=
(
byte
)
(
data
[
byteOffset
++]
<<
bitOffset
);
buffer
[
i
]
|=
(
data
[
byteOffset
]
&
0xFF
)
>>
(
8
-
bitOffset
);
buffer
[
i
]
=
(
byte
)
(
buffer
[
i
]
|
((
data
[
byteOffset
]
&
0xFF
)
>>
(
8
-
bitOffset
))
);
}
// Trailing bits.
int
bitsLeft
=
numBits
&
7
/* numBits % 8 */
;
if
(
bitsLeft
==
0
)
{
return
;
}
buffer
[
to
]
&=
0xFF
>>
bitsLeft
;
// Set to 0 the bits that are going to be overwritten.
// Set bits that are going to be overwritten to 0.
buffer
[
to
]
=
(
byte
)
(
buffer
[
to
]
&
(
0xFF
>>
bitsLeft
));
if
(
bitOffset
+
bitsLeft
>
8
)
{
// We read the rest of data[byteOffset] and increase byteOffset.
buffer
[
to
]
|=
(
byte
)
((
data
[
byteOffset
++]
&
0xFF
)
<<
bitOffset
);
buffer
[
to
]
=
(
byte
)
(
buffer
[
to
]
|
((
data
[
byteOffset
++]
&
0xFF
)
<<
bitOffset
)
);
bitOffset
-=
8
;
}
bitOffset
+=
bitsLeft
;
...
...
@@ -280,9 +281,10 @@ public final class ParsableBitArray {
int
firstByteReadSize
=
Math
.
min
(
8
-
bitOffset
,
numBits
);
int
firstByteRightPaddingSize
=
8
-
bitOffset
-
firstByteReadSize
;
int
firstByteBitmask
=
(
0xFF00
>>
bitOffset
)
|
((
1
<<
firstByteRightPaddingSize
)
-
1
);
data
[
byteOffset
]
&=
firstByteBitmask
;
data
[
byteOffset
]
=
(
byte
)
(
data
[
byteOffset
]
&
firstByteBitmask
)
;
int
firstByteInputBits
=
value
>>>
(
numBits
-
firstByteReadSize
);
data
[
byteOffset
]
|=
firstByteInputBits
<<
firstByteRightPaddingSize
;
data
[
byteOffset
]
=
(
byte
)
(
data
[
byteOffset
]
|
(
firstByteInputBits
<<
firstByteRightPaddingSize
));
remainingBitsToRead
-=
firstByteReadSize
;
int
currentByteIndex
=
byteOffset
+
1
;
while
(
remainingBitsToRead
>
8
)
{
...
...
@@ -290,9 +292,11 @@ public final class ParsableBitArray {
remainingBitsToRead
-=
8
;
}
int
lastByteRightPaddingSize
=
8
-
remainingBitsToRead
;
data
[
currentByteIndex
]
&=
(
1
<<
lastByteRightPaddingSize
)
-
1
;
data
[
currentByteIndex
]
=
(
byte
)
(
data
[
currentByteIndex
]
&
((
1
<<
lastByteRightPaddingSize
)
-
1
));
int
lastByteInput
=
value
&
((
1
<<
remainingBitsToRead
)
-
1
);
data
[
currentByteIndex
]
|=
lastByteInput
<<
lastByteRightPaddingSize
;
data
[
currentByteIndex
]
=
(
byte
)
(
data
[
currentByteIndex
]
|
(
lastByteInput
<<
lastByteRightPaddingSize
));
skipBits
(
numBits
);
assertValidOffset
();
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java
View file @
f7ed789f
...
...
@@ -470,7 +470,7 @@ public final class ParsableByteArray {
if
(
lastIndex
<
limit
&&
data
[
lastIndex
]
==
0
)
{
stringLength
--;
}
String
result
=
new
String
(
data
,
position
,
stringLength
);
String
result
=
Util
.
fromUtf8Bytes
(
data
,
position
,
stringLength
);
position
+=
length
;
return
result
;
}
...
...
@@ -489,7 +489,7 @@ public final class ParsableByteArray {
while
(
stringLimit
<
limit
&&
data
[
stringLimit
]
!=
0
)
{
stringLimit
++;
}
String
string
=
new
String
(
data
,
position
,
stringLimit
-
position
);
String
string
=
Util
.
fromUtf8Bytes
(
data
,
position
,
stringLimit
-
position
);
position
=
stringLimit
;
if
(
position
<
limit
)
{
position
++;
...
...
@@ -520,7 +520,7 @@ public final class ParsableByteArray {
// There's a byte order mark at the start of the line. Discard it.
position
+=
3
;
}
String
line
=
new
String
(
data
,
position
,
lineLimit
-
position
);
String
line
=
Util
.
fromUtf8Bytes
(
data
,
position
,
lineLimit
-
position
);
position
=
lineLimit
;
if
(
position
==
limit
)
{
return
line
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArray.java
View file @
f7ed789f
...
...
@@ -140,7 +140,7 @@ public final class ParsableNalUnitBitArray {
returnValue
|=
(
data
[
byteOffset
]
&
0xFF
)
<<
bitOffset
;
byteOffset
+=
shouldSkipByte
(
byteOffset
+
1
)
?
2
:
1
;
}
returnValue
|=
(
data
[
byteOffset
]
&
0xFF
)
>>
8
-
bitOffset
;
returnValue
|=
(
data
[
byteOffset
]
&
0xFF
)
>>
(
8
-
bitOffset
)
;
returnValue
&=
0xFFFFFFFF
>>>
(
32
-
numBits
);
if
(
bitOffset
==
8
)
{
bitOffset
=
0
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/util/Util.java
View file @
f7ed789f
...
...
@@ -311,10 +311,10 @@ public final class Util {
* Returns a normalized RFC 639-2/T code for {@code language}.
*
* @param language A case-insensitive ISO 639 alpha-2 or alpha-3 language code.
* @return The all-lowercase normalized code, or null if the input was null, or
*
{@code
language.toLowerCase()} if the language could not be normalized.
* @return The all-lowercase normalized code, or null if the input was null, or
{@code
* language.toLowerCase()} if the language could not be normalized.
*/
public
static
String
normalizeLanguageCode
(
String
language
)
{
public
static
@Nullable
String
normalizeLanguageCode
(
@Nullable
String
language
)
{
try
{
return
language
==
null
?
null
:
new
Locale
(
language
).
getISO3Language
();
}
catch
(
MissingResourceException
e
)
{
...
...
@@ -333,6 +333,18 @@ public final class Util {
}
/**
* Returns a new {@link String} constructed by decoding UTF-8 encoded bytes in a subarray.
*
* @param bytes The UTF-8 encoded bytes to decode.
* @param offset The index of the first byte to decode.
* @param length The number of bytes to decode.
* @return The string.
*/
public
static
String
fromUtf8Bytes
(
byte
[]
bytes
,
int
offset
,
int
length
)
{
return
new
String
(
bytes
,
offset
,
length
,
Charset
.
forName
(
C
.
UTF8_NAME
));
}
/**
* Returns a new byte array containing the code points of a {@link String} encoded using UTF-8.
*
* @param value The {@link String} whose bytes should be obtained.
...
...
@@ -343,6 +355,33 @@ public final class Util {
}
/**
* Splits a string using {@code value.split(regex, -1}). Note: this is is similar to {@link
* String#split(String)} but empty matches at the end of the string will not be omitted from the
* returned array.
*
* @param value The string to split.
* @param regex A delimiting regular expression.
* @return The array of strings resulting from splitting the string.
*/
public
static
String
[]
split
(
String
value
,
String
regex
)
{
return
value
.
split
(
regex
,
/* limit= */
-
1
);
}
/**
* Splits the string at the first occurrence of the delimiter {@code regex}. If the delimiter does
* not match, returns an array with one element which is the input string. If the delimiter does
* match, returns an array with the portion of the string before the delimiter and the rest of the
* string.
*
* @param value The string.
* @param regex A delimiting regular expression.
* @return The string split by the first occurrence of the delimiter.
*/
public
static
String
[]
splitAtFirst
(
String
value
,
String
regex
)
{
return
value
.
split
(
regex
,
/* limit= */
2
);
}
/**
* Returns whether the given character is a carriage return ('\r') or a line feed ('\n').
*
* @param c The character.
...
...
@@ -978,7 +1017,7 @@ public final class Util {
if
(
TextUtils
.
isEmpty
(
codecs
))
{
return
null
;
}
String
[]
codecArray
=
codecs
.
trim
().
split
(
"(\\s*,\\s*)"
);
String
[]
codecArray
=
split
(
codecs
.
trim
(),
"(\\s*,\\s*)"
);
StringBuilder
builder
=
new
StringBuilder
();
for
(
String
codec
:
codecArray
)
{
if
(
trackType
==
MimeTypes
.
getTrackTypeOfCodec
(
codec
))
{
...
...
@@ -1454,7 +1493,7 @@ public final class Util {
// If we managed to read sys.display-size, attempt to parse it.
if
(!
TextUtils
.
isEmpty
(
sysDisplaySize
))
{
try
{
String
[]
sysDisplaySizeParts
=
s
ysDisplaySize
.
trim
().
split
(
"x"
);
String
[]
sysDisplaySizeParts
=
s
plit
(
sysDisplaySize
.
trim
(),
"x"
);
if
(
sysDisplaySizeParts
.
length
==
2
)
{
int
width
=
Integer
.
parseInt
(
sysDisplaySizeParts
[
0
]);
int
height
=
Integer
.
parseInt
(
sysDisplaySizeParts
[
1
]);
...
...
library/core/src/main/java/com/google/android/exoplayer2/video/DummySurface.java
View file @
f7ed789f
...
...
@@ -156,7 +156,7 @@ public final class DummySurface extends Surface {
private
static
final
int
MSG_INIT
=
1
;
private
static
final
int
MSG_RELEASE
=
2
;
private
@MonotonicNonNull
EGLSurfaceTexture
eglSurfaceTexure
;
private
@MonotonicNonNull
EGLSurfaceTexture
eglSurfaceTex
t
ure
;
private
@MonotonicNonNull
Handler
handler
;
private
@Nullable
Error
initError
;
private
@Nullable
RuntimeException
initException
;
...
...
@@ -169,7 +169,7 @@ public final class DummySurface extends Surface {
public
DummySurface
init
(
@SecureMode
int
secureMode
)
{
start
();
handler
=
new
Handler
(
getLooper
(),
/* callback= */
this
);
eglSurfaceTexure
=
new
EGLSurfaceTexture
(
handler
);
eglSurfaceTex
t
ure
=
new
EGLSurfaceTexture
(
handler
);
boolean
wasInterrupted
=
false
;
synchronized
(
this
)
{
handler
.
obtainMessage
(
MSG_INIT
,
secureMode
,
0
).
sendToTarget
();
...
...
@@ -232,16 +232,16 @@ public final class DummySurface extends Surface {
}
private
void
initInternal
(
@SecureMode
int
secureMode
)
{
Assertions
.
checkNotNull
(
eglSurfaceTexure
);
eglSurfaceTexure
.
init
(
secureMode
);
Assertions
.
checkNotNull
(
eglSurfaceTex
t
ure
);
eglSurfaceTex
t
ure
.
init
(
secureMode
);
this
.
surface
=
new
DummySurface
(
this
,
eglSurfaceTexure
.
getSurfaceTexture
(),
secureMode
!=
SECURE_MODE_NONE
);
this
,
eglSurfaceTex
t
ure
.
getSurfaceTexture
(),
secureMode
!=
SECURE_MODE_NONE
);
}
private
void
releaseInternal
()
{
Assertions
.
checkNotNull
(
eglSurfaceTexure
);
eglSurfaceTexure
.
release
();
Assertions
.
checkNotNull
(
eglSurfaceTex
t
ure
);
eglSurfaceTex
t
ure
.
release
();
}
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java
View file @
f7ed789f
...
...
@@ -205,7 +205,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
this
.
allowedJoiningTimeMs
=
allowedJoiningTimeMs
;
this
.
maxDroppedFramesToNotify
=
maxDroppedFramesToNotify
;
this
.
context
=
context
.
getApplicationContext
();
frameReleaseTimeHelper
=
new
VideoFrameReleaseTimeHelper
(
context
);
frameReleaseTimeHelper
=
new
VideoFrameReleaseTimeHelper
(
this
.
context
);
eventDispatcher
=
new
EventDispatcher
(
eventHandler
,
eventListener
);
deviceNeedsAutoFrcWorkaround
=
deviceNeedsAutoFrcWorkaround
();
pendingOutputStreamOffsetsUs
=
new
long
[
MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT
];
...
...
@@ -1177,8 +1177,9 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
// https://github.com/google/ExoPlayer/issues/3835,
// https://github.com/google/ExoPlayer/issues/4006,
// https://github.com/google/ExoPlayer/issues/4084,
// https://github.com/google/ExoPlayer/issues/4104.
// https://github.com/google/ExoPlayer/issues/4134.
// https://github.com/google/ExoPlayer/issues/4104,
// https://github.com/google/ExoPlayer/issues/4134,
// https://github.com/google/ExoPlayer/issues/4315.
return
((
"deb"
.
equals
(
Util
.
DEVICE
)
// Nexus 7 (2013)
||
"flo"
.
equals
(
Util
.
DEVICE
)
// Nexus 7 (2013)
||
"mido"
.
equals
(
Util
.
DEVICE
)
// Redmi Note 4
...
...
@@ -1192,7 +1193,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
||
"M5c"
.
equals
(
Util
.
DEVICE
)
// Meizu M5C
||
"QM16XE_U"
.
equals
(
Util
.
DEVICE
)
// Philips QM163E
||
"A7010a48"
.
equals
(
Util
.
DEVICE
)
// Lenovo K4 Note
||
"woods_f"
.
equals
(
Util
.
MODEL
))
// Moto E (4)
||
"woods_f"
.
equals
(
Util
.
MODEL
)
// Moto E (4)
||
"watson"
.
equals
(
Util
.
DEVICE
))
// Moto C
&&
"OMX.MTK.VIDEO.DECODER.AVC"
.
equals
(
name
))
||
((
"ALE-L21"
.
equals
(
Util
.
MODEL
)
// Huawei P8 Lite
||
"CAM-L21"
.
equals
(
Util
.
MODEL
))
// Huawei Y6II
...
...
library/core/src/main/java/com/google/android/exoplayer2/video/VideoFrameReleaseTimeHelper.java
View file @
f7ed789f
...
...
@@ -72,8 +72,12 @@ public final class VideoFrameReleaseTimeHelper {
* @param context A context from which information about the default display can be retrieved.
*/
public
VideoFrameReleaseTimeHelper
(
@Nullable
Context
context
)
{
windowManager
=
context
==
null
?
null
:
(
WindowManager
)
context
.
getSystemService
(
Context
.
WINDOW_SERVICE
);
if
(
context
!=
null
)
{
context
=
context
.
getApplicationContext
();
windowManager
=
(
WindowManager
)
context
.
getSystemService
(
Context
.
WINDOW_SERVICE
);
}
else
{
windowManager
=
null
;
}
if
(
windowManager
!=
null
)
{
displayListener
=
Util
.
SDK_INT
>=
17
?
maybeBuildDefaultDisplayListenerV17
(
context
)
:
null
;
vsyncSampler
=
VSyncSampler
.
getInstance
();
...
...
library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java
View file @
f7ed789f
...
...
@@ -1980,6 +1980,105 @@ public final class ExoPlayerTest {
.
inOrder
();
}
@Test
public
void
testRecursivePlayerChangesReportConsistentValuesForAllListeners
()
throws
Exception
{
// We add two listeners to the player. The first stops the player as soon as it's ready and both
// record the state change events they receive.
final
AtomicReference
<
Player
>
playerReference
=
new
AtomicReference
<>();
final
List
<
Integer
>
eventListener1States
=
new
ArrayList
<>();
final
List
<
Integer
>
eventListener2States
=
new
ArrayList
<>();
final
EventListener
eventListener1
=
new
DefaultEventListener
()
{
@Override
public
void
onPlayerStateChanged
(
boolean
playWhenReady
,
int
playbackState
)
{
eventListener1States
.
add
(
playbackState
);
if
(
playbackState
==
Player
.
STATE_READY
)
{
playerReference
.
get
().
stop
(
/* reset= */
true
);
}
}
};
final
EventListener
eventListener2
=
new
DefaultEventListener
()
{
@Override
public
void
onPlayerStateChanged
(
boolean
playWhenReady
,
int
playbackState
)
{
eventListener2States
.
add
(
playbackState
);
}
};
ActionSchedule
actionSchedule
=
new
ActionSchedule
.
Builder
(
"testRecursivePlayerChanges"
)
.
executeRunnable
(
new
PlayerRunnable
()
{
@Override
public
void
run
(
SimpleExoPlayer
player
)
{
playerReference
.
set
(
player
);
player
.
addListener
(
eventListener1
);
player
.
addListener
(
eventListener2
);
}
})
.
build
();
new
ExoPlayerTestRunner
.
Builder
()
.
setActionSchedule
(
actionSchedule
)
.
build
()
.
start
()
.
blockUntilEnded
(
TIMEOUT_MS
);
assertThat
(
eventListener1States
)
.
containsExactly
(
Player
.
STATE_BUFFERING
,
Player
.
STATE_READY
,
Player
.
STATE_IDLE
)
.
inOrder
();
assertThat
(
eventListener2States
)
.
containsExactly
(
Player
.
STATE_BUFFERING
,
Player
.
STATE_READY
,
Player
.
STATE_IDLE
)
.
inOrder
();
}
@Test
public
void
testRecursivePlayerChangesAreReportedInCorrectOrder
()
throws
Exception
{
// The listener stops the player as soon as it's ready (which should report a timeline and state
// change) and sets playWhenReady to false when the timeline callback is received.
final
AtomicReference
<
Player
>
playerReference
=
new
AtomicReference
<>();
final
List
<
Boolean
>
eventListenerPlayWhenReady
=
new
ArrayList
<>();
final
List
<
Integer
>
eventListenerStates
=
new
ArrayList
<>();
final
EventListener
eventListener
=
new
DefaultEventListener
()
{
@Override
public
void
onTimelineChanged
(
Timeline
timeline
,
Object
manifest
,
int
reason
)
{
if
(
timeline
.
isEmpty
())
{
playerReference
.
get
().
setPlayWhenReady
(
/* playWhenReady= */
false
);
}
}
@Override
public
void
onPlayerStateChanged
(
boolean
playWhenReady
,
int
playbackState
)
{
eventListenerPlayWhenReady
.
add
(
playWhenReady
);
eventListenerStates
.
add
(
playbackState
);
if
(
playbackState
==
Player
.
STATE_READY
)
{
playerReference
.
get
().
stop
(
/* reset= */
true
);
}
}
};
ActionSchedule
actionSchedule
=
new
ActionSchedule
.
Builder
(
"testRecursivePlayerChanges"
)
.
executeRunnable
(
new
PlayerRunnable
()
{
@Override
public
void
run
(
SimpleExoPlayer
player
)
{
playerReference
.
set
(
player
);
player
.
addListener
(
eventListener
);
}
})
.
build
();
new
ExoPlayerTestRunner
.
Builder
()
.
setActionSchedule
(
actionSchedule
)
.
build
()
.
start
()
.
blockUntilEnded
(
TIMEOUT_MS
);
assertThat
(
eventListenerStates
)
.
containsExactly
(
Player
.
STATE_BUFFERING
,
Player
.
STATE_READY
,
Player
.
STATE_IDLE
,
Player
.
STATE_IDLE
)
.
inOrder
();
assertThat
(
eventListenerPlayWhenReady
).
containsExactly
(
true
,
true
,
true
,
false
).
inOrder
();
}
// Internal methods.
private
static
ActionSchedule
.
Builder
addSurfaceSwitch
(
ActionSchedule
.
Builder
builder
)
{
...
...
library/core/src/test/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelectionTest.java
View file @
f7ed789f
...
...
@@ -392,11 +392,6 @@ public final class AdaptiveTrackSelectionTest {
}
@Override
public
boolean
isLoadCanceled
()
{
return
false
;
}
@Override
public
void
load
()
throws
IOException
,
InterruptedException
{
// Do nothing.
}
...
...
library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java
View file @
f7ed789f
...
...
@@ -16,6 +16,7 @@
package
com
.
google
.
android
.
exoplayer2
.
source
.
dash
;
import
android.support.annotation.IntDef
;
import
android.support.annotation.Nullable
;
import
android.util.Pair
;
import
android.util.SparseArray
;
import
android.util.SparseIntArray
;
...
...
@@ -72,7 +73,7 @@ import java.util.List;
private
final
IdentityHashMap
<
ChunkSampleStream
<
DashChunkSource
>,
PlayerTrackEmsgHandler
>
trackEmsgHandlerBySampleStream
;
private
Callback
callback
;
private
@Nullable
Callback
callback
;
private
ChunkSampleStream
<
DashChunkSource
>[]
sampleStreams
;
private
EventSampleStream
[]
eventSampleStreams
;
private
SequenceableLoader
compositeSequenceableLoader
;
...
...
@@ -150,6 +151,7 @@ import java.util.List;
for
(
ChunkSampleStream
<
DashChunkSource
>
sampleStream
:
sampleStreams
)
{
sampleStream
.
release
(
this
);
}
callback
=
null
;
eventDispatcher
.
mediaPeriodReleased
();
}
...
...
library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java
View file @
f7ed789f
...
...
@@ -25,12 +25,15 @@ import com.google.android.exoplayer2.source.dash.manifest.RangedUri;
public
final
class
DashWrappingSegmentIndex
implements
DashSegmentIndex
{
private
final
ChunkIndex
chunkIndex
;
private
final
long
timeOffsetUs
;
/**
* @param chunkIndex The {@link ChunkIndex} to wrap.
* @param timeOffsetUs An offset to subtract from the times in the wrapped index, in microseconds.
*/
public
DashWrappingSegmentIndex
(
ChunkIndex
chunkIndex
)
{
public
DashWrappingSegmentIndex
(
ChunkIndex
chunkIndex
,
long
timeOffsetUs
)
{
this
.
chunkIndex
=
chunkIndex
;
this
.
timeOffsetUs
=
timeOffsetUs
;
}
@Override
...
...
@@ -45,7 +48,7 @@ public final class DashWrappingSegmentIndex implements DashSegmentIndex {
@Override
public
long
getTimeUs
(
long
segmentNum
)
{
return
chunkIndex
.
timesUs
[(
int
)
segmentNum
];
return
chunkIndex
.
timesUs
[(
int
)
segmentNum
]
-
timeOffsetUs
;
}
@Override
...
...
@@ -61,7 +64,7 @@ public final class DashWrappingSegmentIndex implements DashSegmentIndex {
@Override
public
long
getSegmentNum
(
long
timeUs
,
long
periodDurationUs
)
{
return
chunkIndex
.
getChunkIndex
(
timeUs
);
return
chunkIndex
.
getChunkIndex
(
timeUs
+
timeOffsetUs
);
}
@Override
...
...
library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java
View file @
f7ed789f
...
...
@@ -354,7 +354,10 @@ public class DefaultDashChunkSource implements DashChunkSource {
if
(
representationHolder
.
segmentIndex
==
null
)
{
SeekMap
seekMap
=
representationHolder
.
extractorWrapper
.
getSeekMap
();
if
(
seekMap
!=
null
)
{
representationHolder
.
segmentIndex
=
new
DashWrappingSegmentIndex
((
ChunkIndex
)
seekMap
);
representationHolder
.
segmentIndex
=
new
DashWrappingSegmentIndex
(
(
ChunkIndex
)
seekMap
,
representationHolder
.
representation
.
presentationTimeOffsetUs
);
}
}
}
...
...
library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java
View file @
f7ed789f
...
...
@@ -167,7 +167,9 @@ public final class DashDownloader extends SegmentDownloader<DashManifest, Repres
return
index
;
}
ChunkIndex
seekMap
=
DashUtil
.
loadChunkIndex
(
dataSource
,
trackType
,
representation
);
return
seekMap
==
null
?
null
:
new
DashWrappingSegmentIndex
(
seekMap
);
return
seekMap
==
null
?
null
:
new
DashWrappingSegmentIndex
(
seekMap
,
representation
.
presentationTimeOffsetUs
);
}
}
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java
View file @
f7ed789f
...
...
@@ -104,7 +104,7 @@ import java.util.List;
// the way in which HlsSampleStreamWrapper generates track groups. Use only index based methods
// in TrackSelection to avoid unexpected behavior.
private
TrackSelection
trackSelection
;
private
long
liveEdgeTimeUs
;
private
long
liveEdge
InPeriod
TimeUs
;
private
boolean
seenExpectedPlaylistError
;
/**
...
...
@@ -128,7 +128,7 @@ import java.util.List;
this
.
variants
=
variants
;
this
.
timestampAdjusterProvider
=
timestampAdjusterProvider
;
this
.
muxedCaptionFormats
=
muxedCaptionFormats
;
liveEdgeTimeUs
=
C
.
TIME_UNSET
;
liveEdge
InPeriod
TimeUs
=
C
.
TIME_UNSET
;
Format
[]
variantFormats
=
new
Format
[
variants
.
length
];
int
[]
initialTrackSelection
=
new
int
[
variants
.
length
];
for
(
int
i
=
0
;
i
<
variants
.
length
;
i
++)
{
...
...
@@ -254,16 +254,17 @@ import java.util.List;
// Select the chunk.
long
chunkMediaSequence
;
long
startOfPlaylistInPeriodUs
=
mediaPlaylist
.
startTimeUs
-
playlistTracker
.
getInitialStartTimeUs
();
if
(
previous
==
null
||
switchingVariant
)
{
long
targetPositionUs
=
(
previous
==
null
||
independentSegments
)
?
loadPositionUs
:
previous
.
startTimeUs
;
if
(!
mediaPlaylist
.
hasEndTag
&&
targetPositionUs
>=
mediaPlaylist
.
getEndTimeUs
())
{
long
endOfPlaylistInPeriodUs
=
startOfPlaylistInPeriodUs
+
mediaPlaylist
.
durationUs
;
long
targetPositionInPeriodUs
=
(
previous
==
null
||
independentSegments
)
?
loadPositionUs
:
previous
.
startTimeUs
;
if
(!
mediaPlaylist
.
hasEndTag
&&
targetPositionInPeriodUs
>=
endOfPlaylistInPeriodUs
)
{
// If the playlist is too old to contain the chunk, we need to refresh it.
chunkMediaSequence
=
mediaPlaylist
.
mediaSequence
+
mediaPlaylist
.
segments
.
size
();
}
else
{
long
positionOfPlaylistInPeriodUs
=
mediaPlaylist
.
startTimeUs
-
playlistTracker
.
getInitialStartTimeUs
();
long
targetPositionInPlaylistUs
=
targetPositionUs
-
positionOfPlaylistInPeriodUs
;
long
targetPositionInPlaylistUs
=
targetPositionInPeriodUs
-
startOfPlaylistInPeriodUs
;
chunkMediaSequence
=
Util
.
binarySearchFloor
(
mediaPlaylist
.
segments
,
...
...
@@ -277,6 +278,8 @@ import java.util.List;
selectedVariantIndex
=
oldVariantIndex
;
selectedUrl
=
variants
[
selectedVariantIndex
];
mediaPlaylist
=
playlistTracker
.
getPlaylistSnapshot
(
selectedUrl
);
startOfPlaylistInPeriodUs
=
mediaPlaylist
.
startTimeUs
-
playlistTracker
.
getInitialStartTimeUs
();
chunkMediaSequence
=
previous
.
getNextChunkIndex
();
}
}
...
...
@@ -331,9 +334,7 @@ import java.util.List;
}
// Compute start time of the next chunk.
long
positionOfPlaylistInPeriodUs
=
mediaPlaylist
.
startTimeUs
-
playlistTracker
.
getInitialStartTimeUs
();
long
segmentStartTimeInPeriodUs
=
positionOfPlaylistInPeriodUs
+
segment
.
relativeStartTimeUs
;
long
segmentStartTimeInPeriodUs
=
startOfPlaylistInPeriodUs
+
segment
.
relativeStartTimeUs
;
int
discontinuitySequence
=
mediaPlaylist
.
discontinuitySequence
+
segment
.
relativeDiscontinuitySequence
;
TimestampAdjuster
timestampAdjuster
=
timestampAdjusterProvider
.
getAdjuster
(
...
...
@@ -420,12 +421,17 @@ import java.util.List;
// Private methods.
private
long
resolveTimeToLiveEdgeUs
(
long
playbackPositionUs
)
{
final
boolean
resolveTimeToLiveEdgePossible
=
liveEdgeTimeUs
!=
C
.
TIME_UNSET
;
return
resolveTimeToLiveEdgePossible
?
liveEdgeTimeUs
-
playbackPositionUs
:
C
.
TIME_UNSET
;
final
boolean
resolveTimeToLiveEdgePossible
=
liveEdgeInPeriodTimeUs
!=
C
.
TIME_UNSET
;
return
resolveTimeToLiveEdgePossible
?
liveEdgeInPeriodTimeUs
-
playbackPositionUs
:
C
.
TIME_UNSET
;
}
private
void
updateLiveEdgeTimeUs
(
HlsMediaPlaylist
mediaPlaylist
)
{
liveEdgeTimeUs
=
mediaPlaylist
.
hasEndTag
?
C
.
TIME_UNSET
:
mediaPlaylist
.
getEndTimeUs
();
liveEdgeInPeriodTimeUs
=
mediaPlaylist
.
hasEndTag
?
C
.
TIME_UNSET
:
(
mediaPlaylist
.
getEndTimeUs
()
-
playlistTracker
.
getInitialStartTimeUs
());
}
private
EncryptionKeyChunk
newEncryptionKeyChunk
(
Uri
keyUri
,
String
iv
,
int
variantIndex
,
...
...
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java
View file @
f7ed789f
...
...
@@ -207,11 +207,6 @@ import java.util.concurrent.atomic.AtomicInteger;
}
@Override
public
boolean
isLoadCanceled
()
{
return
loadCanceled
;
}
@Override
public
void
load
()
throws
IOException
,
InterruptedException
{
maybeLoadInitData
();
if
(!
loadCanceled
)
{
...
...
@@ -242,7 +237,7 @@ import java.util.concurrent.atomic.AtomicInteger;
initSegmentBytesLoaded
=
(
int
)
(
input
.
getPosition
()
-
initDataSpec
.
absoluteStreamPosition
);
}
}
finally
{
Util
.
closeQuietly
(
d
ataSource
);
Util
.
closeQuietly
(
initD
ataSource
);
}
initLoadCompleted
=
true
;
}
...
...
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java
View file @
f7ed789f
...
...
@@ -15,6 +15,7 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
source
.
hls
;
import
android.support.annotation.Nullable
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.Format
;
import
com.google.android.exoplayer2.SeekParameters
;
...
...
@@ -57,7 +58,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
private
final
CompositeSequenceableLoaderFactory
compositeSequenceableLoaderFactory
;
private
final
boolean
allowChunklessPreparation
;
private
Callback
callback
;
private
@Nullable
Callback
callback
;
private
int
pendingPrepareCount
;
private
TrackGroupArray
trackGroups
;
private
HlsSampleStreamWrapper
[]
sampleStreamWrappers
;
...
...
@@ -96,6 +97,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
for
(
HlsSampleStreamWrapper
sampleStreamWrapper
:
sampleStreamWrappers
)
{
sampleStreamWrapper
.
release
();
}
callback
=
null
;
eventDispatcher
.
mediaPeriodReleased
();
}
...
...
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java
View file @
f7ed789f
...
...
@@ -32,6 +32,7 @@ import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispat
import
com.google.android.exoplayer2.source.SequenceableLoader
;
import
com.google.android.exoplayer2.source.SinglePeriodTimeline
;
import
com.google.android.exoplayer2.source.ads.AdsMediaSource
;
import
com.google.android.exoplayer2.source.hls.playlist.DefaultHlsPlaylistTracker
;
import
com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist
;
import
com.google.android.exoplayer2.source.hls.playlist.HlsPlaylist
;
import
com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser
;
...
...
@@ -58,6 +59,7 @@ public final class HlsMediaSource extends BaseMediaSource
private
HlsExtractorFactory
extractorFactory
;
private
@Nullable
ParsingLoadable
.
Parser
<
HlsPlaylist
>
playlistParser
;
private
@Nullable
HlsPlaylistTracker
playlistTracker
;
private
CompositeSequenceableLoaderFactory
compositeSequenceableLoaderFactory
;
private
int
minLoadableRetryCount
;
private
boolean
allowChunklessPreparation
;
...
...
@@ -136,17 +138,38 @@ public final class HlsMediaSource extends BaseMediaSource
* Sets the parser to parse HLS playlists. The default is an instance of {@link
* HlsPlaylistParser}.
*
* <p>Must not be called after calling {@link #setPlaylistTracker} on the same builder.
*
* @param playlistParser A {@link ParsingLoadable.Parser} for HLS playlists.
* @return This factory, for convenience.
* @throws IllegalStateException If one of the {@code create} methods has already been called.
*/
public
Factory
setPlaylistParser
(
ParsingLoadable
.
Parser
<
HlsPlaylist
>
playlistParser
)
{
Assertions
.
checkState
(!
isCreateCalled
);
Assertions
.
checkState
(
playlistTracker
==
null
,
"A playlist tracker has already been set."
);
this
.
playlistParser
=
Assertions
.
checkNotNull
(
playlistParser
);
return
this
;
}
/**
* Sets the HLS playlist tracker. The default is an instance of {@link
* DefaultHlsPlaylistTracker}. Playlist trackers must not be shared by {@link HlsMediaSource}
* instances.
*
* <p>Must not be called after calling {@link #setPlaylistParser} on the same builder.
*
* @param playlistTracker A tracker for HLS playlists.
* @return This factory, for convenience.
* @throws IllegalStateException If one of the {@code create} methods has already been called.
*/
public
Factory
setPlaylistTracker
(
HlsPlaylistTracker
playlistTracker
)
{
Assertions
.
checkState
(!
isCreateCalled
);
Assertions
.
checkState
(
playlistParser
==
null
,
"A playlist parser has already been set."
);
this
.
playlistTracker
=
Assertions
.
checkNotNull
(
playlistTracker
);
return
this
;
}
/**
* Sets the factory to create composite {@link SequenceableLoader}s for when this media source
* loads data from multiple streams (video, audio etc...). The default is an instance of {@link
* DefaultCompositeSequenceableLoaderFactory}.
...
...
@@ -187,8 +210,12 @@ public final class HlsMediaSource extends BaseMediaSource
@Override
public
HlsMediaSource
createMediaSource
(
Uri
playlistUri
)
{
isCreateCalled
=
true
;
if
(
playlistParser
==
null
)
{
playlistParser
=
new
HlsPlaylistParser
();
if
(
playlistTracker
==
null
)
{
playlistTracker
=
new
DefaultHlsPlaylistTracker
(
hlsDataSourceFactory
,
minLoadableRetryCount
,
playlistParser
!=
null
?
playlistParser
:
new
HlsPlaylistParser
());
}
return
new
HlsMediaSource
(
playlistUri
,
...
...
@@ -196,7 +223,7 @@ public final class HlsMediaSource extends BaseMediaSource
extractorFactory
,
compositeSequenceableLoaderFactory
,
minLoadableRetryCount
,
playlist
Pars
er
,
playlist
Track
er
,
allowChunklessPreparation
,
tag
);
}
...
...
@@ -233,12 +260,10 @@ public final class HlsMediaSource extends BaseMediaSource
private
final
HlsDataSourceFactory
dataSourceFactory
;
private
final
CompositeSequenceableLoaderFactory
compositeSequenceableLoaderFactory
;
private
final
int
minLoadableRetryCount
;
private
final
ParsingLoadable
.
Parser
<
HlsPlaylist
>
playlistParser
;
private
final
boolean
allowChunklessPreparation
;
private
final
HlsPlaylistTracker
playlistTracker
;
private
final
@Nullable
Object
tag
;
private
HlsPlaylistTracker
playlistTracker
;
/**
* @param manifestUri The {@link Uri} of the HLS manifest.
* @param dataSourceFactory An {@link HlsDataSourceFactory} for {@link DataSource}s for manifests,
...
...
@@ -276,8 +301,13 @@ public final class HlsMediaSource extends BaseMediaSource
int
minLoadableRetryCount
,
Handler
eventHandler
,
MediaSourceEventListener
eventListener
)
{
this
(
manifestUri
,
new
DefaultHlsDataSourceFactory
(
dataSourceFactory
),
HlsExtractorFactory
.
DEFAULT
,
minLoadableRetryCount
,
eventHandler
,
eventListener
,
this
(
manifestUri
,
new
DefaultHlsDataSourceFactory
(
dataSourceFactory
),
HlsExtractorFactory
.
DEFAULT
,
minLoadableRetryCount
,
eventHandler
,
eventListener
,
new
HlsPlaylistParser
());
}
...
...
@@ -309,7 +339,8 @@ public final class HlsMediaSource extends BaseMediaSource
extractorFactory
,
new
DefaultCompositeSequenceableLoaderFactory
(),
minLoadableRetryCount
,
playlistParser
,
new
DefaultHlsPlaylistTracker
(
dataSourceFactory
,
minLoadableRetryCount
,
new
HlsPlaylistParser
()),
/* allowChunklessPreparation= */
false
,
/* tag= */
null
);
if
(
eventHandler
!=
null
&&
eventListener
!=
null
)
{
...
...
@@ -323,7 +354,7 @@ public final class HlsMediaSource extends BaseMediaSource
HlsExtractorFactory
extractorFactory
,
CompositeSequenceableLoaderFactory
compositeSequenceableLoaderFactory
,
int
minLoadableRetryCount
,
ParsingLoadable
.
Parser
<
HlsPlaylist
>
playlistPars
er
,
HlsPlaylistTracker
playlistTrack
er
,
boolean
allowChunklessPreparation
,
@Nullable
Object
tag
)
{
this
.
manifestUri
=
manifestUri
;
...
...
@@ -331,7 +362,7 @@ public final class HlsMediaSource extends BaseMediaSource
this
.
extractorFactory
=
extractorFactory
;
this
.
compositeSequenceableLoaderFactory
=
compositeSequenceableLoaderFactory
;
this
.
minLoadableRetryCount
=
minLoadableRetryCount
;
this
.
playlist
Parser
=
playlistPars
er
;
this
.
playlist
Tracker
=
playlistTrack
er
;
this
.
allowChunklessPreparation
=
allowChunklessPreparation
;
this
.
tag
=
tag
;
}
...
...
@@ -339,9 +370,7 @@ public final class HlsMediaSource extends BaseMediaSource
@Override
public
void
prepareSourceInternal
(
ExoPlayer
player
,
boolean
isTopLevelSource
)
{
EventDispatcher
eventDispatcher
=
createEventDispatcher
(
/* mediaPeriodId= */
null
);
playlistTracker
=
new
HlsPlaylistTracker
(
manifestUri
,
dataSourceFactory
,
eventDispatcher
,
minLoadableRetryCount
,
this
,
playlistParser
);
playlistTracker
.
start
();
playlistTracker
.
start
(
manifestUri
,
eventDispatcher
,
/* listener= */
this
);
}
@Override
...
...
@@ -373,7 +402,6 @@ public final class HlsMediaSource extends BaseMediaSource
public
void
releaseSourceInternal
()
{
if
(
playlistTracker
!=
null
)
{
playlistTracker
.
release
();
playlistTracker
=
null
;
}
}
...
...
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloadHelper.java
View file @
f7ed789f
...
...
@@ -73,6 +73,7 @@ public final class HlsDownloadHelper extends DownloadHelper {
public
TrackGroupArray
getTrackGroups
(
int
periodIndex
)
{
Assertions
.
checkNotNull
(
playlist
);
if
(
playlist
instanceof
HlsMediaPlaylist
)
{
renditionTypes
=
new
int
[
0
];
return
TrackGroupArray
.
EMPTY
;
}
// TODO: Generate track groups as in playback. Reverse the mapping in getDownloadAction.
...
...
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java
0 → 100644
View file @
f7ed789f
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
.
source
.
hls
.
playlist
;
import
android.net.Uri
;
import
android.os.Handler
;
import
android.os.SystemClock
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.ParserException
;
import
com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher
;
import
com.google.android.exoplayer2.source.chunk.ChunkedTrackBlacklistUtil
;
import
com.google.android.exoplayer2.source.hls.HlsDataSourceFactory
;
import
com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl
;
import
com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment
;
import
com.google.android.exoplayer2.upstream.DataSource
;
import
com.google.android.exoplayer2.upstream.Loader
;
import
com.google.android.exoplayer2.upstream.ParsingLoadable
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.UriUtil
;
import
java.io.IOException
;
import
java.util.ArrayList
;
import
java.util.IdentityHashMap
;
import
java.util.List
;
/** Default implementation for {@link HlsPlaylistTracker}. */
public
final
class
DefaultHlsPlaylistTracker
implements
HlsPlaylistTracker
,
Loader
.
Callback
<
ParsingLoadable
<
HlsPlaylist
>>
{
/**
* Coefficient applied on the target duration of a playlist to determine the amount of time after
* which an unchanging playlist is considered stuck.
*/
private
static
final
double
PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT
=
3.5
;
private
final
HlsDataSourceFactory
dataSourceFactory
;
private
final
ParsingLoadable
.
Parser
<
HlsPlaylist
>
playlistParser
;
private
final
int
minRetryCount
;
private
final
IdentityHashMap
<
HlsUrl
,
MediaPlaylistBundle
>
playlistBundles
;
private
final
List
<
PlaylistEventListener
>
listeners
;
private
EventDispatcher
eventDispatcher
;
private
Loader
initialPlaylistLoader
;
private
Handler
playlistRefreshHandler
;
private
PrimaryPlaylistListener
primaryPlaylistListener
;
private
HlsMasterPlaylist
masterPlaylist
;
private
HlsUrl
primaryHlsUrl
;
private
HlsMediaPlaylist
primaryUrlSnapshot
;
private
boolean
isLive
;
private
long
initialStartTimeUs
;
/**
* @param dataSourceFactory A factory for {@link DataSource} instances.
* @param minRetryCount The minimum number of times loads must be retried before {@link
* #maybeThrowPlaylistRefreshError(HlsUrl)} and {@link
* #maybeThrowPrimaryPlaylistRefreshError()} propagate any loading errors.
* @param playlistParser A {@link ParsingLoadable.Parser} for HLS playlists.
*/
public
DefaultHlsPlaylistTracker
(
HlsDataSourceFactory
dataSourceFactory
,
int
minRetryCount
,
ParsingLoadable
.
Parser
<
HlsPlaylist
>
playlistParser
)
{
this
.
dataSourceFactory
=
dataSourceFactory
;
this
.
minRetryCount
=
minRetryCount
;
this
.
playlistParser
=
playlistParser
;
listeners
=
new
ArrayList
<>();
playlistBundles
=
new
IdentityHashMap
<>();
initialStartTimeUs
=
C
.
TIME_UNSET
;
}
// HlsPlaylistTracker implementation.
@Override
public
void
start
(
Uri
initialPlaylistUri
,
EventDispatcher
eventDispatcher
,
PrimaryPlaylistListener
primaryPlaylistListener
)
{
this
.
playlistRefreshHandler
=
new
Handler
();
this
.
eventDispatcher
=
eventDispatcher
;
this
.
primaryPlaylistListener
=
primaryPlaylistListener
;
ParsingLoadable
<
HlsPlaylist
>
masterPlaylistLoadable
=
new
ParsingLoadable
<>(
dataSourceFactory
.
createDataSource
(
C
.
DATA_TYPE_MANIFEST
),
initialPlaylistUri
,
C
.
DATA_TYPE_MANIFEST
,
playlistParser
);
Assertions
.
checkState
(
initialPlaylistLoader
==
null
);
initialPlaylistLoader
=
new
Loader
(
"DefaultHlsPlaylistTracker:MasterPlaylist"
);
long
elapsedRealtime
=
initialPlaylistLoader
.
startLoading
(
masterPlaylistLoadable
,
this
,
minRetryCount
);
eventDispatcher
.
loadStarted
(
masterPlaylistLoadable
.
dataSpec
,
masterPlaylistLoadable
.
type
,
elapsedRealtime
);
}
@Override
public
void
release
()
{
primaryHlsUrl
=
null
;
primaryUrlSnapshot
=
null
;
masterPlaylist
=
null
;
initialStartTimeUs
=
C
.
TIME_UNSET
;
initialPlaylistLoader
.
release
();
initialPlaylistLoader
=
null
;
for
(
MediaPlaylistBundle
bundle
:
playlistBundles
.
values
())
{
bundle
.
release
();
}
playlistRefreshHandler
.
removeCallbacksAndMessages
(
null
);
playlistRefreshHandler
=
null
;
playlistBundles
.
clear
();
}
@Override
public
void
addListener
(
PlaylistEventListener
listener
)
{
listeners
.
add
(
listener
);
}
@Override
public
void
removeListener
(
PlaylistEventListener
listener
)
{
listeners
.
remove
(
listener
);
}
@Override
public
HlsMasterPlaylist
getMasterPlaylist
()
{
return
masterPlaylist
;
}
@Override
public
HlsMediaPlaylist
getPlaylistSnapshot
(
HlsUrl
url
)
{
HlsMediaPlaylist
snapshot
=
playlistBundles
.
get
(
url
).
getPlaylistSnapshot
();
if
(
snapshot
!=
null
)
{
maybeSetPrimaryUrl
(
url
);
}
return
snapshot
;
}
@Override
public
long
getInitialStartTimeUs
()
{
return
initialStartTimeUs
;
}
@Override
public
boolean
isSnapshotValid
(
HlsUrl
url
)
{
return
playlistBundles
.
get
(
url
).
isSnapshotValid
();
}
@Override
public
void
maybeThrowPrimaryPlaylistRefreshError
()
throws
IOException
{
if
(
initialPlaylistLoader
!=
null
)
{
initialPlaylistLoader
.
maybeThrowError
();
}
if
(
primaryHlsUrl
!=
null
)
{
maybeThrowPlaylistRefreshError
(
primaryHlsUrl
);
}
}
@Override
public
void
maybeThrowPlaylistRefreshError
(
HlsUrl
url
)
throws
IOException
{
playlistBundles
.
get
(
url
).
maybeThrowPlaylistRefreshError
();
}
@Override
public
void
refreshPlaylist
(
HlsUrl
url
)
{
playlistBundles
.
get
(
url
).
loadPlaylist
();
}
@Override
public
boolean
isLive
()
{
return
isLive
;
}
// Loader.Callback implementation.
@Override
public
void
onLoadCompleted
(
ParsingLoadable
<
HlsPlaylist
>
loadable
,
long
elapsedRealtimeMs
,
long
loadDurationMs
)
{
HlsPlaylist
result
=
loadable
.
getResult
();
HlsMasterPlaylist
masterPlaylist
;
boolean
isMediaPlaylist
=
result
instanceof
HlsMediaPlaylist
;
if
(
isMediaPlaylist
)
{
masterPlaylist
=
HlsMasterPlaylist
.
createSingleVariantMasterPlaylist
(
result
.
baseUri
);
}
else
/* result instanceof HlsMasterPlaylist */
{
masterPlaylist
=
(
HlsMasterPlaylist
)
result
;
}
this
.
masterPlaylist
=
masterPlaylist
;
primaryHlsUrl
=
masterPlaylist
.
variants
.
get
(
0
);
ArrayList
<
HlsUrl
>
urls
=
new
ArrayList
<>();
urls
.
addAll
(
masterPlaylist
.
variants
);
urls
.
addAll
(
masterPlaylist
.
audios
);
urls
.
addAll
(
masterPlaylist
.
subtitles
);
createBundles
(
urls
);
MediaPlaylistBundle
primaryBundle
=
playlistBundles
.
get
(
primaryHlsUrl
);
if
(
isMediaPlaylist
)
{
// We don't need to load the playlist again. We can use the same result.
primaryBundle
.
processLoadedPlaylist
((
HlsMediaPlaylist
)
result
);
}
else
{
primaryBundle
.
loadPlaylist
();
}
eventDispatcher
.
loadCompleted
(
loadable
.
dataSpec
,
C
.
DATA_TYPE_MANIFEST
,
elapsedRealtimeMs
,
loadDurationMs
,
loadable
.
bytesLoaded
());
}
@Override
public
void
onLoadCanceled
(
ParsingLoadable
<
HlsPlaylist
>
loadable
,
long
elapsedRealtimeMs
,
long
loadDurationMs
,
boolean
released
)
{
eventDispatcher
.
loadCanceled
(
loadable
.
dataSpec
,
C
.
DATA_TYPE_MANIFEST
,
elapsedRealtimeMs
,
loadDurationMs
,
loadable
.
bytesLoaded
());
}
@Override
public
@Loader
.
RetryAction
int
onLoadError
(
ParsingLoadable
<
HlsPlaylist
>
loadable
,
long
elapsedRealtimeMs
,
long
loadDurationMs
,
IOException
error
)
{
boolean
isFatal
=
error
instanceof
ParserException
;
eventDispatcher
.
loadError
(
loadable
.
dataSpec
,
C
.
DATA_TYPE_MANIFEST
,
elapsedRealtimeMs
,
loadDurationMs
,
loadable
.
bytesLoaded
(),
error
,
isFatal
);
return
isFatal
?
Loader
.
DONT_RETRY_FATAL
:
Loader
.
RETRY
;
}
// Internal methods.
private
boolean
maybeSelectNewPrimaryUrl
()
{
List
<
HlsUrl
>
variants
=
masterPlaylist
.
variants
;
int
variantsSize
=
variants
.
size
();
long
currentTimeMs
=
SystemClock
.
elapsedRealtime
();
for
(
int
i
=
0
;
i
<
variantsSize
;
i
++)
{
MediaPlaylistBundle
bundle
=
playlistBundles
.
get
(
variants
.
get
(
i
));
if
(
currentTimeMs
>
bundle
.
blacklistUntilMs
)
{
primaryHlsUrl
=
bundle
.
playlistUrl
;
bundle
.
loadPlaylist
();
return
true
;
}
}
return
false
;
}
private
void
maybeSetPrimaryUrl
(
HlsUrl
url
)
{
if
(
url
==
primaryHlsUrl
||
!
masterPlaylist
.
variants
.
contains
(
url
)
||
(
primaryUrlSnapshot
!=
null
&&
primaryUrlSnapshot
.
hasEndTag
))
{
// Ignore if the primary url is unchanged, if the url is not a variant url, or if the last
// primary snapshot contains an end tag.
return
;
}
primaryHlsUrl
=
url
;
playlistBundles
.
get
(
primaryHlsUrl
).
loadPlaylist
();
}
private
void
createBundles
(
List
<
HlsUrl
>
urls
)
{
int
listSize
=
urls
.
size
();
for
(
int
i
=
0
;
i
<
listSize
;
i
++)
{
HlsUrl
url
=
urls
.
get
(
i
);
MediaPlaylistBundle
bundle
=
new
MediaPlaylistBundle
(
url
);
playlistBundles
.
put
(
url
,
bundle
);
}
}
/**
* Called by the bundles when a snapshot changes.
*
* @param url The url of the playlist.
* @param newSnapshot The new snapshot.
*/
private
void
onPlaylistUpdated
(
HlsUrl
url
,
HlsMediaPlaylist
newSnapshot
)
{
if
(
url
==
primaryHlsUrl
)
{
if
(
primaryUrlSnapshot
==
null
)
{
// This is the first primary url snapshot.
isLive
=
!
newSnapshot
.
hasEndTag
;
initialStartTimeUs
=
newSnapshot
.
startTimeUs
;
}
primaryUrlSnapshot
=
newSnapshot
;
primaryPlaylistListener
.
onPrimaryPlaylistRefreshed
(
newSnapshot
);
}
int
listenersSize
=
listeners
.
size
();
for
(
int
i
=
0
;
i
<
listenersSize
;
i
++)
{
listeners
.
get
(
i
).
onPlaylistChanged
();
}
}
private
boolean
notifyPlaylistError
(
HlsUrl
playlistUrl
,
boolean
shouldBlacklist
)
{
int
listenersSize
=
listeners
.
size
();
boolean
anyBlacklistingFailed
=
false
;
for
(
int
i
=
0
;
i
<
listenersSize
;
i
++)
{
anyBlacklistingFailed
|=
!
listeners
.
get
(
i
).
onPlaylistError
(
playlistUrl
,
shouldBlacklist
);
}
return
anyBlacklistingFailed
;
}
private
HlsMediaPlaylist
getLatestPlaylistSnapshot
(
HlsMediaPlaylist
oldPlaylist
,
HlsMediaPlaylist
loadedPlaylist
)
{
if
(!
loadedPlaylist
.
isNewerThan
(
oldPlaylist
))
{
if
(
loadedPlaylist
.
hasEndTag
)
{
// If the loaded playlist has an end tag but is not newer than the old playlist then we have
// an inconsistent state. This is typically caused by the server incorrectly resetting the
// media sequence when appending the end tag. We resolve this case as best we can by
// returning the old playlist with the end tag appended.
return
oldPlaylist
.
copyWithEndTag
();
}
else
{
return
oldPlaylist
;
}
}
long
startTimeUs
=
getLoadedPlaylistStartTimeUs
(
oldPlaylist
,
loadedPlaylist
);
int
discontinuitySequence
=
getLoadedPlaylistDiscontinuitySequence
(
oldPlaylist
,
loadedPlaylist
);
return
loadedPlaylist
.
copyWith
(
startTimeUs
,
discontinuitySequence
);
}
private
long
getLoadedPlaylistStartTimeUs
(
HlsMediaPlaylist
oldPlaylist
,
HlsMediaPlaylist
loadedPlaylist
)
{
if
(
loadedPlaylist
.
hasProgramDateTime
)
{
return
loadedPlaylist
.
startTimeUs
;
}
long
primarySnapshotStartTimeUs
=
primaryUrlSnapshot
!=
null
?
primaryUrlSnapshot
.
startTimeUs
:
0
;
if
(
oldPlaylist
==
null
)
{
return
primarySnapshotStartTimeUs
;
}
int
oldPlaylistSize
=
oldPlaylist
.
segments
.
size
();
Segment
firstOldOverlappingSegment
=
getFirstOldOverlappingSegment
(
oldPlaylist
,
loadedPlaylist
);
if
(
firstOldOverlappingSegment
!=
null
)
{
return
oldPlaylist
.
startTimeUs
+
firstOldOverlappingSegment
.
relativeStartTimeUs
;
}
else
if
(
oldPlaylistSize
==
loadedPlaylist
.
mediaSequence
-
oldPlaylist
.
mediaSequence
)
{
return
oldPlaylist
.
getEndTimeUs
();
}
else
{
// No segments overlap, we assume the new playlist start coincides with the primary playlist.
return
primarySnapshotStartTimeUs
;
}
}
private
int
getLoadedPlaylistDiscontinuitySequence
(
HlsMediaPlaylist
oldPlaylist
,
HlsMediaPlaylist
loadedPlaylist
)
{
if
(
loadedPlaylist
.
hasDiscontinuitySequence
)
{
return
loadedPlaylist
.
discontinuitySequence
;
}
// TODO: Improve cross-playlist discontinuity adjustment.
int
primaryUrlDiscontinuitySequence
=
primaryUrlSnapshot
!=
null
?
primaryUrlSnapshot
.
discontinuitySequence
:
0
;
if
(
oldPlaylist
==
null
)
{
return
primaryUrlDiscontinuitySequence
;
}
Segment
firstOldOverlappingSegment
=
getFirstOldOverlappingSegment
(
oldPlaylist
,
loadedPlaylist
);
if
(
firstOldOverlappingSegment
!=
null
)
{
return
oldPlaylist
.
discontinuitySequence
+
firstOldOverlappingSegment
.
relativeDiscontinuitySequence
-
loadedPlaylist
.
segments
.
get
(
0
).
relativeDiscontinuitySequence
;
}
return
primaryUrlDiscontinuitySequence
;
}
private
static
Segment
getFirstOldOverlappingSegment
(
HlsMediaPlaylist
oldPlaylist
,
HlsMediaPlaylist
loadedPlaylist
)
{
int
mediaSequenceOffset
=
(
int
)
(
loadedPlaylist
.
mediaSequence
-
oldPlaylist
.
mediaSequence
);
List
<
Segment
>
oldSegments
=
oldPlaylist
.
segments
;
return
mediaSequenceOffset
<
oldSegments
.
size
()
?
oldSegments
.
get
(
mediaSequenceOffset
)
:
null
;
}
/** Holds all information related to a specific Media Playlist. */
private
final
class
MediaPlaylistBundle
implements
Loader
.
Callback
<
ParsingLoadable
<
HlsPlaylist
>>,
Runnable
{
private
final
HlsUrl
playlistUrl
;
private
final
Loader
mediaPlaylistLoader
;
private
final
ParsingLoadable
<
HlsPlaylist
>
mediaPlaylistLoadable
;
private
HlsMediaPlaylist
playlistSnapshot
;
private
long
lastSnapshotLoadMs
;
private
long
lastSnapshotChangeMs
;
private
long
earliestNextLoadTimeMs
;
private
long
blacklistUntilMs
;
private
boolean
loadPending
;
private
IOException
playlistError
;
public
MediaPlaylistBundle
(
HlsUrl
playlistUrl
)
{
this
.
playlistUrl
=
playlistUrl
;
mediaPlaylistLoader
=
new
Loader
(
"DefaultHlsPlaylistTracker:MediaPlaylist"
);
mediaPlaylistLoadable
=
new
ParsingLoadable
<>(
dataSourceFactory
.
createDataSource
(
C
.
DATA_TYPE_MANIFEST
),
UriUtil
.
resolveToUri
(
masterPlaylist
.
baseUri
,
playlistUrl
.
url
),
C
.
DATA_TYPE_MANIFEST
,
playlistParser
);
}
public
HlsMediaPlaylist
getPlaylistSnapshot
()
{
return
playlistSnapshot
;
}
public
boolean
isSnapshotValid
()
{
if
(
playlistSnapshot
==
null
)
{
return
false
;
}
long
currentTimeMs
=
SystemClock
.
elapsedRealtime
();
long
snapshotValidityDurationMs
=
Math
.
max
(
30000
,
C
.
usToMs
(
playlistSnapshot
.
durationUs
));
return
playlistSnapshot
.
hasEndTag
||
playlistSnapshot
.
playlistType
==
HlsMediaPlaylist
.
PLAYLIST_TYPE_EVENT
||
playlistSnapshot
.
playlistType
==
HlsMediaPlaylist
.
PLAYLIST_TYPE_VOD
||
lastSnapshotLoadMs
+
snapshotValidityDurationMs
>
currentTimeMs
;
}
public
void
release
()
{
mediaPlaylistLoader
.
release
();
}
public
void
loadPlaylist
()
{
blacklistUntilMs
=
0
;
if
(
loadPending
||
mediaPlaylistLoader
.
isLoading
())
{
// Load already pending or in progress. Do nothing.
return
;
}
long
currentTimeMs
=
SystemClock
.
elapsedRealtime
();
if
(
currentTimeMs
<
earliestNextLoadTimeMs
)
{
loadPending
=
true
;
playlistRefreshHandler
.
postDelayed
(
this
,
earliestNextLoadTimeMs
-
currentTimeMs
);
}
else
{
loadPlaylistImmediately
();
}
}
public
void
maybeThrowPlaylistRefreshError
()
throws
IOException
{
mediaPlaylistLoader
.
maybeThrowError
();
if
(
playlistError
!=
null
)
{
throw
playlistError
;
}
}
// Loader.Callback implementation.
@Override
public
void
onLoadCompleted
(
ParsingLoadable
<
HlsPlaylist
>
loadable
,
long
elapsedRealtimeMs
,
long
loadDurationMs
)
{
HlsPlaylist
result
=
loadable
.
getResult
();
if
(
result
instanceof
HlsMediaPlaylist
)
{
processLoadedPlaylist
((
HlsMediaPlaylist
)
result
);
eventDispatcher
.
loadCompleted
(
loadable
.
dataSpec
,
C
.
DATA_TYPE_MANIFEST
,
elapsedRealtimeMs
,
loadDurationMs
,
loadable
.
bytesLoaded
());
}
else
{
playlistError
=
new
ParserException
(
"Loaded playlist has unexpected type."
);
}
}
@Override
public
void
onLoadCanceled
(
ParsingLoadable
<
HlsPlaylist
>
loadable
,
long
elapsedRealtimeMs
,
long
loadDurationMs
,
boolean
released
)
{
eventDispatcher
.
loadCanceled
(
loadable
.
dataSpec
,
C
.
DATA_TYPE_MANIFEST
,
elapsedRealtimeMs
,
loadDurationMs
,
loadable
.
bytesLoaded
());
}
@Override
public
@Loader
.
RetryAction
int
onLoadError
(
ParsingLoadable
<
HlsPlaylist
>
loadable
,
long
elapsedRealtimeMs
,
long
loadDurationMs
,
IOException
error
)
{
boolean
isFatal
=
error
instanceof
ParserException
;
eventDispatcher
.
loadError
(
loadable
.
dataSpec
,
C
.
DATA_TYPE_MANIFEST
,
elapsedRealtimeMs
,
loadDurationMs
,
loadable
.
bytesLoaded
(),
error
,
isFatal
);
boolean
shouldBlacklist
=
ChunkedTrackBlacklistUtil
.
shouldBlacklist
(
error
);
boolean
shouldRetryIfNotFatal
=
notifyPlaylistError
(
playlistUrl
,
shouldBlacklist
)
||
!
shouldBlacklist
;
if
(
isFatal
)
{
return
Loader
.
DONT_RETRY_FATAL
;
}
if
(
shouldBlacklist
)
{
shouldRetryIfNotFatal
|=
blacklistPlaylist
();
}
return
shouldRetryIfNotFatal
?
Loader
.
RETRY
:
Loader
.
DONT_RETRY
;
}
// Runnable implementation.
@Override
public
void
run
()
{
loadPending
=
false
;
loadPlaylistImmediately
();
}
// Internal methods.
private
void
loadPlaylistImmediately
()
{
long
elapsedRealtime
=
mediaPlaylistLoader
.
startLoading
(
mediaPlaylistLoadable
,
this
,
minRetryCount
);
eventDispatcher
.
loadStarted
(
mediaPlaylistLoadable
.
dataSpec
,
mediaPlaylistLoadable
.
type
,
elapsedRealtime
);
}
private
void
processLoadedPlaylist
(
HlsMediaPlaylist
loadedPlaylist
)
{
HlsMediaPlaylist
oldPlaylist
=
playlistSnapshot
;
long
currentTimeMs
=
SystemClock
.
elapsedRealtime
();
lastSnapshotLoadMs
=
currentTimeMs
;
playlistSnapshot
=
getLatestPlaylistSnapshot
(
oldPlaylist
,
loadedPlaylist
);
if
(
playlistSnapshot
!=
oldPlaylist
)
{
playlistError
=
null
;
lastSnapshotChangeMs
=
currentTimeMs
;
onPlaylistUpdated
(
playlistUrl
,
playlistSnapshot
);
}
else
if
(!
playlistSnapshot
.
hasEndTag
)
{
if
(
loadedPlaylist
.
mediaSequence
+
loadedPlaylist
.
segments
.
size
()
<
playlistSnapshot
.
mediaSequence
)
{
// The media sequence jumped backwards. The server has probably reset.
playlistError
=
new
PlaylistResetException
(
playlistUrl
.
url
);
notifyPlaylistError
(
playlistUrl
,
false
);
}
else
if
(
currentTimeMs
-
lastSnapshotChangeMs
>
C
.
usToMs
(
playlistSnapshot
.
targetDurationUs
)
*
PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT
)
{
// The playlist seems to be stuck. Blacklist it.
playlistError
=
new
PlaylistStuckException
(
playlistUrl
.
url
);
notifyPlaylistError
(
playlistUrl
,
true
);
blacklistPlaylist
();
}
}
// Do not allow the playlist to load again within the target duration if we obtained a new
// snapshot, or half the target duration otherwise.
earliestNextLoadTimeMs
=
currentTimeMs
+
C
.
usToMs
(
playlistSnapshot
!=
oldPlaylist
?
playlistSnapshot
.
targetDurationUs
:
(
playlistSnapshot
.
targetDurationUs
/
2
));
// Schedule a load if this is the primary playlist and it doesn't have an end tag. Else the
// next load will be scheduled when refreshPlaylist is called, or when this playlist becomes
// the primary.
if
(
playlistUrl
==
primaryHlsUrl
&&
!
playlistSnapshot
.
hasEndTag
)
{
loadPlaylist
();
}
}
/**
* Blacklists the playlist.
*
* @return Whether the playlist is the primary, despite being blacklisted.
*/
private
boolean
blacklistPlaylist
()
{
blacklistUntilMs
=
SystemClock
.
elapsedRealtime
()
+
ChunkedTrackBlacklistUtil
.
DEFAULT_TRACK_BLACKLIST_MS
;
return
primaryHlsUrl
==
playlistUrl
&&
!
maybeSelectNewPrimaryUrl
();
}
}
}
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java
View file @
f7ed789f
/*
* Copyright (C) 201
6
The Android Open Source Project
* Copyright (C) 201
8
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.
...
...
@@ -16,66 +16,28 @@
package
com
.
google
.
android
.
exoplayer2
.
source
.
hls
.
playlist
;
import
android.net.Uri
;
import
android.os.Handler
;
import
android.os.SystemClock
;
import
android.support.annotation.Nullable
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.ParserException
;
import
com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher
;
import
com.google.android.exoplayer2.source.chunk.ChunkedTrackBlacklistUtil
;
import
com.google.android.exoplayer2.source.hls.HlsDataSourceFactory
;
import
com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl
;
import
com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment
;
import
com.google.android.exoplayer2.upstream.DataSource
;
import
com.google.android.exoplayer2.upstream.Loader
;
import
com.google.android.exoplayer2.upstream.ParsingLoadable
;
import
com.google.android.exoplayer2.util.UriUtil
;
import
java.io.IOException
;
import
java.util.ArrayList
;
import
java.util.IdentityHashMap
;
import
java.util.List
;
/**
* Tracks playlists linked to a provided playlist url. The provided url might reference an HLS
* master playlist or a media playlist.
* Tracks playlists associated to an HLS stream and provides snapshots.
*
* <p>The playlist tracker is responsible for exposing the seeking window, which is defined by the
* segments that one of the playlists exposes. This playlist is called primary and needs to be
* periodically refreshed in the case of live streams. Note that the primary playlist is one of the
* media playlists while the master playlist is an optional kind of playlist defined by the HLS
* specification (RFC 8216).
*
* <p>Playlist loads might encounter errors. The tracker may choose to blacklist them to ensure a
* primary playlist is always available.
*/
public
final
class
HlsPlaylistTracker
implements
Loader
.
Callback
<
ParsingLoadable
<
HlsPlaylist
>>
{
public
interface
HlsPlaylistTracker
{
/**
* Thrown when a playlist is considered to be stuck due to a server side error.
*/
public
static
final
class
PlaylistStuckException
extends
IOException
{
/**
* The url of the stuck playlist.
*/
public
final
String
url
;
private
PlaylistStuckException
(
String
url
)
{
this
.
url
=
url
;
}
}
/**
* Thrown when the media sequence of a new snapshot indicates the server has reset.
*/
public
static
final
class
PlaylistResetException
extends
IOException
{
/**
* The url of the reset playlist.
*/
public
final
String
url
;
private
PlaylistResetException
(
String
url
)
{
this
.
url
=
url
;
}
}
/**
* Listener for primary playlist changes.
*/
public
interface
PrimaryPlaylistListener
{
/** Listener for primary playlist changes. */
interface
PrimaryPlaylistListener
{
/**
* Called when the primary playlist changes.
...
...
@@ -85,10 +47,8 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
void
onPrimaryPlaylistRefreshed
(
HlsMediaPlaylist
mediaPlaylist
);
}
/**
* Called on playlist loading events.
*/
public
interface
PlaylistEventListener
{
/** Called on playlist loading events. */
interface
PlaylistEventListener
{
/**
* Called a playlist changes.
...
...
@@ -105,141 +65,107 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
boolean
onPlaylistError
(
HlsUrl
url
,
boolean
shouldBlacklist
);
}
/**
* Coefficient applied on the target duration of a playlist to determine the amount of time after
* which an unchanging playlist is considered stuck.
*/
private
static
final
double
PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT
=
3.5
;
/**
Thrown when a playlist is considered to be stuck due to a server side error. */
final
class
PlaylistStuckException
extends
IOException
{
/** The url of the stuck playlist.
*/
public
final
String
url
;
private
final
Uri
initialPlaylistUri
;
private
final
HlsDataSourceFactory
dataSourceFactory
;
private
final
ParsingLoadable
.
Parser
<
HlsPlaylist
>
playlistParser
;
private
final
int
minRetryCount
;
private
final
IdentityHashMap
<
HlsUrl
,
MediaPlaylistBundle
>
playlistBundles
;
private
final
Handler
playlistRefreshHandler
;
private
final
PrimaryPlaylistListener
primaryPlaylistListener
;
private
final
List
<
PlaylistEventListener
>
listeners
;
private
final
Loader
initialPlaylistLoader
;
private
final
EventDispatcher
eventDispatcher
;
/**
* Creates an instance.
*
* @param url See {@link #url}.
*/
public
PlaylistStuckException
(
String
url
)
{
this
.
url
=
url
;
}
}
private
HlsMasterPlaylist
masterPlaylist
;
private
HlsUrl
primaryHlsUrl
;
private
HlsMediaPlaylist
primaryUrlSnapshot
;
private
boolean
isLive
;
private
long
initialStartTimeUs
;
/** Thrown when the media sequence of a new snapshot indicates the server has reset. */
final
class
PlaylistResetException
extends
IOException
{
/** The url of the reset playlist. */
public
final
String
url
;
/**
* Creates an instance.
*
* @param url See {@link #url}.
*/
public
PlaylistResetException
(
String
url
)
{
this
.
url
=
url
;
}
}
/**
* @param initialPlaylistUri Uri for the initial playlist of the stream. Can refer a media
* playlist or a master playlist.
* @param dataSourceFactory A factory for {@link DataSource} instances.
* Starts the playlist tracker.
*
* <p>Must be called from the playback thread. A tracker may be restarted after a {@link
* #release()} call.
*
* @param initialPlaylistUri Uri of the HLS stream. Can point to a media playlist or a master
* playlist.
* @param eventDispatcher A dispatcher to notify of events.
* @param minRetryCount The minimum number of times loads must be retried before
* {@link #maybeThrowPlaylistRefreshError(HlsUrl)} and
* {@link #maybeThrowPrimaryPlaylistRefreshError()} propagate any loading errors.
* @param primaryPlaylistListener A callback for the primary playlist change events.
* @param listener A callback for the primary playlist change events.
*/
public
HlsPlaylistTracker
(
Uri
initialPlaylistUri
,
HlsDataSourceFactory
dataSourceFactory
,
EventDispatcher
eventDispatcher
,
int
minRetryCount
,
PrimaryPlaylistListener
primaryPlaylistListener
,
ParsingLoadable
.
Parser
<
HlsPlaylist
>
playlistParser
)
{
this
.
initialPlaylistUri
=
initialPlaylistUri
;
this
.
dataSourceFactory
=
dataSourceFactory
;
this
.
eventDispatcher
=
eventDispatcher
;
this
.
minRetryCount
=
minRetryCount
;
this
.
primaryPlaylistListener
=
primaryPlaylistListener
;
this
.
playlistParser
=
playlistParser
;
listeners
=
new
ArrayList
<>();
initialPlaylistLoader
=
new
Loader
(
"HlsPlaylistTracker:MasterPlaylist"
);
playlistBundles
=
new
IdentityHashMap
<>();
playlistRefreshHandler
=
new
Handler
();
initialStartTimeUs
=
C
.
TIME_UNSET
;
}
void
start
(
Uri
initialPlaylistUri
,
EventDispatcher
eventDispatcher
,
PrimaryPlaylistListener
listener
);
/** Releases all acquired resources. Must be called once per {@link #start} call. */
void
release
();
/**
* Registers a listener to receive events from the playlist tracker.
*
* @param listener The listener.
*/
public
void
addListener
(
PlaylistEventListener
listener
)
{
listeners
.
add
(
listener
);
}
void
addListener
(
PlaylistEventListener
listener
);
/**
* Unregisters a listener.
*
* @param listener The listener to unregister.
*/
public
void
removeListener
(
PlaylistEventListener
listener
)
{
listeners
.
remove
(
listener
);
}
/**
* Starts tracking all the playlists related to the provided Uri.
*/
public
void
start
()
{
ParsingLoadable
<
HlsPlaylist
>
masterPlaylistLoadable
=
new
ParsingLoadable
<>(
dataSourceFactory
.
createDataSource
(
C
.
DATA_TYPE_MANIFEST
),
initialPlaylistUri
,
C
.
DATA_TYPE_MANIFEST
,
playlistParser
);
initialPlaylistLoader
.
startLoading
(
masterPlaylistLoadable
,
this
,
minRetryCount
);
}
void
removeListener
(
PlaylistEventListener
listener
);
/**
* Returns the master playlist.
*
* <p>If the uri passed to {@link #start} points to a media playlist, an {@link HlsMasterPlaylist}
* with a single variant for said media playlist is returned.
*
* @return The master playlist. Null if the initial playlist has yet to be loaded.
*/
public
HlsMasterPlaylist
getMasterPlaylist
()
{
return
masterPlaylist
;
}
@Nullable
HlsMasterPlaylist
getMasterPlaylist
();
/**
* Returns the most recent snapshot available of the playlist referenced by the provided
*
{@link
HlsUrl}.
* Returns the most recent snapshot available of the playlist referenced by the provided
{@link
* HlsUrl}.
*
* @param url The {@link HlsUrl} corresponding to the requested media playlist.
* @return The most recent snapshot of the playlist referenced by the provided {@link HlsUrl}. May
* be null if no snapshot has been loaded yet.
*/
public
HlsMediaPlaylist
getPlaylistSnapshot
(
HlsUrl
url
)
{
HlsMediaPlaylist
snapshot
=
playlistBundles
.
get
(
url
).
getPlaylistSnapshot
();
if
(
snapshot
!=
null
)
{
maybeSetPrimaryUrl
(
url
);
}
return
snapshot
;
}
@Nullable
HlsMediaPlaylist
getPlaylistSnapshot
(
HlsUrl
url
);
/**
* Returns the start time of the first loaded primary playlist, or {@link C#TIME_UNSET} if no
* media playlist has been loaded.
*/
public
long
getInitialStartTimeUs
()
{
return
initialStartTimeUs
;
}
long
getInitialStartTimeUs
();
/**
* Returns whether the snapshot of the playlist referenced by the provided {@link HlsUrl} is
* valid, meaning all the segments referenced by the playlist are expected to be available. If the
* playlist is not valid then some of the segments may no longer be available.
*
* @param url The {@link HlsUrl}.
* @return Whether the snapshot of the playlist referenced by the provided {@link HlsUrl} is
* valid.
*/
public
boolean
isSnapshotValid
(
HlsUrl
url
)
{
return
playlistBundles
.
get
(
url
).
isSnapshotValid
();
}
/**
* Releases the playlist tracker.
*/
public
void
release
()
{
initialPlaylistLoader
.
release
();
for
(
MediaPlaylistBundle
bundle
:
playlistBundles
.
values
())
{
bundle
.
release
();
}
playlistRefreshHandler
.
removeCallbacksAndMessages
(
null
);
playlistBundles
.
clear
();
}
boolean
isSnapshotValid
(
HlsUrl
url
);
/**
* If the tracker is having trouble refreshing the master playlist or the primary playlist, this
...
...
@@ -247,401 +173,31 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
*
* @throws IOException The underlying error.
*/
public
void
maybeThrowPrimaryPlaylistRefreshError
()
throws
IOException
{
initialPlaylistLoader
.
maybeThrowError
();
if
(
primaryHlsUrl
!=
null
)
{
maybeThrowPlaylistRefreshError
(
primaryHlsUrl
);
}
}
void
maybeThrowPrimaryPlaylistRefreshError
()
throws
IOException
;
/**
* If the playlist is having trouble refreshing the playlist referenced by the given
*
{@link
HlsUrl}, this method throws the underlying error.
* If the playlist is having trouble refreshing the playlist referenced by the given
{@link
* HlsUrl}, this method throws the underlying error.
*
* @param url The {@link HlsUrl}.
* @throws IOException The underyling error.
*/
public
void
maybeThrowPlaylistRefreshError
(
HlsUrl
url
)
throws
IOException
{
playlistBundles
.
get
(
url
).
maybeThrowPlaylistRefreshError
();
}
void
maybeThrowPlaylistRefreshError
(
HlsUrl
url
)
throws
IOException
;
/**
* Triggers a playlist refresh and whitelists it.
* Requests a playlist refresh and whitelists it.
*
* <p>The playlist tracker may choose the delay the playlist refresh. The request is discarded if
* a refresh was already pending.
*
* @param url The {@link HlsUrl} of the playlist to be refreshed.
*/
public
void
refreshPlaylist
(
HlsUrl
url
)
{
playlistBundles
.
get
(
url
).
loadPlaylist
();
}
void
refreshPlaylist
(
HlsUrl
url
);
/**
* Returns whether th
is is live content
.
* Returns whether th
e tracked playlists describe a live stream
.
*
* @return True if the content is live. False otherwise.
*/
public
boolean
isLive
()
{
return
isLive
;
}
// Loader.Callback implementation.
@Override
public
void
onLoadCompleted
(
ParsingLoadable
<
HlsPlaylist
>
loadable
,
long
elapsedRealtimeMs
,
long
loadDurationMs
)
{
HlsPlaylist
result
=
loadable
.
getResult
();
HlsMasterPlaylist
masterPlaylist
;
boolean
isMediaPlaylist
=
result
instanceof
HlsMediaPlaylist
;
if
(
isMediaPlaylist
)
{
masterPlaylist
=
HlsMasterPlaylist
.
createSingleVariantMasterPlaylist
(
result
.
baseUri
);
}
else
/* result instanceof HlsMasterPlaylist */
{
masterPlaylist
=
(
HlsMasterPlaylist
)
result
;
}
this
.
masterPlaylist
=
masterPlaylist
;
primaryHlsUrl
=
masterPlaylist
.
variants
.
get
(
0
);
ArrayList
<
HlsUrl
>
urls
=
new
ArrayList
<>();
urls
.
addAll
(
masterPlaylist
.
variants
);
urls
.
addAll
(
masterPlaylist
.
audios
);
urls
.
addAll
(
masterPlaylist
.
subtitles
);
createBundles
(
urls
);
MediaPlaylistBundle
primaryBundle
=
playlistBundles
.
get
(
primaryHlsUrl
);
if
(
isMediaPlaylist
)
{
// We don't need to load the playlist again. We can use the same result.
primaryBundle
.
processLoadedPlaylist
((
HlsMediaPlaylist
)
result
);
}
else
{
primaryBundle
.
loadPlaylist
();
}
eventDispatcher
.
loadCompleted
(
loadable
.
dataSpec
,
C
.
DATA_TYPE_MANIFEST
,
elapsedRealtimeMs
,
loadDurationMs
,
loadable
.
bytesLoaded
());
}
@Override
public
void
onLoadCanceled
(
ParsingLoadable
<
HlsPlaylist
>
loadable
,
long
elapsedRealtimeMs
,
long
loadDurationMs
,
boolean
released
)
{
eventDispatcher
.
loadCanceled
(
loadable
.
dataSpec
,
C
.
DATA_TYPE_MANIFEST
,
elapsedRealtimeMs
,
loadDurationMs
,
loadable
.
bytesLoaded
());
}
@Override
public
@Loader
.
RetryAction
int
onLoadError
(
ParsingLoadable
<
HlsPlaylist
>
loadable
,
long
elapsedRealtimeMs
,
long
loadDurationMs
,
IOException
error
)
{
boolean
isFatal
=
error
instanceof
ParserException
;
eventDispatcher
.
loadError
(
loadable
.
dataSpec
,
C
.
DATA_TYPE_MANIFEST
,
elapsedRealtimeMs
,
loadDurationMs
,
loadable
.
bytesLoaded
(),
error
,
isFatal
);
return
isFatal
?
Loader
.
DONT_RETRY_FATAL
:
Loader
.
RETRY
;
}
// Internal methods.
private
boolean
maybeSelectNewPrimaryUrl
()
{
List
<
HlsUrl
>
variants
=
masterPlaylist
.
variants
;
int
variantsSize
=
variants
.
size
();
long
currentTimeMs
=
SystemClock
.
elapsedRealtime
();
for
(
int
i
=
0
;
i
<
variantsSize
;
i
++)
{
MediaPlaylistBundle
bundle
=
playlistBundles
.
get
(
variants
.
get
(
i
));
if
(
currentTimeMs
>
bundle
.
blacklistUntilMs
)
{
primaryHlsUrl
=
bundle
.
playlistUrl
;
bundle
.
loadPlaylist
();
return
true
;
}
}
return
false
;
}
private
void
maybeSetPrimaryUrl
(
HlsUrl
url
)
{
if
(
url
==
primaryHlsUrl
||
!
masterPlaylist
.
variants
.
contains
(
url
)
||
(
primaryUrlSnapshot
!=
null
&&
primaryUrlSnapshot
.
hasEndTag
))
{
// Ignore if the primary url is unchanged, if the url is not a variant url, or if the last
// primary snapshot contains an end tag.
return
;
}
primaryHlsUrl
=
url
;
playlistBundles
.
get
(
primaryHlsUrl
).
loadPlaylist
();
}
private
void
createBundles
(
List
<
HlsUrl
>
urls
)
{
int
listSize
=
urls
.
size
();
for
(
int
i
=
0
;
i
<
listSize
;
i
++)
{
HlsUrl
url
=
urls
.
get
(
i
);
MediaPlaylistBundle
bundle
=
new
MediaPlaylistBundle
(
url
);
playlistBundles
.
put
(
url
,
bundle
);
}
}
/**
* Called by the bundles when a snapshot changes.
*
* @param url The url of the playlist.
* @param newSnapshot The new snapshot.
*/
private
void
onPlaylistUpdated
(
HlsUrl
url
,
HlsMediaPlaylist
newSnapshot
)
{
if
(
url
==
primaryHlsUrl
)
{
if
(
primaryUrlSnapshot
==
null
)
{
// This is the first primary url snapshot.
isLive
=
!
newSnapshot
.
hasEndTag
;
initialStartTimeUs
=
newSnapshot
.
startTimeUs
;
}
primaryUrlSnapshot
=
newSnapshot
;
primaryPlaylistListener
.
onPrimaryPlaylistRefreshed
(
newSnapshot
);
}
int
listenersSize
=
listeners
.
size
();
for
(
int
i
=
0
;
i
<
listenersSize
;
i
++)
{
listeners
.
get
(
i
).
onPlaylistChanged
();
}
}
private
boolean
notifyPlaylistError
(
HlsUrl
playlistUrl
,
boolean
shouldBlacklist
)
{
int
listenersSize
=
listeners
.
size
();
boolean
anyBlacklistingFailed
=
false
;
for
(
int
i
=
0
;
i
<
listenersSize
;
i
++)
{
anyBlacklistingFailed
|=
!
listeners
.
get
(
i
).
onPlaylistError
(
playlistUrl
,
shouldBlacklist
);
}
return
anyBlacklistingFailed
;
}
private
HlsMediaPlaylist
getLatestPlaylistSnapshot
(
HlsMediaPlaylist
oldPlaylist
,
HlsMediaPlaylist
loadedPlaylist
)
{
if
(!
loadedPlaylist
.
isNewerThan
(
oldPlaylist
))
{
if
(
loadedPlaylist
.
hasEndTag
)
{
// If the loaded playlist has an end tag but is not newer than the old playlist then we have
// an inconsistent state. This is typically caused by the server incorrectly resetting the
// media sequence when appending the end tag. We resolve this case as best we can by
// returning the old playlist with the end tag appended.
return
oldPlaylist
.
copyWithEndTag
();
}
else
{
return
oldPlaylist
;
}
}
long
startTimeUs
=
getLoadedPlaylistStartTimeUs
(
oldPlaylist
,
loadedPlaylist
);
int
discontinuitySequence
=
getLoadedPlaylistDiscontinuitySequence
(
oldPlaylist
,
loadedPlaylist
);
return
loadedPlaylist
.
copyWith
(
startTimeUs
,
discontinuitySequence
);
}
private
long
getLoadedPlaylistStartTimeUs
(
HlsMediaPlaylist
oldPlaylist
,
HlsMediaPlaylist
loadedPlaylist
)
{
if
(
loadedPlaylist
.
hasProgramDateTime
)
{
return
loadedPlaylist
.
startTimeUs
;
}
long
primarySnapshotStartTimeUs
=
primaryUrlSnapshot
!=
null
?
primaryUrlSnapshot
.
startTimeUs
:
0
;
if
(
oldPlaylist
==
null
)
{
return
primarySnapshotStartTimeUs
;
}
int
oldPlaylistSize
=
oldPlaylist
.
segments
.
size
();
Segment
firstOldOverlappingSegment
=
getFirstOldOverlappingSegment
(
oldPlaylist
,
loadedPlaylist
);
if
(
firstOldOverlappingSegment
!=
null
)
{
return
oldPlaylist
.
startTimeUs
+
firstOldOverlappingSegment
.
relativeStartTimeUs
;
}
else
if
(
oldPlaylistSize
==
loadedPlaylist
.
mediaSequence
-
oldPlaylist
.
mediaSequence
)
{
return
oldPlaylist
.
getEndTimeUs
();
}
else
{
// No segments overlap, we assume the new playlist start coincides with the primary playlist.
return
primarySnapshotStartTimeUs
;
}
}
private
int
getLoadedPlaylistDiscontinuitySequence
(
HlsMediaPlaylist
oldPlaylist
,
HlsMediaPlaylist
loadedPlaylist
)
{
if
(
loadedPlaylist
.
hasDiscontinuitySequence
)
{
return
loadedPlaylist
.
discontinuitySequence
;
}
// TODO: Improve cross-playlist discontinuity adjustment.
int
primaryUrlDiscontinuitySequence
=
primaryUrlSnapshot
!=
null
?
primaryUrlSnapshot
.
discontinuitySequence
:
0
;
if
(
oldPlaylist
==
null
)
{
return
primaryUrlDiscontinuitySequence
;
}
Segment
firstOldOverlappingSegment
=
getFirstOldOverlappingSegment
(
oldPlaylist
,
loadedPlaylist
);
if
(
firstOldOverlappingSegment
!=
null
)
{
return
oldPlaylist
.
discontinuitySequence
+
firstOldOverlappingSegment
.
relativeDiscontinuitySequence
-
loadedPlaylist
.
segments
.
get
(
0
).
relativeDiscontinuitySequence
;
}
return
primaryUrlDiscontinuitySequence
;
}
private
static
Segment
getFirstOldOverlappingSegment
(
HlsMediaPlaylist
oldPlaylist
,
HlsMediaPlaylist
loadedPlaylist
)
{
int
mediaSequenceOffset
=
(
int
)
(
loadedPlaylist
.
mediaSequence
-
oldPlaylist
.
mediaSequence
);
List
<
Segment
>
oldSegments
=
oldPlaylist
.
segments
;
return
mediaSequenceOffset
<
oldSegments
.
size
()
?
oldSegments
.
get
(
mediaSequenceOffset
)
:
null
;
}
/**
* Holds all information related to a specific Media Playlist.
*/
private
final
class
MediaPlaylistBundle
implements
Loader
.
Callback
<
ParsingLoadable
<
HlsPlaylist
>>,
Runnable
{
private
final
HlsUrl
playlistUrl
;
private
final
Loader
mediaPlaylistLoader
;
private
final
ParsingLoadable
<
HlsPlaylist
>
mediaPlaylistLoadable
;
private
HlsMediaPlaylist
playlistSnapshot
;
private
long
lastSnapshotLoadMs
;
private
long
lastSnapshotChangeMs
;
private
long
earliestNextLoadTimeMs
;
private
long
blacklistUntilMs
;
private
boolean
loadPending
;
private
IOException
playlistError
;
public
MediaPlaylistBundle
(
HlsUrl
playlistUrl
)
{
this
.
playlistUrl
=
playlistUrl
;
mediaPlaylistLoader
=
new
Loader
(
"HlsPlaylistTracker:MediaPlaylist"
);
mediaPlaylistLoadable
=
new
ParsingLoadable
<>(
dataSourceFactory
.
createDataSource
(
C
.
DATA_TYPE_MANIFEST
),
UriUtil
.
resolveToUri
(
masterPlaylist
.
baseUri
,
playlistUrl
.
url
),
C
.
DATA_TYPE_MANIFEST
,
playlistParser
);
}
public
HlsMediaPlaylist
getPlaylistSnapshot
()
{
return
playlistSnapshot
;
}
public
boolean
isSnapshotValid
()
{
if
(
playlistSnapshot
==
null
)
{
return
false
;
}
long
currentTimeMs
=
SystemClock
.
elapsedRealtime
();
long
snapshotValidityDurationMs
=
Math
.
max
(
30000
,
C
.
usToMs
(
playlistSnapshot
.
durationUs
));
return
playlistSnapshot
.
hasEndTag
||
playlistSnapshot
.
playlistType
==
HlsMediaPlaylist
.
PLAYLIST_TYPE_EVENT
||
playlistSnapshot
.
playlistType
==
HlsMediaPlaylist
.
PLAYLIST_TYPE_VOD
||
lastSnapshotLoadMs
+
snapshotValidityDurationMs
>
currentTimeMs
;
}
public
void
release
()
{
mediaPlaylistLoader
.
release
();
}
public
void
loadPlaylist
()
{
blacklistUntilMs
=
0
;
if
(
loadPending
||
mediaPlaylistLoader
.
isLoading
())
{
// Load already pending or in progress. Do nothing.
return
;
}
long
currentTimeMs
=
SystemClock
.
elapsedRealtime
();
if
(
currentTimeMs
<
earliestNextLoadTimeMs
)
{
loadPending
=
true
;
playlistRefreshHandler
.
postDelayed
(
this
,
earliestNextLoadTimeMs
-
currentTimeMs
);
}
else
{
loadPlaylistImmediately
();
}
}
public
void
maybeThrowPlaylistRefreshError
()
throws
IOException
{
mediaPlaylistLoader
.
maybeThrowError
();
if
(
playlistError
!=
null
)
{
throw
playlistError
;
}
}
// Loader.Callback implementation.
@Override
public
void
onLoadCompleted
(
ParsingLoadable
<
HlsPlaylist
>
loadable
,
long
elapsedRealtimeMs
,
long
loadDurationMs
)
{
HlsPlaylist
result
=
loadable
.
getResult
();
if
(
result
instanceof
HlsMediaPlaylist
)
{
processLoadedPlaylist
((
HlsMediaPlaylist
)
result
);
eventDispatcher
.
loadCompleted
(
loadable
.
dataSpec
,
C
.
DATA_TYPE_MANIFEST
,
elapsedRealtimeMs
,
loadDurationMs
,
loadable
.
bytesLoaded
());
}
else
{
playlistError
=
new
ParserException
(
"Loaded playlist has unexpected type."
);
}
}
@Override
public
void
onLoadCanceled
(
ParsingLoadable
<
HlsPlaylist
>
loadable
,
long
elapsedRealtimeMs
,
long
loadDurationMs
,
boolean
released
)
{
eventDispatcher
.
loadCanceled
(
loadable
.
dataSpec
,
C
.
DATA_TYPE_MANIFEST
,
elapsedRealtimeMs
,
loadDurationMs
,
loadable
.
bytesLoaded
());
}
@Override
public
@Loader
.
RetryAction
int
onLoadError
(
ParsingLoadable
<
HlsPlaylist
>
loadable
,
long
elapsedRealtimeMs
,
long
loadDurationMs
,
IOException
error
)
{
boolean
isFatal
=
error
instanceof
ParserException
;
eventDispatcher
.
loadError
(
loadable
.
dataSpec
,
C
.
DATA_TYPE_MANIFEST
,
elapsedRealtimeMs
,
loadDurationMs
,
loadable
.
bytesLoaded
(),
error
,
isFatal
);
boolean
shouldBlacklist
=
ChunkedTrackBlacklistUtil
.
shouldBlacklist
(
error
);
boolean
shouldRetryIfNotFatal
=
notifyPlaylistError
(
playlistUrl
,
shouldBlacklist
)
||
!
shouldBlacklist
;
if
(
isFatal
)
{
return
Loader
.
DONT_RETRY_FATAL
;
}
if
(
shouldBlacklist
)
{
shouldRetryIfNotFatal
|=
blacklistPlaylist
();
}
return
shouldRetryIfNotFatal
?
Loader
.
RETRY
:
Loader
.
DONT_RETRY
;
}
// Runnable implementation.
@Override
public
void
run
()
{
loadPending
=
false
;
loadPlaylistImmediately
();
}
// Internal methods.
private
void
loadPlaylistImmediately
()
{
mediaPlaylistLoader
.
startLoading
(
mediaPlaylistLoadable
,
this
,
minRetryCount
);
}
private
void
processLoadedPlaylist
(
HlsMediaPlaylist
loadedPlaylist
)
{
HlsMediaPlaylist
oldPlaylist
=
playlistSnapshot
;
long
currentTimeMs
=
SystemClock
.
elapsedRealtime
();
lastSnapshotLoadMs
=
currentTimeMs
;
playlistSnapshot
=
getLatestPlaylistSnapshot
(
oldPlaylist
,
loadedPlaylist
);
if
(
playlistSnapshot
!=
oldPlaylist
)
{
playlistError
=
null
;
lastSnapshotChangeMs
=
currentTimeMs
;
onPlaylistUpdated
(
playlistUrl
,
playlistSnapshot
);
}
else
if
(!
playlistSnapshot
.
hasEndTag
)
{
if
(
loadedPlaylist
.
mediaSequence
+
loadedPlaylist
.
segments
.
size
()
<
playlistSnapshot
.
mediaSequence
)
{
// The media sequence jumped backwards. The server has probably reset.
playlistError
=
new
PlaylistResetException
(
playlistUrl
.
url
);
notifyPlaylistError
(
playlistUrl
,
false
);
}
else
if
(
currentTimeMs
-
lastSnapshotChangeMs
>
C
.
usToMs
(
playlistSnapshot
.
targetDurationUs
)
*
PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT
)
{
// The playlist seems to be stuck. Blacklist it.
playlistError
=
new
PlaylistStuckException
(
playlistUrl
.
url
);
notifyPlaylistError
(
playlistUrl
,
true
);
blacklistPlaylist
();
}
}
// Do not allow the playlist to load again within the target duration if we obtained a new
// snapshot, or half the target duration otherwise.
earliestNextLoadTimeMs
=
currentTimeMs
+
C
.
usToMs
(
playlistSnapshot
!=
oldPlaylist
?
playlistSnapshot
.
targetDurationUs
:
(
playlistSnapshot
.
targetDurationUs
/
2
));
// Schedule a load if this is the primary playlist and it doesn't have an end tag. Else the
// next load will be scheduled when refreshPlaylist is called, or when this playlist becomes
// the primary.
if
(
playlistUrl
==
primaryHlsUrl
&&
!
playlistSnapshot
.
hasEndTag
)
{
loadPlaylist
();
}
}
/**
* Blacklists the playlist.
*
* @return Whether the playlist is the primary, despite being blacklisted.
*/
private
boolean
blacklistPlaylist
()
{
blacklistUntilMs
=
SystemClock
.
elapsedRealtime
()
+
ChunkedTrackBlacklistUtil
.
DEFAULT_TRACK_BLACKLIST_MS
;
return
primaryHlsUrl
==
playlistUrl
&&
!
maybeSelectNewPrimaryUrl
();
}
}
boolean
isLive
();
}
library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java
View file @
f7ed789f
...
...
@@ -15,6 +15,7 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
source
.
smoothstreaming
;
import
android.support.annotation.Nullable
;
import
android.util.Base64
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.SeekParameters
;
...
...
@@ -52,7 +53,7 @@ import java.util.ArrayList;
private
final
TrackEncryptionBox
[]
trackEncryptionBoxes
;
private
final
CompositeSequenceableLoaderFactory
compositeSequenceableLoaderFactory
;
private
Callback
callback
;
private
@Nullable
Callback
callback
;
private
SsManifest
manifest
;
private
ChunkSampleStream
<
SsChunkSource
>[]
sampleStreams
;
private
SequenceableLoader
compositeSequenceableLoader
;
...
...
@@ -98,6 +99,7 @@ import java.util.ArrayList;
for
(
ChunkSampleStream
<
SsChunkSource
>
sampleStream
:
sampleStreams
)
{
sampleStream
.
release
();
}
callback
=
null
;
eventDispatcher
.
mediaPeriodReleased
();
}
...
...
library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java
View file @
f7ed789f
...
...
@@ -251,7 +251,7 @@ public final class SubtitleView extends View implements TextOutput {
// Calculate the bounds after padding is taken into account.
int
left
=
getLeft
()
+
getPaddingLeft
();
int
top
=
rawTop
+
getPaddingTop
();
int
right
=
getRight
()
+
getPaddingRight
();
int
right
=
getRight
()
-
getPaddingRight
();
int
bottom
=
rawBottom
-
getPaddingBottom
();
if
(
bottom
<=
top
||
right
<=
left
)
{
// No space to draw subtitles.
...
...
library/ui/src/main/java/com/google/android/exoplayer2/ui/TrackSelectionView.java
View file @
f7ed789f
...
...
@@ -203,7 +203,9 @@ public class TrackSelectionView extends LinearLayout {
removeViewAt
(
i
);
}
if
(
trackSelector
==
null
)
{
MappingTrackSelector
.
MappedTrackInfo
trackInfo
=
trackSelector
==
null
?
null
:
trackSelector
.
getCurrentMappedTrackInfo
();
if
(
trackSelector
==
null
||
trackInfo
==
null
)
{
// The view is not initialized.
disableView
.
setEnabled
(
false
);
defaultView
.
setEnabled
(
false
);
...
...
@@ -212,7 +214,6 @@ public class TrackSelectionView extends LinearLayout {
disableView
.
setEnabled
(
true
);
defaultView
.
setEnabled
(
true
);
MappingTrackSelector
.
MappedTrackInfo
trackInfo
=
trackSelector
.
getCurrentMappedTrackInfo
();
trackGroups
=
trackInfo
.
getTrackGroups
(
rendererIndex
);
DefaultTrackSelector
.
Parameters
parameters
=
trackSelector
.
getParameters
();
...
...
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