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
8cbcfd66
authored
Aug 07, 2018
by
ojw28
Committed by
GitHub
Aug 07, 2018
Browse files
Options
_('Browse Files')
Download
Plain Diff
Merge pull request #4556 from google/dev-v2-r2.8.3
r2.8.3
parents
f7ed789f
563f13a8
Hide whitespace changes
Inline
Side-by-side
Showing
59 changed files
with
1954 additions
and
707 deletions
.idea/codeStyleSettings.xml
RELEASENOTES.md
constants.gradle
demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java
extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java
extensions/leanback/src/main/java/com/google/android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.java
extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java
library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java
library/core/src/main/java/com/google/android/exoplayer2/Format.java
library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java
library/core/src/main/java/com/google/android/exoplayer2/Player.java
library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java
library/core/src/main/java/com/google/android/exoplayer2/audio/SilenceSkippingAudioProcessor.java
library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java
library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java
library/core/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java
library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java
library/core/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java
library/core/src/main/java/com/google/android/exoplayer2/drm/LocalMediaDrmCallback.java
library/core/src/main/java/com/google/android/exoplayer2/drm/MediaDrmCallback.java
library/core/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfoHolder.java
library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java
library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.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/MetadataUtil.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/TrackSampleTable.java
library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java
library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java
library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/InternalFrame.java
library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java
library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java
library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java
library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java
library/core/src/main/java/com/google/android/exoplayer2/util/TimestampAdjuster.java
library/core/src/main/java/com/google/android/exoplayer2/util/XmlPullParserUtil.java
library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java
library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java
library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.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/DashMediaSource.java
library/dash/src/main/java/com/google/android/exoplayer2/source/dash/EventSampleStream.java
library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.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/HlsSampleStreamWrapper.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/hls/src/test/java/com/google/android/exoplayer2/source/hls/Aes128DataSourceTest.java
library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java
library/ui/src/main/java/com/google/android/exoplayer2/ui/DownloadNotificationUtil.java
library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java
library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java
library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java
library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java
library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java
library/ui/src/main/res/drawable-ldpi/exo_controls_fullscreen_enter.png
library/ui/src/main/res/drawable-ldpi/exo_controls_fullscreen_exit.png
testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java
testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java
.idea/codeStyleSettings.xml
0 → 100644
View file @
8cbcfd66
<?xml version="1.0" encoding="UTF-8"?>
<project
version=
"4"
>
<component
name=
"ProjectCodeStyleSettingsManager"
>
<option
name=
"PER_PROJECT_SETTINGS"
>
<value>
<option
name=
"OTHER_INDENT_OPTIONS"
>
<value>
<option
name=
"INDENT_SIZE"
value=
"2"
/>
<option
name=
"CONTINUATION_INDENT_SIZE"
value=
"4"
/>
<option
name=
"TAB_SIZE"
value=
"2"
/>
<option
name=
"USE_TAB_CHARACTER"
value=
"false"
/>
<option
name=
"SMART_TABS"
value=
"false"
/>
<option
name=
"LABEL_INDENT_SIZE"
value=
"0"
/>
<option
name=
"LABEL_INDENT_ABSOLUTE"
value=
"false"
/>
<option
name=
"USE_RELATIVE_INDENTS"
value=
"false"
/>
</value>
</option>
<option
name=
"CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND"
value=
"999"
/>
<option
name=
"NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND"
value=
"999"
/>
<option
name=
"PACKAGES_TO_USE_IMPORT_ON_DEMAND"
>
<value
/>
</option>
<option
name=
"IMPORT_LAYOUT_TABLE"
>
<value>
<package
name=
""
withSubpackages=
"true"
static=
"true"
/>
<emptyLine
/>
<package
name=
""
withSubpackages=
"true"
static=
"false"
/>
</value>
</option>
<option
name=
"RIGHT_MARGIN"
value=
"100"
/>
<option
name=
"JD_ALIGN_PARAM_COMMENTS"
value=
"false"
/>
<option
name=
"JD_ALIGN_EXCEPTION_COMMENTS"
value=
"false"
/>
<option
name=
"JD_P_AT_EMPTY_LINES"
value=
"false"
/>
<option
name=
"JD_KEEP_EMPTY_PARAMETER"
value=
"false"
/>
<option
name=
"JD_KEEP_EMPTY_EXCEPTION"
value=
"false"
/>
<option
name=
"JD_KEEP_EMPTY_RETURN"
value=
"false"
/>
<option
name=
"KEEP_CONTROL_STATEMENT_IN_ONE_LINE"
value=
"false"
/>
<option
name=
"KEEP_BLANK_LINES_IN_CODE"
value=
"1"
/>
<option
name=
"KEEP_BLANK_LINES_BEFORE_RBRACE"
value=
"0"
/>
<option
name=
"ALIGN_MULTILINE_PARAMETERS"
value=
"false"
/>
<option
name=
"ALIGN_MULTILINE_FOR"
value=
"false"
/>
<option
name=
"SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE"
value=
"true"
/>
<option
name=
"CALL_PARAMETERS_WRAP"
value=
"1"
/>
<option
name=
"METHOD_PARAMETERS_WRAP"
value=
"1"
/>
<option
name=
"EXTENDS_LIST_WRAP"
value=
"1"
/>
<option
name=
"THROWS_KEYWORD_WRAP"
value=
"1"
/>
<option
name=
"METHOD_CALL_CHAIN_WRAP"
value=
"1"
/>
<option
name=
"BINARY_OPERATION_WRAP"
value=
"1"
/>
<option
name=
"BINARY_OPERATION_SIGN_ON_NEXT_LINE"
value=
"true"
/>
<option
name=
"TERNARY_OPERATION_WRAP"
value=
"1"
/>
<option
name=
"TERNARY_OPERATION_SIGNS_ON_NEXT_LINE"
value=
"true"
/>
<option
name=
"FOR_STATEMENT_WRAP"
value=
"1"
/>
<option
name=
"ARRAY_INITIALIZER_WRAP"
value=
"1"
/>
<option
name=
"WRAP_COMMENTS"
value=
"true"
/>
<option
name=
"IF_BRACE_FORCE"
value=
"3"
/>
<option
name=
"DOWHILE_BRACE_FORCE"
value=
"3"
/>
<option
name=
"WHILE_BRACE_FORCE"
value=
"3"
/>
<option
name=
"FOR_BRACE_FORCE"
value=
"3"
/>
<AndroidXmlCodeStyleSettings>
<option
name=
"USE_CUSTOM_SETTINGS"
value=
"true"
/>
<option
name=
"LAYOUT_SETTINGS"
>
<value>
<option
name=
"INSERT_BLANK_LINE_BEFORE_TAG"
value=
"false"
/>
</value>
</option>
</AndroidXmlCodeStyleSettings>
<Objective-C>
<option
name=
"INDENT_NAMESPACE_MEMBERS"
value=
"0"
/>
<option
name=
"INDENT_C_STRUCT_MEMBERS"
value=
"2"
/>
<option
name=
"INDENT_CLASS_MEMBERS"
value=
"2"
/>
<option
name=
"INDENT_VISIBILITY_KEYWORDS"
value=
"1"
/>
<option
name=
"INDENT_INSIDE_CODE_BLOCK"
value=
"2"
/>
<option
name=
"KEEP_STRUCTURES_IN_ONE_LINE"
value=
"true"
/>
<option
name=
"FUNCTION_PARAMETERS_WRAP"
value=
"5"
/>
<option
name=
"FUNCTION_CALL_ARGUMENTS_WRAP"
value=
"5"
/>
<option
name=
"TEMPLATE_CALL_ARGUMENTS_WRAP"
value=
"5"
/>
<option
name=
"TEMPLATE_CALL_ARGUMENTS_ALIGN_MULTILINE"
value=
"true"
/>
<option
name=
"ALIGN_INIT_LIST_IN_COLUMNS"
value=
"false"
/>
<option
name=
"SPACE_BEFORE_SUPERCLASS_COLON"
value=
"false"
/>
</Objective-C>
<Objective-C-extensions>
<file>
<option
name=
"com.jetbrains.cidr.lang.util.OCDeclarationKind"
value=
"Import"
/>
<option
name=
"com.jetbrains.cidr.lang.util.OCDeclarationKind"
value=
"Macro"
/>
<option
name=
"com.jetbrains.cidr.lang.util.OCDeclarationKind"
value=
"Typedef"
/>
<option
name=
"com.jetbrains.cidr.lang.util.OCDeclarationKind"
value=
"Enum"
/>
<option
name=
"com.jetbrains.cidr.lang.util.OCDeclarationKind"
value=
"Constant"
/>
<option
name=
"com.jetbrains.cidr.lang.util.OCDeclarationKind"
value=
"Global"
/>
<option
name=
"com.jetbrains.cidr.lang.util.OCDeclarationKind"
value=
"Struct"
/>
<option
name=
"com.jetbrains.cidr.lang.util.OCDeclarationKind"
value=
"FunctionPredecl"
/>
<option
name=
"com.jetbrains.cidr.lang.util.OCDeclarationKind"
value=
"Function"
/>
</file>
<class>
<option
name=
"com.jetbrains.cidr.lang.util.OCDeclarationKind"
value=
"Property"
/>
<option
name=
"com.jetbrains.cidr.lang.util.OCDeclarationKind"
value=
"Synthesize"
/>
<option
name=
"com.jetbrains.cidr.lang.util.OCDeclarationKind"
value=
"InitMethod"
/>
<option
name=
"com.jetbrains.cidr.lang.util.OCDeclarationKind"
value=
"StaticMethod"
/>
<option
name=
"com.jetbrains.cidr.lang.util.OCDeclarationKind"
value=
"InstanceMethod"
/>
<option
name=
"com.jetbrains.cidr.lang.util.OCDeclarationKind"
value=
"DeallocMethod"
/>
</class>
<extensions>
<pair
source=
"cc"
header=
"h"
/>
<pair
source=
"c"
header=
"h"
/>
</extensions>
</Objective-C-extensions>
<XML>
<option
name=
"XML_ALIGN_ATTRIBUTES"
value=
"false"
/>
<option
name=
"XML_LEGACY_SETTINGS_IMPORTED"
value=
"true"
/>
</XML>
<codeStyleSettings
language=
"HTML"
>
<indentOptions>
<option
name=
"INDENT_SIZE"
value=
"2"
/>
<option
name=
"CONTINUATION_INDENT_SIZE"
value=
"4"
/>
<option
name=
"TAB_SIZE"
value=
"2"
/>
</indentOptions>
</codeStyleSettings>
<codeStyleSettings
language=
"JAVA"
>
<option
name=
"KEEP_CONTROL_STATEMENT_IN_ONE_LINE"
value=
"false"
/>
<option
name=
"KEEP_BLANK_LINES_IN_CODE"
value=
"1"
/>
<option
name=
"BLANK_LINES_AFTER_CLASS_HEADER"
value=
"1"
/>
<option
name=
"ALIGN_MULTILINE_PARAMETERS"
value=
"false"
/>
<option
name=
"ALIGN_MULTILINE_RESOURCES"
value=
"false"
/>
<option
name=
"ALIGN_MULTILINE_FOR"
value=
"false"
/>
<option
name=
"CALL_PARAMETERS_WRAP"
value=
"1"
/>
<option
name=
"METHOD_PARAMETERS_WRAP"
value=
"1"
/>
<option
name=
"EXTENDS_LIST_WRAP"
value=
"1"
/>
<option
name=
"THROWS_KEYWORD_WRAP"
value=
"1"
/>
<option
name=
"METHOD_CALL_CHAIN_WRAP"
value=
"1"
/>
<option
name=
"BINARY_OPERATION_WRAP"
value=
"1"
/>
<option
name=
"BINARY_OPERATION_SIGN_ON_NEXT_LINE"
value=
"true"
/>
<option
name=
"TERNARY_OPERATION_WRAP"
value=
"1"
/>
<option
name=
"TERNARY_OPERATION_SIGNS_ON_NEXT_LINE"
value=
"true"
/>
<option
name=
"FOR_STATEMENT_WRAP"
value=
"1"
/>
<option
name=
"ARRAY_INITIALIZER_WRAP"
value=
"1"
/>
<option
name=
"IF_BRACE_FORCE"
value=
"3"
/>
<option
name=
"DOWHILE_BRACE_FORCE"
value=
"3"
/>
<option
name=
"WHILE_BRACE_FORCE"
value=
"3"
/>
<option
name=
"FOR_BRACE_FORCE"
value=
"3"
/>
<option
name=
"PARENT_SETTINGS_INSTALLED"
value=
"true"
/>
<indentOptions>
<option
name=
"INDENT_SIZE"
value=
"2"
/>
<option
name=
"CONTINUATION_INDENT_SIZE"
value=
"4"
/>
<option
name=
"TAB_SIZE"
value=
"2"
/>
</indentOptions>
</codeStyleSettings>
<codeStyleSettings
language=
"JSON"
>
<indentOptions>
<option
name=
"CONTINUATION_INDENT_SIZE"
value=
"4"
/>
<option
name=
"TAB_SIZE"
value=
"2"
/>
</indentOptions>
</codeStyleSettings>
<codeStyleSettings
language=
"ObjectiveC"
>
<option
name=
"KEEP_BLANK_LINES_BEFORE_RBRACE"
value=
"1"
/>
<option
name=
"BLANK_LINES_BEFORE_IMPORTS"
value=
"0"
/>
<option
name=
"BLANK_LINES_AFTER_IMPORTS"
value=
"0"
/>
<option
name=
"BLANK_LINES_AROUND_CLASS"
value=
"0"
/>
<option
name=
"BLANK_LINES_AROUND_METHOD"
value=
"0"
/>
<option
name=
"BLANK_LINES_AROUND_METHOD_IN_INTERFACE"
value=
"0"
/>
<option
name=
"ALIGN_MULTILINE_BINARY_OPERATION"
value=
"false"
/>
<option
name=
"BINARY_OPERATION_SIGN_ON_NEXT_LINE"
value=
"true"
/>
<option
name=
"FOR_STATEMENT_WRAP"
value=
"1"
/>
<option
name=
"ASSIGNMENT_WRAP"
value=
"1"
/>
<indentOptions>
<option
name=
"INDENT_SIZE"
value=
"2"
/>
<option
name=
"CONTINUATION_INDENT_SIZE"
value=
"4"
/>
</indentOptions>
</codeStyleSettings>
<codeStyleSettings
language=
"XML"
>
<indentOptions>
<option
name=
"INDENT_SIZE"
value=
"2"
/>
<option
name=
"CONTINUATION_INDENT_SIZE"
value=
"2"
/>
<option
name=
"TAB_SIZE"
value=
"2"
/>
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>
xmlns:android
</NAME>
<XML_ATTRIBUTE
/>
<XML_NAMESPACE>
^$
</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>
xmlns:.*
</NAME>
<XML_ATTRIBUTE
/>
<XML_NAMESPACE>
^$
</XML_NAMESPACE>
</AND>
</match>
<order>
BY_NAME
</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>
.*:id
</NAME>
<XML_ATTRIBUTE
/>
<XML_NAMESPACE>
http://schemas.android.com/apk/res/android
</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>
style
</NAME>
<XML_ATTRIBUTE
/>
<XML_NAMESPACE>
^$
</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>
.*
</NAME>
<XML_ATTRIBUTE
/>
<XML_NAMESPACE>
^$
</XML_NAMESPACE>
</AND>
</match>
<order>
BY_NAME
</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>
.*:.*Style
</NAME>
<XML_ATTRIBUTE
/>
<XML_NAMESPACE>
http://schemas.android.com/apk/res/android
</XML_NAMESPACE>
</AND>
</match>
<order>
BY_NAME
</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>
.*:layout_width
</NAME>
<XML_ATTRIBUTE
/>
<XML_NAMESPACE>
http://schemas.android.com/apk/res/android
</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>
.*:layout_height
</NAME>
<XML_ATTRIBUTE
/>
<XML_NAMESPACE>
http://schemas.android.com/apk/res/android
</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>
.*:layout_weight
</NAME>
<XML_ATTRIBUTE
/>
<XML_NAMESPACE>
http://schemas.android.com/apk/res/android
</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>
.*:layout_margin
</NAME>
<XML_ATTRIBUTE
/>
<XML_NAMESPACE>
http://schemas.android.com/apk/res/android
</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>
.*:layout_marginTop
</NAME>
<XML_ATTRIBUTE
/>
<XML_NAMESPACE>
http://schemas.android.com/apk/res/android
</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>
.*:layout_marginBottom
</NAME>
<XML_ATTRIBUTE
/>
<XML_NAMESPACE>
http://schemas.android.com/apk/res/android
</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>
.*:layout_marginStart
</NAME>
<XML_ATTRIBUTE
/>
<XML_NAMESPACE>
http://schemas.android.com/apk/res/android
</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>
.*:layout_marginEnd
</NAME>
<XML_ATTRIBUTE
/>
<XML_NAMESPACE>
http://schemas.android.com/apk/res/android
</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>
.*:layout_marginLeft
</NAME>
<XML_ATTRIBUTE
/>
<XML_NAMESPACE>
http://schemas.android.com/apk/res/android
</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>
.*:layout_marginRight
</NAME>
<XML_ATTRIBUTE
/>
<XML_NAMESPACE>
http://schemas.android.com/apk/res/android
</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>
.*:layout_.*
</NAME>
<XML_ATTRIBUTE
/>
<XML_NAMESPACE>
http://schemas.android.com/apk/res/android
</XML_NAMESPACE>
</AND>
</match>
<order>
BY_NAME
</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>
.*:padding
</NAME>
<XML_ATTRIBUTE
/>
<XML_NAMESPACE>
http://schemas.android.com/apk/res/android
</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>
.*:paddingTop
</NAME>
<XML_ATTRIBUTE
/>
<XML_NAMESPACE>
http://schemas.android.com/apk/res/android
</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>
.*:paddingBottom
</NAME>
<XML_ATTRIBUTE
/>
<XML_NAMESPACE>
http://schemas.android.com/apk/res/android
</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>
.*:paddingStart
</NAME>
<XML_ATTRIBUTE
/>
<XML_NAMESPACE>
http://schemas.android.com/apk/res/android
</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>
.*:paddingEnd
</NAME>
<XML_ATTRIBUTE
/>
<XML_NAMESPACE>
http://schemas.android.com/apk/res/android
</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>
.*:paddingLeft
</NAME>
<XML_ATTRIBUTE
/>
<XML_NAMESPACE>
http://schemas.android.com/apk/res/android
</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>
.*:paddingRight
</NAME>
<XML_ATTRIBUTE
/>
<XML_NAMESPACE>
http://schemas.android.com/apk/res/android
</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>
.*
</NAME>
<XML_NAMESPACE>
http://schemas.android.com/apk/res/android
</XML_NAMESPACE>
</AND>
</match>
<order>
BY_NAME
</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>
.*
</NAME>
<XML_NAMESPACE>
http://schemas.android.com/apk/res-auto
</XML_NAMESPACE>
</AND>
</match>
<order>
BY_NAME
</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>
.*
</NAME>
<XML_NAMESPACE>
http://schemas.android.com/tools
</XML_NAMESPACE>
</AND>
</match>
<order>
BY_NAME
</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>
.*
</NAME>
<XML_NAMESPACE>
.*
</XML_NAMESPACE>
</AND>
</match>
<order>
BY_NAME
</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
</value>
</option>
<option
name=
"USE_PER_PROJECT_SETTINGS"
value=
"true"
/>
</component>
</project>
RELEASENOTES.md
View file @
8cbcfd66
# Release notes #
### 2.8.3 ###
*
IMA:
*
Fix behavior when creating/releasing the player then releasing
`ImaAdsLoader`
(
[
#3879
](
https://github.com/google/ExoPlayer/issues/3879
)
).
*
Add support for setting slots for companion ads.
*
Captions:
*
TTML: Fix an issue with TTML using font size as % of cell resolution that
makes
`SubtitleView.setApplyEmbeddedFontSizes()`
not work correctly.
(
[
#4491
](
https://github.com/google/ExoPlayer/issues/4491
)
).
*
CEA-608: Improve handling of embedded styles
(
[
#4321
](
https://github.com/google/ExoPlayer/issues/4321
)
).
*
DASH:
*
Exclude text streams from duration calculations
(
[
#4029
](
https://github.com/google/ExoPlayer/issues/4029
)
).
*
Fix freezing when playing multi-period manifests with
`EventStream`
s
(
[
#4492
](
https://github.com/google/ExoPlayer/issues/4492
)
).
*
DRM: Allow DrmInitData to carry a license server URL
(
[
#3393
](
https://github.com/google/ExoPlayer/issues/3393
)
).
*
MPEG-TS: Fix bug preventing SCTE-35 cues from being output
(
[
#4573
](
https://github.com/google/ExoPlayer/issues/4573
)
).
*
Expose all internal ID3 data stored in MP4 udta boxes, and switch from using
CommentFrame to InternalFrame for frames with gapless metadata in MP4.
*
Add
`PlayerView.isControllerVisible`
(
[
#4385
](
https://github.com/google/ExoPlayer/issues/4385
)
).
*
Fix issue playing DRM protected streams on Asus Zenfone 2
(
[
#4403
](
https://github.com/google/ExoPlayer/issues/4413
)
).
*
Add support for multiple audio and video tracks in MPEG-PS streams
(
[
#4406
](
https://github.com/google/ExoPlayer/issues/4406
)
).
*
Add workaround for track index mismatches between trex and tkhd boxes in
fragmented MP4 files
(
[
#4477
](
https://github.com/google/ExoPlayer/issues/4477
)
).
*
Add workaround for track index mismatches between tfhd and tkhd boxes in
fragmented MP4 files
(
[
#4083
](
https://github.com/google/ExoPlayer/issues/4083
)
).
*
Ignore all MP4 edit lists if one edit list couldn't be handled
(
[
#4348
](
https://github.com/google/ExoPlayer/issues/4348
)
).
*
Fix issue when switching track selection from an embedded track to a primary
track in DASH (
[
#4477
](
https://github.com/google/ExoPlayer/issues/4477
)
).
*
Fix accessibility class name for
`DefaultTimeBar`
(
[
#4611
](
https://github.com/google/ExoPlayer/issues/4611
)
).
*
Improved compatibility with FireOS devices.
### 2.8.2 ###
*
IMA: Don't advertise support for video/mpeg ad media, as we don't have an
...
...
constants.gradle
View file @
8cbcfd66
...
...
@@ -13,8 +13,8 @@
// limitations under the License.
project
.
ext
{
// ExoPlayer version and version code.
releaseVersion
=
'2.8.
2
'
releaseVersionCode
=
280
2
releaseVersion
=
'2.8.
3
'
releaseVersionCode
=
280
3
// 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
...
...
demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java
View file @
8cbcfd66
...
...
@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.castdemo;
import
android.content.Context
;
import
android.net.Uri
;
import
android.support.annotation.Nullable
;
import
android.view.KeyEvent
;
import
android.view.View
;
import
com.google.android.exoplayer2.C
;
...
...
@@ -282,7 +283,7 @@ import java.util.ArrayList;
@Override
public
void
onTimelineChanged
(
Timeline
timeline
,
Object
manifest
,
@TimelineChangeReason
int
reason
)
{
Timeline
timeline
,
@Nullable
Object
manifest
,
@TimelineChangeReason
int
reason
)
{
updateCurrentItemIndex
();
if
(
timeline
.
isEmpty
())
{
castMediaQueueCreationPending
=
true
;
...
...
extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java
View file @
8cbcfd66
...
...
@@ -38,6 +38,7 @@ import com.google.ads.interactivemedia.v3.api.AdsManager;
import
com.google.ads.interactivemedia.v3.api.AdsManagerLoadedEvent
;
import
com.google.ads.interactivemedia.v3.api.AdsRenderingSettings
;
import
com.google.ads.interactivemedia.v3.api.AdsRequest
;
import
com.google.ads.interactivemedia.v3.api.CompanionAdSlot
;
import
com.google.ads.interactivemedia.v3.api.ImaSdkFactory
;
import
com.google.ads.interactivemedia.v3.api.ImaSdkSettings
;
import
com.google.ads.interactivemedia.v3.api.player.ContentProgressProvider
;
...
...
@@ -62,6 +63,7 @@ import java.lang.annotation.Retention;
import
java.lang.annotation.RetentionPolicy
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.Collection
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.Map
;
...
...
@@ -267,13 +269,9 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
/** The expected ad group index that IMA should load next. */
private
int
expectedAdGroupIndex
;
/**
* The index of the current ad group that IMA is loading.
*/
/** The index of the current ad group that IMA is loading. */
private
int
adGroupIndex
;
/**
* Whether IMA has sent an ad event to pause content since the last resume content event.
*/
/** Whether IMA has sent an ad event to pause content since the last resume content event. */
private
boolean
imaPausedContent
;
/** The current ad playback state. */
private
@ImaAdState
int
imaAdState
;
...
...
@@ -285,9 +283,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
// Fields tracking the player/loader state.
/**
* Whether the player is playing an ad.
*/
/** Whether the player is playing an ad. */
private
boolean
playingAd
;
/**
* If the player is playing an ad, stores the ad index in its ad group. {@link C#INDEX_UNSET}
...
...
@@ -310,13 +306,9 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
* content progress should increase. {@link C#TIME_UNSET} otherwise.
*/
private
long
fakeContentProgressOffsetMs
;
/**
* Stores the pending content position when a seek operation was intercepted to play an ad.
*/
/** Stores the pending content position when a seek operation was intercepted to play an ad. */
private
long
pendingContentPositionMs
;
/**
* Whether {@link #getContentProgress()} has sent {@link #pendingContentPositionMs} to IMA.
*/
/** Whether {@link #getContentProgress()} has sent {@link #pendingContentPositionMs} to IMA. */
private
boolean
sentPendingContentPositionMs
;
/**
...
...
@@ -406,6 +398,17 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
}
/**
* Sets the slots for displaying companion ads. Individual slots can be created using {@link
* ImaSdkFactory#createCompanionAdSlot()}.
*
* @param companionSlots Slots for displaying companion ads.
* @see AdDisplayContainer#setCompanionSlots(Collection)
*/
public
void
setCompanionSlots
(
Collection
<
CompanionAdSlot
>
companionSlots
)
{
adDisplayContainer
.
setCompanionSlots
(
companionSlots
);
}
/**
* Requests ads, if they have not already been requested. Must be called on the main thread.
*
* <p>Ads will be requested automatically when the player is prepared if this method has not been
...
...
@@ -509,6 +512,11 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
adsManager
.
destroy
();
adsManager
=
null
;
}
imaPausedContent
=
false
;
imaAdState
=
IMA_AD_STATE_NONE
;
pendingAdLoadError
=
null
;
adPlaybackState
=
AdPlaybackState
.
NONE
;
updateAdPlaybackState
();
}
@Override
...
...
@@ -558,7 +566,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
Log
.
d
(
TAG
,
"onAdEvent: "
+
adEventType
);
}
if
(
adsManager
==
null
)
{
Log
.
w
(
TAG
,
"
Dropping ad e
vent after release: "
+
adEvent
);
Log
.
w
(
TAG
,
"
Ignoring AdE
vent after release: "
+
adEvent
);
return
;
}
try
{
...
...
@@ -654,6 +662,13 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
@Override
public
void
loadAd
(
String
adUriString
)
{
try
{
if
(
DEBUG
)
{
Log
.
d
(
TAG
,
"loadAd in ad group "
+
adGroupIndex
);
}
if
(
adsManager
==
null
)
{
Log
.
w
(
TAG
,
"Ignoring loadAd after release"
);
return
;
}
if
(
adGroupIndex
==
C
.
INDEX_UNSET
)
{
Log
.
w
(
TAG
,
...
...
@@ -662,9 +677,6 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
adGroupIndex
=
expectedAdGroupIndex
;
adsManager
.
start
();
}
if
(
DEBUG
)
{
Log
.
d
(
TAG
,
"loadAd in ad group "
+
adGroupIndex
);
}
int
adIndexInAdGroup
=
getAdIndexInAdGroupToLoad
(
adGroupIndex
);
if
(
adIndexInAdGroup
==
C
.
INDEX_UNSET
)
{
Log
.
w
(
TAG
,
"Unexpected loadAd in an ad group with no remaining unavailable ads"
);
...
...
@@ -693,6 +705,10 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
if
(
DEBUG
)
{
Log
.
d
(
TAG
,
"playAd"
);
}
if
(
adsManager
==
null
)
{
Log
.
w
(
TAG
,
"Ignoring playAd after release"
);
return
;
}
switch
(
imaAdState
)
{
case
IMA_AD_STATE_PLAYING:
// IMA does not always call stopAd before resuming content.
...
...
@@ -736,6 +752,10 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
if
(
DEBUG
)
{
Log
.
d
(
TAG
,
"stopAd"
);
}
if
(
adsManager
==
null
)
{
Log
.
w
(
TAG
,
"Ignoring stopAd after release"
);
return
;
}
if
(
player
==
null
)
{
// Sometimes messages from IMA arrive after detaching the player. See [Internal: b/63801642].
Log
.
w
(
TAG
,
"Unexpected stopAd while detached"
);
...
...
@@ -775,8 +795,8 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
// Player.EventListener implementation.
@Override
public
void
onTimelineChanged
(
Timeline
timeline
,
Object
manifest
,
@Player
.
TimelineChangeReason
int
reason
)
{
public
void
onTimelineChanged
(
Timeline
timeline
,
@Nullable
Object
manifest
,
@Player
.
TimelineChangeReason
int
reason
)
{
if
(
reason
==
Player
.
TIMELINE_CHANGE_REASON_RESET
)
{
// The player is being reset and this source will be released.
return
;
...
...
@@ -1083,6 +1103,10 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
Log
.
d
(
TAG
,
"Prepare error for ad "
+
adIndexInAdGroup
+
" in group "
+
adGroupIndex
,
exception
);
}
if
(
adsManager
==
null
)
{
Log
.
w
(
TAG
,
"Ignoring ad prepare error after release"
);
return
;
}
if
(
imaAdState
==
IMA_AD_STATE_NONE
)
{
// Send IMA a content position at the ad group so that it will try to play it, at which point
// we can notify that it failed to load.
...
...
@@ -1165,7 +1189,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
Log
.
e
(
TAG
,
message
,
cause
);
// We can't recover from an unexpected error in general, so skip all remaining ads.
if
(
adPlaybackState
==
null
)
{
adPlaybackState
=
new
AdPlaybackState
()
;
adPlaybackState
=
AdPlaybackState
.
NONE
;
}
else
{
for
(
int
i
=
0
;
i
<
adPlaybackState
.
adGroupCount
;
i
++)
{
adPlaybackState
=
adPlaybackState
.
withSkippedAdGroup
(
i
);
...
...
extensions/leanback/src/main/java/com/google/android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.java
View file @
8cbcfd66
...
...
@@ -281,8 +281,8 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter {
}
@Override
public
void
onTimelineChanged
(
Timeline
timeline
,
Object
manifest
,
@TimelineChangeReason
int
reason
)
{
public
void
onTimelineChanged
(
Timeline
timeline
,
@Nullable
Object
manifest
,
@TimelineChangeReason
int
reason
)
{
Callback
callback
=
getCallback
();
callback
.
onDurationChanged
(
LeanbackPlayerAdapter
.
this
);
callback
.
onCurrentPositionChanged
(
LeanbackPlayerAdapter
.
this
);
...
...
extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java
View file @
8cbcfd66
...
...
@@ -674,8 +674,8 @@ public final class MediaSessionConnector {
private
int
currentWindowCount
;
@Override
public
void
onTimelineChanged
(
Timeline
timeline
,
Object
manifest
,
@Player
.
TimelineChangeReason
int
reason
)
{
public
void
onTimelineChanged
(
Timeline
timeline
,
@Nullable
Object
manifest
,
@Player
.
TimelineChangeReason
int
reason
)
{
int
windowCount
=
player
.
getCurrentTimeline
().
getWindowCount
();
int
windowIndex
=
player
.
getCurrentWindowIndex
();
if
(
queueNavigator
!=
null
)
{
...
...
library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java
View file @
8cbcfd66
...
...
@@ -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.
2
"
;
public
static
final
String
VERSION
=
"2.8.
3
"
;
/** 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.
2
"
;
public
static
final
String
VERSION_SLASHY
=
"ExoPlayerLib/2.8.
3
"
;
/**
* 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
2
;
public
static
final
int
VERSION_INT
=
200800
3
;
/**
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}
...
...
library/core/src/main/java/com/google/android/exoplayer2/Format.java
View file @
8cbcfd66
...
...
@@ -80,6 +80,13 @@ public final class Format implements Parcelable {
/** DRM initialization data if the stream is protected, or null otherwise. */
public
final
@Nullable
DrmInitData
drmInitData
;
/**
* For samples that contain subsamples, this is an offset that should be added to subsample
* timestamps. A value of {@link #OFFSET_SAMPLE_RELATIVE} indicates that subsample timestamps are
* relative to the timestamps of their parent samples.
*/
public
final
long
subsampleOffsetUs
;
// Video specific.
/**
...
...
@@ -141,15 +148,6 @@ public final class Format implements Parcelable {
*/
public
final
int
encoderPadding
;
// Text specific.
/**
* For samples that contain subsamples, this is an offset that should be added to subsample
* timestamps. A value of {@link #OFFSET_SAMPLE_RELATIVE} indicates that subsample timestamps are
* relative to the timestamps of their parent samples.
*/
public
final
long
subsampleOffsetUs
;
// Audio and text specific.
/**
...
...
library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java
View file @
8cbcfd66
...
...
@@ -228,11 +228,13 @@ import com.google.android.exoplayer2.util.Assertions;
reading
=
playing
.
next
;
}
playing
.
release
();
playing
=
playing
.
next
;
length
--;
if
(
length
==
0
)
{
loading
=
null
;
oldFrontPeriodUid
=
playing
.
uid
;
oldFrontPeriodWindowSequenceNumber
=
playing
.
info
.
id
.
windowSequenceNumber
;
}
playing
=
playing
.
next
;
}
else
{
playing
=
loading
;
reading
=
loading
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/Player.java
View file @
8cbcfd66
...
...
@@ -191,7 +191,8 @@ public interface Player {
* @param manifest The latest manifest. May be null.
* @param reason The {@link TimelineChangeReason} responsible for this timeline change.
*/
void
onTimelineChanged
(
Timeline
timeline
,
Object
manifest
,
@TimelineChangeReason
int
reason
);
void
onTimelineChanged
(
Timeline
timeline
,
@Nullable
Object
manifest
,
@TimelineChangeReason
int
reason
);
/**
* Called when the available or selected tracks change.
...
...
@@ -281,8 +282,8 @@ public interface Player {
abstract
class
DefaultEventListener
implements
EventListener
{
@Override
public
void
onTimelineChanged
(
Timeline
timeline
,
Object
manifest
,
@TimelineChangeReason
int
reason
)
{
public
void
onTimelineChanged
(
Timeline
timeline
,
@Nullable
Object
manifest
,
@TimelineChangeReason
int
reason
)
{
// Call deprecated version. Otherwise, do nothing.
onTimelineChanged
(
timeline
,
manifest
);
}
...
...
@@ -337,7 +338,7 @@ public interface Player {
* instead.
*/
@Deprecated
public
void
onTimelineChanged
(
Timeline
timeline
,
Object
manifest
)
{
public
void
onTimelineChanged
(
Timeline
timeline
,
@Nullable
Object
manifest
)
{
// Do nothing.
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java
View file @
8cbcfd66
...
...
@@ -420,7 +420,7 @@ public class AnalyticsCollector
@Override
public
final
void
onTimelineChanged
(
Timeline
timeline
,
Object
manifest
,
@Player
.
TimelineChangeReason
int
reason
)
{
Timeline
timeline
,
@Nullable
Object
manifest
,
@Player
.
TimelineChangeReason
int
reason
)
{
mediaPeriodQueueTracker
.
onTimelineChanged
(
timeline
);
EventTime
eventTime
=
generatePlayingMediaPeriodEventTime
();
for
(
AnalyticsListener
listener
:
listeners
)
{
...
...
library/core/src/main/java/com/google/android/exoplayer2/audio/SilenceSkippingAudioProcessor.java
View file @
8cbcfd66
...
...
@@ -33,12 +33,12 @@ public final class SilenceSkippingAudioProcessor implements AudioProcessor {
* The minimum duration of audio that must be below {@link #SILENCE_THRESHOLD_LEVEL} to classify
* that part of audio as silent, in microseconds.
*/
private
static
final
long
MINIMUM_SILENCE_DURATION_US
=
1
0
0_000
;
private
static
final
long
MINIMUM_SILENCE_DURATION_US
=
1
5
0_000
;
/**
* The duration of silence by which to extend non-silent sections, in microseconds. The value must
* not exceed {@link #MINIMUM_SILENCE_DURATION_US}.
*/
private
static
final
long
PADDING_SILENCE_US
=
1
0_000
;
private
static
final
long
PADDING_SILENCE_US
=
2
0_000
;
/**
* The absolute level below which an individual PCM sample is classified as silent. Note: the
* specified value will be rounded so that the threshold check only depends on the more
...
...
library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java
View file @
8cbcfd66
...
...
@@ -22,11 +22,12 @@ import android.os.Handler;
import
android.os.HandlerThread
;
import
android.os.Looper
;
import
android.os.Message
;
import
android.support.annotation.Nullable
;
import
android.util.Log
;
import
android.util.Pair
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.drm.DefaultDrmSessionEventListener.EventDispatcher
;
import
com.google.android.exoplayer2.drm.
ExoMediaDrm.DefaultKeyRequest
;
import
com.google.android.exoplayer2.drm.
DrmInitData.SchemeData
;
import
com.google.android.exoplayer2.drm.ExoMediaDrm.KeyRequest
;
import
com.google.android.exoplayer2.drm.ExoMediaDrm.ProvisionRequest
;
import
java.util.Arrays
;
...
...
@@ -77,8 +78,7 @@ import java.util.UUID;
private
final
ExoMediaDrm
<
T
>
mediaDrm
;
private
final
ProvisioningManager
<
T
>
provisioningManager
;
private
final
byte
[]
initData
;
private
final
String
mimeType
;
private
final
SchemeData
schemeData
;
private
final
@DefaultDrmSessionManager
.
Mode
int
mode
;
private
final
HashMap
<
String
,
String
>
optionalKeyRequestParameters
;
private
final
EventDispatcher
eventDispatcher
;
...
...
@@ -97,15 +97,20 @@ import java.util.UUID;
private
byte
[]
sessionId
;
private
byte
[]
offlineLicenseKeySetId
;
private
Object
currentKeyRequest
;
private
Object
currentProvisionRequest
;
/**
* Instantiates a new DRM session.
*
* @param uuid The UUID of the drm scheme.
* @param mediaDrm The media DRM.
* @param provisioningManager The manager for provisioning.
* @param initData The DRM init data.
* @param schemeData The DRM data for this session, or null if a {@code offlineLicenseKeySetId} is
* provided.
* @param mode The DRM mode.
* @param offlineLicenseKeySetId The offlineLicense KeySetId.
* @param offlineLicenseKeySetId The offline license key set identifier, or null when not using
* offline keys.
* @param optionalKeyRequestParameters The optional key request parameters.
* @param callback The media DRM callback.
* @param playbackLooper The playback looper.
...
...
@@ -117,10 +122,9 @@ import java.util.UUID;
UUID
uuid
,
ExoMediaDrm
<
T
>
mediaDrm
,
ProvisioningManager
<
T
>
provisioningManager
,
byte
[]
initData
,
String
mimeType
,
@Nullable
SchemeData
schemeData
,
@DefaultDrmSessionManager
.
Mode
int
mode
,
byte
[]
offlineLicenseKeySetId
,
@Nullable
byte
[]
offlineLicenseKeySetId
,
HashMap
<
String
,
String
>
optionalKeyRequestParameters
,
MediaDrmCallback
callback
,
Looper
playbackLooper
,
...
...
@@ -131,6 +135,7 @@ import java.util.UUID;
this
.
mediaDrm
=
mediaDrm
;
this
.
mode
=
mode
;
this
.
offlineLicenseKeySetId
=
offlineLicenseKeySetId
;
this
.
schemeData
=
offlineLicenseKeySetId
==
null
?
schemeData
:
null
;
this
.
optionalKeyRequestParameters
=
optionalKeyRequestParameters
;
this
.
callback
=
callback
;
this
.
initialDrmRequestRetryCount
=
initialDrmRequestRetryCount
;
...
...
@@ -141,14 +146,6 @@ import java.util.UUID;
requestHandlerThread
=
new
HandlerThread
(
"DrmRequestHandler"
);
requestHandlerThread
.
start
();
postRequestHandler
=
new
PostRequestHandler
(
requestHandlerThread
.
getLooper
());
if
(
offlineLicenseKeySetId
==
null
)
{
this
.
initData
=
initData
;
this
.
mimeType
=
mimeType
;
}
else
{
this
.
initData
=
null
;
this
.
mimeType
=
null
;
}
}
// Life cycle.
...
...
@@ -177,6 +174,8 @@ import java.util.UUID;
requestHandlerThread
=
null
;
mediaCrypto
=
null
;
lastException
=
null
;
currentKeyRequest
=
null
;
currentProvisionRequest
=
null
;
if
(
sessionId
!=
null
)
{
mediaDrm
.
closeSession
(
sessionId
);
sessionId
=
null
;
...
...
@@ -187,18 +186,42 @@ import java.util.UUID;
}
public
boolean
hasInitData
(
byte
[]
initData
)
{
return
Arrays
.
equals
(
this
.
initData
,
initData
);
return
Arrays
.
equals
(
schemeData
!=
null
?
schemeData
.
data
:
null
,
initData
);
}
public
boolean
hasSessionId
(
byte
[]
sessionId
)
{
return
Arrays
.
equals
(
this
.
sessionId
,
sessionId
);
}
@SuppressWarnings
(
"deprecation"
)
public
void
onMediaDrmEvent
(
int
what
)
{
if
(!
isOpen
())
{
return
;
}
switch
(
what
)
{
case
ExoMediaDrm
.
EVENT_KEY_REQUIRED
:
doLicense
(
false
);
break
;
case
ExoMediaDrm
.
EVENT_KEY_EXPIRED
:
// When an already expired key is loaded MediaDrm sends this event immediately. Ignore
// this event if the state isn't STATE_OPENED_WITH_KEYS yet which means we're still
// waiting for key response.
onKeysExpired
();
break
;
case
ExoMediaDrm
.
EVENT_PROVISION_REQUIRED
:
state
=
STATE_OPENED
;
provisioningManager
.
provisionRequired
(
this
);
break
;
default
:
break
;
}
}
// Provisioning implementation.
public
void
provision
()
{
ProvisionRequest
r
equest
=
mediaDrm
.
getProvisionRequest
();
postRequestHandler
.
obtainMessage
(
MSG_PROVISION
,
request
,
true
).
sendToTarget
(
);
currentProvisionR
equest
=
mediaDrm
.
getProvisionRequest
();
postRequestHandler
.
post
(
MSG_PROVISION
,
currentProvisionRequest
,
/* allowRetry= */
true
);
}
public
void
onProvisionCompleted
()
{
...
...
@@ -271,11 +294,12 @@ import java.util.UUID;
return
false
;
}
private
void
onProvisionResponse
(
Object
response
)
{
if
(
state
!=
STATE_OPENING
&&
!
isOpen
(
))
{
private
void
onProvisionResponse
(
Object
re
quest
,
Object
re
sponse
)
{
if
(
request
!=
currentProvisionRequest
||
(
state
!=
STATE_OPENING
&&
!
isOpen
()
))
{
// This event is stale.
return
;
}
currentProvisionRequest
=
null
;
if
(
response
instanceof
Exception
)
{
provisioningManager
.
onProvisionError
((
Exception
)
response
);
...
...
@@ -356,24 +380,30 @@ import java.util.UUID;
private
void
postKeyRequest
(
int
type
,
boolean
allowRetry
)
{
byte
[]
scope
=
type
==
ExoMediaDrm
.
KEY_TYPE_RELEASE
?
offlineLicenseKeySetId
:
sessionId
;
byte
[]
initData
=
null
;
String
mimeType
=
null
;
String
licenseServerUrl
=
null
;
if
(
schemeData
!=
null
)
{
initData
=
schemeData
.
data
;
mimeType
=
schemeData
.
mimeType
;
licenseServerUrl
=
schemeData
.
licenseServerUrl
;
}
try
{
KeyRequest
request
=
mediaDrm
.
getKeyRequest
(
scope
,
initData
,
mimeType
,
type
,
optionalKeyRequestParameters
);
if
(
C
.
CLEARKEY_UUID
.
equals
(
uuid
))
{
request
=
new
DefaultKeyRequest
(
ClearKeyUtil
.
adjustRequestData
(
request
.
getData
()),
request
.
getDefaultUrl
());
}
postRequestHandler
.
obtainMessage
(
MSG_KEYS
,
request
,
allowRetry
).
sendToTarget
();
KeyRequest
mediaDrmKeyRequest
=
mediaDrm
.
getKeyRequest
(
scope
,
initData
,
mimeType
,
type
,
optionalKeyRequestParameters
);
currentKeyRequest
=
Pair
.
create
(
mediaDrmKeyRequest
,
licenseServerUrl
);
postRequestHandler
.
post
(
MSG_KEYS
,
currentKeyRequest
,
allowRetry
);
}
catch
(
Exception
e
)
{
onKeysError
(
e
);
}
}
private
void
onKeyResponse
(
Object
response
)
{
if
(!
isOpen
())
{
private
void
onKeyResponse
(
Object
re
quest
,
Object
re
sponse
)
{
if
(
request
!=
currentKeyRequest
||
!
isOpen
())
{
// This event is stale.
return
;
}
currentKeyRequest
=
null
;
if
(
response
instanceof
Exception
)
{
onKeysError
((
Exception
)
response
);
...
...
@@ -382,9 +412,6 @@ import java.util.UUID;
try
{
byte
[]
responseData
=
(
byte
[])
response
;
if
(
C
.
CLEARKEY_UUID
.
equals
(
uuid
))
{
responseData
=
ClearKeyUtil
.
adjustResponseData
(
responseData
);
}
if
(
mode
==
DefaultDrmSessionManager
.
MODE_RELEASE
)
{
mediaDrm
.
provideKeyResponse
(
offlineLicenseKeySetId
,
responseData
);
eventDispatcher
.
drmKeysRemoved
();
...
...
@@ -430,30 +457,7 @@ import java.util.UUID;
return
state
==
STATE_OPENED
||
state
==
STATE_OPENED_WITH_KEYS
;
}
@SuppressWarnings
(
"deprecation"
)
public
void
onMediaDrmEvent
(
int
what
)
{
if
(!
isOpen
())
{
return
;
}
switch
(
what
)
{
case
ExoMediaDrm
.
EVENT_KEY_REQUIRED
:
doLicense
(
false
);
break
;
case
ExoMediaDrm
.
EVENT_KEY_EXPIRED
:
// When an already expired key is loaded MediaDrm sends this event immediately. Ignore
// this event if the state isn't STATE_OPENED_WITH_KEYS yet which means we're still
// waiting for key response.
onKeysExpired
();
break
;
case
ExoMediaDrm
.
EVENT_PROVISION_REQUIRED
:
state
=
STATE_OPENED
;
provisioningManager
.
provisionRequired
(
this
);
break
;
default
:
break
;
}
}
// Internal classes.
@SuppressLint
(
"HandlerLeak"
)
private
class
PostResponseHandler
extends
Handler
{
...
...
@@ -464,12 +468,15 @@ import java.util.UUID;
@Override
public
void
handleMessage
(
Message
msg
)
{
Pair
<?,
?>
requestAndResponse
=
(
Pair
<?,
?>)
msg
.
obj
;
Object
request
=
requestAndResponse
.
first
;
Object
response
=
requestAndResponse
.
second
;
switch
(
msg
.
what
)
{
case
MSG_PROVISION:
onProvisionResponse
(
msg
.
obj
);
onProvisionResponse
(
request
,
response
);
break
;
case
MSG_KEYS:
onKeyResponse
(
msg
.
obj
);
onKeyResponse
(
request
,
response
);
break
;
default
:
break
;
...
...
@@ -486,21 +493,27 @@ import java.util.UUID;
super
(
backgroundLooper
);
}
Message
obtainMessage
(
int
what
,
Object
object
,
boolean
allowRetry
)
{
return
obtainMessage
(
what
,
allowRetry
?
1
:
0
/* allow retry*/
,
0
/* error count */
,
object
);
void
post
(
int
what
,
Object
request
,
boolean
allowRetry
)
{
int
allowRetryInt
=
allowRetry
?
1
:
0
;
int
errorCount
=
0
;
obtainMessage
(
what
,
allowRetryInt
,
errorCount
,
request
).
sendToTarget
();
}
@Override
@SuppressWarnings
(
"unchecked"
)
public
void
handleMessage
(
Message
msg
)
{
Object
request
=
msg
.
obj
;
Object
response
;
try
{
switch
(
msg
.
what
)
{
case
MSG_PROVISION:
response
=
callback
.
executeProvisionRequest
(
uuid
,
(
ProvisionRequest
)
msg
.
obj
);
response
=
callback
.
executeProvisionRequest
(
uuid
,
(
ProvisionRequest
)
request
);
break
;
case
MSG_KEYS:
response
=
callback
.
executeKeyRequest
(
uuid
,
(
KeyRequest
)
msg
.
obj
);
Pair
<
KeyRequest
,
String
>
keyRequest
=
(
Pair
<
KeyRequest
,
String
>)
request
;
KeyRequest
mediaDrmKeyRequest
=
keyRequest
.
first
;
String
licenseServerUrl
=
keyRequest
.
second
;
response
=
callback
.
executeKeyRequest
(
uuid
,
mediaDrmKeyRequest
,
licenseServerUrl
);
break
;
default
:
throw
new
RuntimeException
();
...
...
@@ -511,7 +524,7 @@ import java.util.UUID;
}
response
=
e
;
}
postResponseHandler
.
obtainMessage
(
msg
.
what
,
response
).
sendToTarget
();
postResponseHandler
.
obtainMessage
(
msg
.
what
,
Pair
.
create
(
request
,
response
)
).
sendToTarget
();
}
private
boolean
maybeRetryRequest
(
Message
originalMsg
)
{
...
...
@@ -534,5 +547,4 @@ import java.util.UUID;
}
}
}
library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java
View file @
8cbcfd66
...
...
@@ -32,7 +32,6 @@ import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
import
com.google.android.exoplayer2.drm.ExoMediaDrm.OnEventListener
;
import
com.google.android.exoplayer2.extractor.mp4.PsshAtomUtil
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.MimeTypes
;
import
com.google.android.exoplayer2.util.Util
;
import
java.lang.annotation.Retention
;
import
java.lang.annotation.RetentionPolicy
;
...
...
@@ -89,7 +88,6 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
public
static
final
int
INITIAL_DRM_REQUEST_RETRY_COUNT
=
3
;
private
static
final
String
TAG
=
"DefaultDrmSessionMgr"
;
private
static
final
String
CENC_SCHEME_MIME_TYPE
=
"cenc"
;
private
final
UUID
uuid
;
private
final
ExoMediaDrm
<
T
>
mediaDrm
;
...
...
@@ -509,17 +507,14 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
}
}
byte
[]
initData
=
null
;
String
mimeType
=
null
;
SchemeData
schemeData
=
null
;
if
(
offlineLicenseKeySetId
==
null
)
{
SchemeData
d
ata
=
getSchemeData
(
drmInitData
,
uuid
,
false
);
if
(
d
ata
==
null
)
{
schemeD
ata
=
getSchemeData
(
drmInitData
,
uuid
,
false
);
if
(
schemeD
ata
==
null
)
{
final
MissingSchemeDataException
error
=
new
MissingSchemeDataException
(
uuid
);
eventDispatcher
.
drmSessionManagerError
(
error
);
return
new
ErrorStateDrmSession
<>(
new
DrmSessionException
(
error
));
}
initData
=
getSchemeInitData
(
data
,
uuid
);
mimeType
=
getSchemeMimeType
(
data
,
uuid
);
}
DefaultDrmSession
<
T
>
session
;
...
...
@@ -528,6 +523,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
}
else
{
// Only use an existing session if it has matching init data.
session
=
null
;
byte
[]
initData
=
schemeData
!=
null
?
schemeData
.
data
:
null
;
for
(
DefaultDrmSession
<
T
>
existingSession
:
sessions
)
{
if
(
existingSession
.
hasInitData
(
initData
))
{
session
=
existingSession
;
...
...
@@ -543,8 +539,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
uuid
,
mediaDrm
,
this
,
initData
,
mimeType
,
schemeData
,
mode
,
offlineLicenseKeySetId
,
optionalKeyRequestParameters
,
...
...
@@ -650,31 +645,6 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
return
matchingSchemeDatas
.
get
(
0
);
}
private
static
byte
[]
getSchemeInitData
(
SchemeData
data
,
UUID
uuid
)
{
byte
[]
schemeInitData
=
data
.
data
;
if
(
Util
.
SDK_INT
<
21
)
{
// Prior to L the Widevine CDM required data to be extracted from the PSSH atom.
byte
[]
psshData
=
PsshAtomUtil
.
parseSchemeSpecificData
(
schemeInitData
,
uuid
);
if
(
psshData
==
null
)
{
// Extraction failed. schemeData isn't a Widevine PSSH atom, so leave it unchanged.
}
else
{
schemeInitData
=
psshData
;
}
}
return
schemeInitData
;
}
private
static
String
getSchemeMimeType
(
SchemeData
data
,
UUID
uuid
)
{
String
schemeMimeType
=
data
.
mimeType
;
if
(
Util
.
SDK_INT
<
26
&&
C
.
CLEARKEY_UUID
.
equals
(
uuid
)
&&
(
MimeTypes
.
VIDEO_MP4
.
equals
(
schemeMimeType
)
||
MimeTypes
.
AUDIO_MP4
.
equals
(
schemeMimeType
)))
{
// Prior to API level 26 the ClearKey CDM only accepted "cenc" as the scheme for MP4.
schemeMimeType
=
CENC_SCHEME_MIME_TYPE
;
}
return
schemeMimeType
;
}
@SuppressLint
(
"HandlerLeak"
)
private
class
MediaDrmHandler
extends
Handler
{
...
...
library/core/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java
View file @
8cbcfd66
...
...
@@ -266,9 +266,9 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
* applies to all schemes).
*/
private
final
UUID
uuid
;
/**
* The mimeType of {@link #data}.
*/
/**
The URL of the server to which license requests should be made. May be null if unknown. */
public
final
@Nullable
String
licenseServerUrl
;
/** The mimeType of {@link #data}.
*/
public
final
String
mimeType
;
/**
* The initialization data. May be null for scheme support checks only.
...
...
@@ -297,7 +297,25 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
* @param requiresSecureDecryption See {@link #requiresSecureDecryption}.
*/
public
SchemeData
(
UUID
uuid
,
String
mimeType
,
byte
[]
data
,
boolean
requiresSecureDecryption
)
{
this
(
uuid
,
/* licenseServerUrl= */
null
,
mimeType
,
data
,
requiresSecureDecryption
);
}
/**
* @param uuid The {@link UUID} of the DRM scheme, or {@link C#UUID_NIL} if the data is
* universal (i.e. applies to all schemes).
* @param licenseServerUrl See {@link #licenseServerUrl}.
* @param mimeType See {@link #mimeType}.
* @param data See {@link #data}.
* @param requiresSecureDecryption See {@link #requiresSecureDecryption}.
*/
public
SchemeData
(
UUID
uuid
,
@Nullable
String
licenseServerUrl
,
String
mimeType
,
byte
[]
data
,
boolean
requiresSecureDecryption
)
{
this
.
uuid
=
Assertions
.
checkNotNull
(
uuid
);
this
.
licenseServerUrl
=
licenseServerUrl
;
this
.
mimeType
=
Assertions
.
checkNotNull
(
mimeType
);
this
.
data
=
data
;
this
.
requiresSecureDecryption
=
requiresSecureDecryption
;
...
...
@@ -305,6 +323,7 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
/* package */
SchemeData
(
Parcel
in
)
{
uuid
=
new
UUID
(
in
.
readLong
(),
in
.
readLong
());
licenseServerUrl
=
in
.
readString
();
mimeType
=
in
.
readString
();
data
=
in
.
createByteArray
();
requiresSecureDecryption
=
in
.
readByte
()
!=
0
;
...
...
@@ -346,7 +365,9 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
return
true
;
}
SchemeData
other
=
(
SchemeData
)
obj
;
return
mimeType
.
equals
(
other
.
mimeType
)
&&
Util
.
areEqual
(
uuid
,
other
.
uuid
)
return
Util
.
areEqual
(
licenseServerUrl
,
other
.
licenseServerUrl
)
&&
Util
.
areEqual
(
mimeType
,
other
.
mimeType
)
&&
Util
.
areEqual
(
uuid
,
other
.
uuid
)
&&
Arrays
.
equals
(
data
,
other
.
data
);
}
...
...
@@ -354,6 +375,7 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
public
int
hashCode
()
{
if
(
hashCode
==
0
)
{
int
result
=
uuid
.
hashCode
();
result
=
31
*
result
+
(
licenseServerUrl
==
null
?
0
:
licenseServerUrl
.
hashCode
());
result
=
31
*
result
+
mimeType
.
hashCode
();
result
=
31
*
result
+
Arrays
.
hashCode
(
data
);
hashCode
=
result
;
...
...
@@ -372,6 +394,7 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
public
void
writeToParcel
(
Parcel
dest
,
int
flags
)
{
dest
.
writeLong
(
uuid
.
getMostSignificantBits
());
dest
.
writeLong
(
uuid
.
getLeastSignificantBits
());
dest
.
writeString
(
licenseServerUrl
);
dest
.
writeString
(
mimeType
);
dest
.
writeByteArray
(
data
);
dest
.
writeByte
((
byte
)
(
requiresSecureDecryption
?
1
:
0
));
...
...
library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java
View file @
8cbcfd66
...
...
@@ -15,6 +15,7 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
drm
;
import
android.annotation.SuppressLint
;
import
android.annotation.TargetApi
;
import
android.media.DeniedByServerException
;
import
android.media.MediaCrypto
;
...
...
@@ -26,7 +27,9 @@ import android.media.UnsupportedSchemeException;
import
android.support.annotation.NonNull
;
import
android.support.annotation.Nullable
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.extractor.mp4.PsshAtomUtil
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.MimeTypes
;
import
com.google.android.exoplayer2.util.Util
;
import
java.util.ArrayList
;
import
java.util.HashMap
;
...
...
@@ -40,6 +43,8 @@ import java.util.UUID;
@TargetApi
(
23
)
public
final
class
FrameworkMediaDrm
implements
ExoMediaDrm
<
FrameworkMediaCrypto
>
{
private
static
final
String
CENC_SCHEME_MIME_TYPE
=
"cenc"
;
private
final
UUID
uuid
;
private
final
MediaDrm
mediaDrm
;
...
...
@@ -60,6 +65,7 @@ public final class FrameworkMediaDrm implements ExoMediaDrm<FrameworkMediaCrypto
}
}
@SuppressLint
(
"WrongConstant"
)
private
FrameworkMediaDrm
(
UUID
uuid
)
throws
UnsupportedSchemeException
{
Assertions
.
checkNotNull
(
uuid
);
Assertions
.
checkArgument
(!
C
.
COMMON_PSSH_UUID
.
equals
(
uuid
),
"Use C.CLEARKEY_UUID instead"
);
...
...
@@ -67,6 +73,9 @@ public final class FrameworkMediaDrm implements ExoMediaDrm<FrameworkMediaCrypto
uuid
=
Util
.
SDK_INT
<
27
&&
C
.
CLEARKEY_UUID
.
equals
(
uuid
)
?
C
.
COMMON_PSSH_UUID
:
uuid
;
this
.
uuid
=
uuid
;
this
.
mediaDrm
=
new
MediaDrm
(
uuid
);
if
(
C
.
WIDEVINE_UUID
.
equals
(
uuid
)
&&
needsForceL3Workaround
())
{
mediaDrm
.
setPropertyString
(
"securityLevel"
,
"L3"
);
}
}
@Override
...
...
@@ -116,14 +125,49 @@ public final class FrameworkMediaDrm implements ExoMediaDrm<FrameworkMediaCrypto
@Override
public
KeyRequest
getKeyRequest
(
byte
[]
scope
,
byte
[]
init
,
String
mimeType
,
int
keyType
,
HashMap
<
String
,
String
>
optionalParameters
)
throws
NotProvisionedException
{
// Prior to L the Widevine CDM required data to be extracted from the PSSH atom. Some Amazon
// devices also required data to be extracted from the PSSH atom for PlayReady.
if
((
Util
.
SDK_INT
<
21
&&
C
.
WIDEVINE_UUID
.
equals
(
uuid
))
||
(
C
.
PLAYREADY_UUID
.
equals
(
uuid
)
&&
"Amazon"
.
equals
(
Util
.
MANUFACTURER
)
&&
(
"AFTB"
.
equals
(
Util
.
MODEL
)
// Fire TV Gen 1
||
"AFTS"
.
equals
(
Util
.
MODEL
)
// Fire TV Gen 2
||
"AFTM"
.
equals
(
Util
.
MODEL
))))
{
// Fire TV Stick Gen 1
byte
[]
psshData
=
PsshAtomUtil
.
parseSchemeSpecificData
(
init
,
uuid
);
if
(
psshData
==
null
)
{
// Extraction failed. schemeData isn't a PSSH atom, so leave it unchanged.
}
else
{
init
=
psshData
;
}
}
// Prior to API level 26 the ClearKey CDM only accepted "cenc" as the scheme for MP4.
if
(
Util
.
SDK_INT
<
26
&&
C
.
CLEARKEY_UUID
.
equals
(
uuid
)
&&
(
MimeTypes
.
VIDEO_MP4
.
equals
(
mimeType
)
||
MimeTypes
.
AUDIO_MP4
.
equals
(
mimeType
)))
{
mimeType
=
CENC_SCHEME_MIME_TYPE
;
}
final
MediaDrm
.
KeyRequest
request
=
mediaDrm
.
getKeyRequest
(
scope
,
init
,
mimeType
,
keyType
,
optionalParameters
);
return
new
DefaultKeyRequest
(
request
.
getData
(),
request
.
getDefaultUrl
());
byte
[]
requestData
=
request
.
getData
();
if
(
C
.
CLEARKEY_UUID
.
equals
(
uuid
))
{
requestData
=
ClearKeyUtil
.
adjustRequestData
(
requestData
);
}
return
new
DefaultKeyRequest
(
requestData
,
request
.
getDefaultUrl
());
}
@Override
public
byte
[]
provideKeyResponse
(
byte
[]
scope
,
byte
[]
response
)
throws
NotProvisionedException
,
DeniedByServerException
{
if
(
C
.
CLEARKEY_UUID
.
equals
(
uuid
))
{
response
=
ClearKeyUtil
.
adjustResponseData
(
response
);
}
return
mediaDrm
.
provideKeyResponse
(
scope
,
response
);
}
...
...
@@ -183,4 +227,12 @@ public final class FrameworkMediaDrm implements ExoMediaDrm<FrameworkMediaCrypto
forceAllowInsecureDecoderComponents
);
}
/**
* Returns whether the device codec is known to fail if security level L1 is used.
*
* <p>See <a href="https://github.com/google/ExoPlayer/issues/4413">GitHub issue #4413</a>.
*/
private
static
boolean
needsForceL3Workaround
()
{
return
"ASUS_Z00AD"
.
equals
(
Util
.
MODEL
);
}
}
library/core/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java
View file @
8cbcfd66
...
...
@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.drm;
import
android.annotation.TargetApi
;
import
android.net.Uri
;
import
android.support.annotation.Nullable
;
import
android.text.TextUtils
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.drm.ExoMediaDrm.KeyRequest
;
...
...
@@ -114,8 +115,13 @@ public final class HttpMediaDrmCallback implements MediaDrmCallback {
}
@Override
public
byte
[]
executeKeyRequest
(
UUID
uuid
,
KeyRequest
request
)
throws
Exception
{
public
byte
[]
executeKeyRequest
(
UUID
uuid
,
KeyRequest
request
,
@Nullable
String
mediaProvidedLicenseServerUrl
)
throws
Exception
{
String
url
=
request
.
getDefaultUrl
();
if
(
TextUtils
.
isEmpty
(
url
))
{
url
=
mediaProvidedLicenseServerUrl
;
}
if
(
forceDefaultLicenseUrl
||
TextUtils
.
isEmpty
(
url
))
{
url
=
defaultLicenseUrl
;
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/drm/LocalMediaDrmCallback.java
View file @
8cbcfd66
...
...
@@ -15,6 +15,7 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
drm
;
import
android.support.annotation.Nullable
;
import
com.google.android.exoplayer2.drm.ExoMediaDrm.KeyRequest
;
import
com.google.android.exoplayer2.drm.ExoMediaDrm.ProvisionRequest
;
import
com.google.android.exoplayer2.util.Assertions
;
...
...
@@ -44,7 +45,9 @@ public final class LocalMediaDrmCallback implements MediaDrmCallback {
}
@Override
public
byte
[]
executeKeyRequest
(
UUID
uuid
,
KeyRequest
request
)
throws
Exception
{
public
byte
[]
executeKeyRequest
(
UUID
uuid
,
KeyRequest
request
,
@Nullable
String
mediaProvidedLicenseServerUrl
)
throws
Exception
{
return
keyResponse
;
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/drm/MediaDrmCallback.java
View file @
8cbcfd66
...
...
@@ -15,6 +15,7 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
drm
;
import
android.support.annotation.Nullable
;
import
com.google.android.exoplayer2.drm.ExoMediaDrm.KeyRequest
;
import
com.google.android.exoplayer2.drm.ExoMediaDrm.ProvisionRequest
;
import
java.util.UUID
;
...
...
@@ -38,10 +39,13 @@ public interface MediaDrmCallback {
* Executes a key request.
*
* @param uuid The UUID of the content protection scheme.
* @param request The request.
* @param request The request generated by the content decryption module.
* @param mediaProvidedLicenseServerUrl A license server URL provided by the media, or null if the
* media does not include any license server URL.
* @return The response data.
* @throws Exception If an error occurred executing the request.
*/
byte
[]
executeKeyRequest
(
UUID
uuid
,
KeyRequest
request
)
throws
Exception
;
byte
[]
executeKeyRequest
(
UUID
uuid
,
KeyRequest
request
,
@Nullable
String
mediaProvidedLicenseServerUrl
)
throws
Exception
;
}
library/core/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfoHolder.java
View file @
8cbcfd66
...
...
@@ -19,6 +19,7 @@ import com.google.android.exoplayer2.Format;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.metadata.id3.CommentFrame
;
import
com.google.android.exoplayer2.metadata.id3.Id3Decoder.FramePredicate
;
import
com.google.android.exoplayer2.metadata.id3.InternalFrame
;
import
java.util.regex.Matcher
;
import
java.util.regex.Pattern
;
...
...
@@ -39,7 +40,8 @@ public final class GaplessInfoHolder {
}
};
private
static
final
String
GAPLESS_COMMENT_ID
=
"iTunSMPB"
;
private
static
final
String
GAPLESS_DOMAIN
=
"com.apple.iTunes"
;
private
static
final
String
GAPLESS_DESCRIPTION
=
"iTunSMPB"
;
private
static
final
Pattern
GAPLESS_COMMENT_PATTERN
=
Pattern
.
compile
(
"^ [0-9a-fA-F]{8} ([0-9a-fA-F]{8}) ([0-9a-fA-F]{8})"
);
...
...
@@ -91,7 +93,15 @@ public final class GaplessInfoHolder {
Metadata
.
Entry
entry
=
metadata
.
get
(
i
);
if
(
entry
instanceof
CommentFrame
)
{
CommentFrame
commentFrame
=
(
CommentFrame
)
entry
;
if
(
setFromComment
(
commentFrame
.
description
,
commentFrame
.
text
))
{
if
(
GAPLESS_DESCRIPTION
.
equals
(
commentFrame
.
description
)
&&
setFromComment
(
commentFrame
.
text
))
{
return
true
;
}
}
else
if
(
entry
instanceof
InternalFrame
)
{
InternalFrame
internalFrame
=
(
InternalFrame
)
entry
;
if
(
GAPLESS_DOMAIN
.
equals
(
internalFrame
.
domain
)
&&
GAPLESS_DESCRIPTION
.
equals
(
internalFrame
.
description
)
&&
setFromComment
(
internalFrame
.
text
))
{
return
true
;
}
}
...
...
@@ -103,14 +113,10 @@ public final class GaplessInfoHolder {
* Populates the holder with data parsed from a gapless playback comment (stored in an ID3 header
* or MPEG 4 user data), if valid and non-zero.
*
* @param name The comment's identifier.
* @param data The comment's payload data.
* @return Whether the holder was populated.
*/
private
boolean
setFromComment
(
String
name
,
String
data
)
{
if
(!
GAPLESS_COMMENT_ID
.
equals
(
name
))
{
return
false
;
}
private
boolean
setFromComment
(
String
data
)
{
Matcher
matcher
=
GAPLESS_COMMENT_PATTERN
.
matcher
(
data
);
if
(
matcher
.
find
())
{
try
{
...
...
library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java
View file @
8cbcfd66
...
...
@@ -616,10 +616,10 @@ public final class MatroskaExtractor implements Extractor {
currentTrack
.
number
=
(
int
)
value
;
break
;
case
ID_FLAG_DEFAULT:
currentTrack
.
flag
Forced
=
value
==
1
;
currentTrack
.
flag
Default
=
value
==
1
;
break
;
case
ID_FLAG_FORCED:
currentTrack
.
flag
Default
=
value
==
1
;
currentTrack
.
flag
Forced
=
value
==
1
;
break
;
case
ID_TRACK_TYPE:
currentTrack
.
type
=
(
int
)
value
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java
View file @
8cbcfd66
...
...
@@ -43,6 +43,9 @@ import java.util.List;
*/
/* package */
final
class
AtomParsers
{
/** Thrown if an edit list couldn't be applied. */
public
static
final
class
UnhandledEditListException
extends
ParserException
{}
private
static
final
String
TAG
=
"AtomParsers"
;
private
static
final
int
TYPE_vide
=
Util
.
getIntegerCodeForString
(
"vide"
);
...
...
@@ -117,10 +120,12 @@ import java.util.List;
* @param stblAtom stbl (sample table) atom to decode.
* @param gaplessInfoHolder Holder to populate with gapless playback information.
* @return Sample table described by the stbl atom.
* @throws ParserException If the resulting sample sequence does not contain a sync sample.
* @throws UnhandledEditListException Thrown if the edit list can't be applied.
* @throws ParserException Thrown if the stbl atom can't be parsed.
*/
public
static
TrackSampleTable
parseStbl
(
Track
track
,
Atom
.
ContainerAtom
stblAtom
,
GaplessInfoHolder
gaplessInfoHolder
)
throws
ParserException
{
public
static
TrackSampleTable
parseStbl
(
Track
track
,
Atom
.
ContainerAtom
stblAtom
,
GaplessInfoHolder
gaplessInfoHolder
)
throws
ParserException
{
SampleSizeBox
sampleSizeBox
;
Atom
.
LeafAtom
stszAtom
=
stblAtom
.
getLeafAtomOfType
(
Atom
.
TYPE_stsz
);
if
(
stszAtom
!=
null
)
{
...
...
@@ -136,7 +141,13 @@ import java.util.List;
int
sampleCount
=
sampleSizeBox
.
getSampleCount
();
if
(
sampleCount
==
0
)
{
return
new
TrackSampleTable
(
new
long
[
0
],
new
int
[
0
],
0
,
new
long
[
0
],
new
int
[
0
],
C
.
TIME_UNSET
);
track
,
/* offsets= */
new
long
[
0
],
/* sizes= */
new
int
[
0
],
/* maximumSize= */
0
,
/* timestampsUs= */
new
long
[
0
],
/* flags= */
new
int
[
0
],
/* durationUs= */
C
.
TIME_UNSET
);
}
// Entries are byte offsets of chunks.
...
...
@@ -315,7 +326,8 @@ import java.util.List;
// There is no edit list, or we are ignoring it as we already have gapless metadata to apply.
// This implementation does not support applying both gapless metadata and an edit list.
Util
.
scaleLargeTimestampsInPlace
(
timestamps
,
C
.
MICROS_PER_SECOND
,
track
.
timescale
);
return
new
TrackSampleTable
(
offsets
,
sizes
,
maximumSize
,
timestamps
,
flags
,
durationUs
);
return
new
TrackSampleTable
(
track
,
offsets
,
sizes
,
maximumSize
,
timestamps
,
flags
,
durationUs
);
}
// See the BMFF spec (ISO 14496-12) subsection 8.6.6. Edit lists that require prerolling from a
...
...
@@ -342,7 +354,8 @@ import java.util.List;
gaplessInfoHolder
.
encoderDelay
=
(
int
)
encoderDelay
;
gaplessInfoHolder
.
encoderPadding
=
(
int
)
encoderPadding
;
Util
.
scaleLargeTimestampsInPlace
(
timestamps
,
C
.
MICROS_PER_SECOND
,
track
.
timescale
);
return
new
TrackSampleTable
(
offsets
,
sizes
,
maximumSize
,
timestamps
,
flags
,
durationUs
);
return
new
TrackSampleTable
(
track
,
offsets
,
sizes
,
maximumSize
,
timestamps
,
flags
,
durationUs
);
}
}
}
...
...
@@ -359,7 +372,8 @@ import java.util.List;
}
durationUs
=
Util
.
scaleLargeTimestamp
(
duration
-
editStartTime
,
C
.
MICROS_PER_SECOND
,
track
.
timescale
);
return
new
TrackSampleTable
(
offsets
,
sizes
,
maximumSize
,
timestamps
,
flags
,
durationUs
);
return
new
TrackSampleTable
(
track
,
offsets
,
sizes
,
maximumSize
,
timestamps
,
flags
,
durationUs
);
}
// Omit any sample at the end point of an edit for audio tracks.
...
...
@@ -409,6 +423,11 @@ import java.util.List;
System
.
arraycopy
(
sizes
,
startIndex
,
editedSizes
,
sampleIndex
,
count
);
System
.
arraycopy
(
flags
,
startIndex
,
editedFlags
,
sampleIndex
,
count
);
}
if
(
startIndex
<
endIndex
&&
(
editedFlags
[
sampleIndex
]
&
C
.
BUFFER_FLAG_KEY_FRAME
)
==
0
)
{
// Applying the edit list would require prerolling from a sync sample.
Log
.
w
(
TAG
,
"Ignoring edit list: edit does not start with a sync sample."
);
throw
new
UnhandledEditListException
();
}
for
(
int
j
=
startIndex
;
j
<
endIndex
;
j
++)
{
long
ptsUs
=
Util
.
scaleLargeTimestamp
(
pts
,
C
.
MICROS_PER_SECOND
,
track
.
movieTimescale
);
long
timeInSegmentUs
=
...
...
@@ -424,20 +443,8 @@ import java.util.List;
pts
+=
editDuration
;
}
long
editedDurationUs
=
Util
.
scaleLargeTimestamp
(
pts
,
C
.
MICROS_PER_SECOND
,
track
.
timescale
);
boolean
hasSyncSample
=
false
;
for
(
int
i
=
0
;
i
<
editedFlags
.
length
&&
!
hasSyncSample
;
i
++)
{
hasSyncSample
|=
(
editedFlags
[
i
]
&
C
.
BUFFER_FLAG_KEY_FRAME
)
!=
0
;
}
if
(!
hasSyncSample
)
{
// We don't support edit lists where the edited sample sequence doesn't contain a sync sample.
// Such edit lists are often (although not always) broken, so we ignore it and continue.
Log
.
w
(
TAG
,
"Ignoring edit list: Edited sample sequence does not contain a sync sample."
);
Util
.
scaleLargeTimestampsInPlace
(
timestamps
,
C
.
MICROS_PER_SECOND
,
track
.
timescale
);
return
new
TrackSampleTable
(
offsets
,
sizes
,
maximumSize
,
timestamps
,
flags
,
durationUs
);
}
return
new
TrackSampleTable
(
track
,
editedOffsets
,
editedSizes
,
editedMaximumSize
,
...
...
library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java
View file @
8cbcfd66
...
...
@@ -499,7 +499,7 @@ public final class FragmentedMp4Extractor implements Extractor {
for
(
int
i
=
0
;
i
<
trackCount
;
i
++)
{
Track
track
=
tracks
.
valueAt
(
i
);
TrackBundle
trackBundle
=
new
TrackBundle
(
extractorOutput
.
track
(
i
,
track
.
type
));
trackBundle
.
init
(
track
,
defaultSampleValuesArray
.
get
(
track
.
id
));
trackBundle
.
init
(
track
,
getDefaultSampleValues
(
defaultSampleValuesArray
,
track
.
id
));
trackBundles
.
put
(
track
.
id
,
trackBundle
);
durationUs
=
Math
.
max
(
durationUs
,
track
.
durationUs
);
}
...
...
@@ -509,11 +509,23 @@ public final class FragmentedMp4Extractor implements Extractor {
Assertions
.
checkState
(
trackBundles
.
size
()
==
trackCount
);
for
(
int
i
=
0
;
i
<
trackCount
;
i
++)
{
Track
track
=
tracks
.
valueAt
(
i
);
trackBundles
.
get
(
track
.
id
).
init
(
track
,
defaultSampleValuesArray
.
get
(
track
.
id
));
trackBundles
.
get
(
track
.
id
)
.
init
(
track
,
getDefaultSampleValues
(
defaultSampleValuesArray
,
track
.
id
));
}
}
}
private
DefaultSampleValues
getDefaultSampleValues
(
SparseArray
<
DefaultSampleValues
>
defaultSampleValuesArray
,
int
trackId
)
{
if
(
defaultSampleValuesArray
.
size
()
==
1
)
{
// Ignore track id if there is only one track to cope with non-matching track indices.
// See https://github.com/google/ExoPlayer/issues/4477.
return
defaultSampleValuesArray
.
valueAt
(
/* index= */
0
);
}
return
Assertions
.
checkNotNull
(
defaultSampleValuesArray
.
get
(
trackId
));
}
private
void
onMoofContainerAtomRead
(
ContainerAtom
moof
)
throws
ParserException
{
parseMoof
(
moof
,
trackBundles
,
flags
,
extendedTypeScratch
);
// If drm init data is sideloaded, we ignore pssh boxes.
...
...
@@ -642,7 +654,7 @@ public final class FragmentedMp4Extractor implements Extractor {
private
static
void
parseTraf
(
ContainerAtom
traf
,
SparseArray
<
TrackBundle
>
trackBundleArray
,
@Flags
int
flags
,
byte
[]
extendedTypeScratch
)
throws
ParserException
{
LeafAtom
tfhd
=
traf
.
getLeafAtomOfType
(
Atom
.
TYPE_tfhd
);
TrackBundle
trackBundle
=
parseTfhd
(
tfhd
.
data
,
trackBundleArray
,
flags
);
TrackBundle
trackBundle
=
parseTfhd
(
tfhd
.
data
,
trackBundleArray
);
if
(
trackBundle
==
null
)
{
return
;
}
...
...
@@ -793,13 +805,13 @@ public final class FragmentedMp4Extractor implements Extractor {
* @return The {@link TrackBundle} to which the {@link TrackFragment} belongs, or null if the tfhd
* does not refer to any {@link TrackBundle}.
*/
private
static
TrackBundle
parseTfhd
(
ParsableByteArray
tfhd
,
SparseArray
<
TrackBundle
>
trackBundles
,
int
flag
s
)
{
private
static
TrackBundle
parseTfhd
(
ParsableByteArray
tfhd
,
SparseArray
<
TrackBundle
>
trackBundle
s
)
{
tfhd
.
setPosition
(
Atom
.
HEADER_SIZE
);
int
fullAtom
=
tfhd
.
readInt
();
int
atomFlags
=
Atom
.
parseFullAtomFlags
(
fullAtom
);
int
trackId
=
tfhd
.
readInt
();
TrackBundle
trackBundle
=
trackBundles
.
get
((
flags
&
FLAG_SIDELOADED
)
==
0
?
trackId
:
0
);
TrackBundle
trackBundle
=
getTrackBundle
(
trackBundles
,
trackId
);
if
(
trackBundle
==
null
)
{
return
null
;
}
...
...
@@ -824,6 +836,17 @@ public final class FragmentedMp4Extractor implements Extractor {
return
trackBundle
;
}
private
static
@Nullable
TrackBundle
getTrackBundle
(
SparseArray
<
TrackBundle
>
trackBundles
,
int
trackId
)
{
if
(
trackBundles
.
size
()
==
1
)
{
// Ignore track id if there is only one track. This is either because we have a side-loaded
// track (flag FLAG_SIDELOADED) or to cope with non-matching track indices (see
// https://github.com/google/ExoPlayer/issues/4083).
return
trackBundles
.
valueAt
(
/* index= */
0
);
}
return
trackBundles
.
get
(
trackId
);
}
/**
* Parses a tfdt atom (defined in 14496-12).
*
...
...
library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java
View file @
8cbcfd66
...
...
@@ -20,6 +20,7 @@ import com.google.android.exoplayer2.metadata.Metadata;
import
com.google.android.exoplayer2.metadata.id3.ApicFrame
;
import
com.google.android.exoplayer2.metadata.id3.CommentFrame
;
import
com.google.android.exoplayer2.metadata.id3.Id3Frame
;
import
com.google.android.exoplayer2.metadata.id3.InternalFrame
;
import
com.google.android.exoplayer2.metadata.id3.TextInformationFrame
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
com.google.android.exoplayer2.util.Util
;
...
...
@@ -293,14 +294,13 @@ import com.google.android.exoplayer2.util.Util;
data
.
skipBytes
(
atomSize
-
12
);
}
}
if
(!
"com.apple.iTunes"
.
equals
(
domain
)
||
!
"iTunSMPB"
.
equals
(
name
)
||
dataAtomPosition
==
-
1
)
{
// We're only interested in iTunSMPB.
if
(
domain
==
null
||
name
==
null
||
dataAtomPosition
==
-
1
)
{
return
null
;
}
data
.
setPosition
(
dataAtomPosition
);
data
.
skipBytes
(
16
);
// size (4), type (4), version (1), flags (3), empty (4)
String
value
=
data
.
readNullTerminatedString
(
dataAtomSize
-
16
);
return
new
CommentFrame
(
LANGUAGE_UNDEFINED
,
name
,
value
);
return
new
InternalFrame
(
domain
,
name
,
value
);
}
private
static
int
parseUint8AttributeValue
(
ParsableByteArray
data
)
{
...
...
library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java
View file @
8cbcfd66
...
...
@@ -391,25 +391,21 @@ public final class Mp4Extractor implements Extractor, SeekMap {
}
}
for
(
int
i
=
0
;
i
<
moov
.
containerChildren
.
size
();
i
++)
{
Atom
.
ContainerAtom
atom
=
moov
.
containerChildren
.
get
(
i
);
if
(
atom
.
type
!=
Atom
.
TYPE_trak
)
{
continue
;
}
Track
track
=
AtomParsers
.
parseTrak
(
atom
,
moov
.
getLeafAtomOfType
(
Atom
.
TYPE_mvhd
),
C
.
TIME_UNSET
,
null
,
(
flags
&
FLAG_WORKAROUND_IGNORE_EDIT_LISTS
)
!=
0
,
isQuickTime
);
if
(
track
==
null
)
{
continue
;
}
Atom
.
ContainerAtom
stblAtom
=
atom
.
getContainerAtomOfType
(
Atom
.
TYPE_mdia
)
.
getContainerAtomOfType
(
Atom
.
TYPE_minf
).
getContainerAtomOfType
(
Atom
.
TYPE_stbl
);
TrackSampleTable
trackSampleTable
=
AtomParsers
.
parseStbl
(
track
,
stblAtom
,
gaplessInfoHolder
);
if
(
trackSampleTable
.
sampleCount
==
0
)
{
continue
;
}
boolean
ignoreEditLists
=
(
flags
&
FLAG_WORKAROUND_IGNORE_EDIT_LISTS
)
!=
0
;
ArrayList
<
TrackSampleTable
>
trackSampleTables
;
try
{
trackSampleTables
=
getTrackSampleTables
(
moov
,
gaplessInfoHolder
,
ignoreEditLists
);
}
catch
(
AtomParsers
.
UnhandledEditListException
e
)
{
// Discard gapless info as we aren't able to handle corresponding edits.
gaplessInfoHolder
=
new
GaplessInfoHolder
();
trackSampleTables
=
getTrackSampleTables
(
moov
,
gaplessInfoHolder
,
/* ignoreEditLists= */
true
);
}
int
trackCount
=
trackSampleTables
.
size
();
for
(
int
i
=
0
;
i
<
trackCount
;
i
++)
{
TrackSampleTable
trackSampleTable
=
trackSampleTables
.
get
(
i
);
Track
track
=
trackSampleTable
.
track
;
Mp4Track
mp4Track
=
new
Mp4Track
(
track
,
trackSampleTable
,
extractorOutput
.
track
(
i
,
track
.
type
));
// Each sample has up to three bytes of overhead for the start code that replaces its length.
...
...
@@ -445,6 +441,39 @@ public final class Mp4Extractor implements Extractor, SeekMap {
extractorOutput
.
seekMap
(
this
);
}
private
ArrayList
<
TrackSampleTable
>
getTrackSampleTables
(
ContainerAtom
moov
,
GaplessInfoHolder
gaplessInfoHolder
,
boolean
ignoreEditLists
)
throws
ParserException
{
ArrayList
<
TrackSampleTable
>
trackSampleTables
=
new
ArrayList
<>();
for
(
int
i
=
0
;
i
<
moov
.
containerChildren
.
size
();
i
++)
{
Atom
.
ContainerAtom
atom
=
moov
.
containerChildren
.
get
(
i
);
if
(
atom
.
type
!=
Atom
.
TYPE_trak
)
{
continue
;
}
Track
track
=
AtomParsers
.
parseTrak
(
atom
,
moov
.
getLeafAtomOfType
(
Atom
.
TYPE_mvhd
),
/* duration= */
C
.
TIME_UNSET
,
/* drmInitData= */
null
,
ignoreEditLists
,
isQuickTime
);
if
(
track
==
null
)
{
continue
;
}
Atom
.
ContainerAtom
stblAtom
=
atom
.
getContainerAtomOfType
(
Atom
.
TYPE_mdia
)
.
getContainerAtomOfType
(
Atom
.
TYPE_minf
)
.
getContainerAtomOfType
(
Atom
.
TYPE_stbl
);
TrackSampleTable
trackSampleTable
=
AtomParsers
.
parseStbl
(
track
,
stblAtom
,
gaplessInfoHolder
);
if
(
trackSampleTable
.
sampleCount
==
0
)
{
continue
;
}
trackSampleTables
.
add
(
trackSampleTable
);
}
return
trackSampleTables
;
}
/**
* Attempts to extract the next sample in the current mdat atom for the specified track.
* <p>
...
...
library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackSampleTable.java
View file @
8cbcfd66
...
...
@@ -24,29 +24,19 @@ import com.google.android.exoplayer2.util.Util;
*/
/* package */
final
class
TrackSampleTable
{
/**
* Number of samples.
*/
/**
The track corresponding to this sample table. */
public
final
Track
track
;
/** Number of samples.
*/
public
final
int
sampleCount
;
/**
* Sample offsets in bytes.
*/
/** Sample offsets in bytes. */
public
final
long
[]
offsets
;
/**
* Sample sizes in bytes.
*/
/** Sample sizes in bytes. */
public
final
int
[]
sizes
;
/**
* Maximum sample size in {@link #sizes}.
*/
/** Maximum sample size in {@link #sizes}. */
public
final
int
maximumSize
;
/**
* Sample timestamps in microseconds.
*/
/** Sample timestamps in microseconds. */
public
final
long
[]
timestampsUs
;
/**
* Sample flags.
*/
/** Sample flags. */
public
final
int
[]
flags
;
/**
* The duration of the track sample table in microseconds, or {@link C#TIME_UNSET} if the sample
...
...
@@ -55,6 +45,7 @@ import com.google.android.exoplayer2.util.Util;
public
final
long
durationUs
;
public
TrackSampleTable
(
Track
track
,
long
[]
offsets
,
int
[]
sizes
,
int
maximumSize
,
...
...
@@ -65,6 +56,7 @@ import com.google.android.exoplayer2.util.Util;
Assertions
.
checkArgument
(
offsets
.
length
==
timestampsUs
.
length
);
Assertions
.
checkArgument
(
flags
.
length
==
timestampsUs
.
length
);
this
.
track
=
track
;
this
.
offsets
=
offsets
;
this
.
sizes
=
sizes
;
this
.
maximumSize
=
maximumSize
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java
View file @
8cbcfd66
...
...
@@ -52,7 +52,12 @@ public final class PsExtractor implements Extractor {
private
static
final
int
PACKET_START_CODE_PREFIX
=
0x000001
;
private
static
final
int
MPEG_PROGRAM_END_CODE
=
0x000001B9
;
private
static
final
int
MAX_STREAM_ID_PLUS_ONE
=
0x100
;
// Max search length for first audio and video track in input data.
private
static
final
long
MAX_SEARCH_LENGTH
=
1024
*
1024
;
// Max search length for additional audio and video tracks in input data after at least one audio
// and video track has been found.
private
static
final
long
MAX_SEARCH_LENGTH_AFTER_AUDIO_AND_VIDEO_FOUND
=
8
*
1024
;
public
static
final
int
PRIVATE_STREAM_1
=
0xBD
;
public
static
final
int
AUDIO_STREAM
=
0xC0
;
...
...
@@ -66,6 +71,7 @@ public final class PsExtractor implements Extractor {
private
boolean
foundAllTracks
;
private
boolean
foundAudioTrack
;
private
boolean
foundVideoTrack
;
private
long
lastTrackPosition
;
// Accessed only by the loading thread.
private
ExtractorOutput
output
;
...
...
@@ -188,18 +194,21 @@ public final class PsExtractor implements Extractor {
if
(!
foundAllTracks
)
{
if
(
payloadReader
==
null
)
{
ElementaryStreamReader
elementaryStreamReader
=
null
;
if
(
!
foundAudioTrack
&&
streamId
==
PRIVATE_STREAM_1
)
{
if
(
streamId
==
PRIVATE_STREAM_1
)
{
// Private stream, used for AC3 audio.
// NOTE: This may need further parsing to determine if its DTS, but that's likely only
// valid for DVDs.
elementaryStreamReader
=
new
Ac3Reader
();
foundAudioTrack
=
true
;
}
else
if
(!
foundAudioTrack
&&
(
streamId
&
AUDIO_STREAM_MASK
)
==
AUDIO_STREAM
)
{
lastTrackPosition
=
input
.
getPosition
();
}
else
if
((
streamId
&
AUDIO_STREAM_MASK
)
==
AUDIO_STREAM
)
{
elementaryStreamReader
=
new
MpegAudioReader
();
foundAudioTrack
=
true
;
}
else
if
(!
foundVideoTrack
&&
(
streamId
&
VIDEO_STREAM_MASK
)
==
VIDEO_STREAM
)
{
lastTrackPosition
=
input
.
getPosition
();
}
else
if
((
streamId
&
VIDEO_STREAM_MASK
)
==
VIDEO_STREAM
)
{
elementaryStreamReader
=
new
H262Reader
();
foundVideoTrack
=
true
;
lastTrackPosition
=
input
.
getPosition
();
}
if
(
elementaryStreamReader
!=
null
)
{
TrackIdGenerator
idGenerator
=
new
TrackIdGenerator
(
streamId
,
MAX_STREAM_ID_PLUS_ONE
);
...
...
@@ -208,7 +217,11 @@ public final class PsExtractor implements Extractor {
psPayloadReaders
.
put
(
streamId
,
payloadReader
);
}
}
if
((
foundAudioTrack
&&
foundVideoTrack
)
||
input
.
getPosition
()
>
MAX_SEARCH_LENGTH
)
{
long
maxSearchPosition
=
foundAudioTrack
&&
foundVideoTrack
?
lastTrackPosition
+
MAX_SEARCH_LENGTH_AFTER_AUDIO_AND_VIDEO_FOUND
:
MAX_SEARCH_LENGTH
;
if
(
input
.
getPosition
()
>
maxSearchPosition
)
{
foundAllTracks
=
true
;
output
.
endTracks
();
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java
View file @
8cbcfd66
...
...
@@ -369,6 +369,15 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
wrappedMediaCrypto
=
mediaCrypto
.
getWrappedMediaCrypto
();
drmSessionRequiresSecureDecoder
=
mediaCrypto
.
requiresSecureDecoderComponent
(
mimeType
);
}
if
(
deviceNeedsDrmKeysToConfigureCodecWorkaround
())
{
@DrmSession
.
State
int
drmSessionState
=
drmSession
.
getState
();
if
(
drmSessionState
==
DrmSession
.
STATE_ERROR
)
{
throw
ExoPlaybackException
.
createForRenderer
(
drmSession
.
getError
(),
getIndex
());
}
else
if
(
drmSessionState
!=
DrmSession
.
STATE_OPENED_WITH_KEYS
)
{
// Wait for keys.
return
;
}
}
}
if
(
codecInfo
==
null
)
{
...
...
@@ -405,7 +414,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codecAdaptationWorkaroundMode
=
codecAdaptationWorkaroundMode
(
codecName
);
codecNeedsDiscardToSpsWorkaround
=
codecNeedsDiscardToSpsWorkaround
(
codecName
,
format
);
codecNeedsFlushWorkaround
=
codecNeedsFlushWorkaround
(
codecName
);
codecNeedsEosPropagationWorkaround
=
codecNeedsEosPropagationWorkaround
(
codec
Name
);
codecNeedsEosPropagationWorkaround
=
codecNeedsEosPropagationWorkaround
(
codec
Info
);
codecNeedsEosFlushWorkaround
=
codecNeedsEosFlushWorkaround
(
codecName
);
codecNeedsEosOutputExceptionWorkaround
=
codecNeedsEosOutputExceptionWorkaround
(
codecName
);
codecNeedsMonoChannelCountWorkaround
=
codecNeedsMonoChannelCountWorkaround
(
codecName
,
format
);
...
...
@@ -1210,6 +1219,16 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
}
/**
* Returns whether the device needs keys to have been loaded into the {@link DrmSession} before
* codec configuration.
*/
private
boolean
deviceNeedsDrmKeysToConfigureCodecWorkaround
()
{
return
"Amazon"
.
equals
(
Util
.
MANUFACTURER
)
&&
(
"AFTM"
.
equals
(
Util
.
MODEL
)
// Fire TV Stick Gen 1
||
"AFTB"
.
equals
(
Util
.
MODEL
));
// Fire TV Gen 1
}
/**
* Returns whether the decoder is known to fail when flushed.
* <p>
* If true is returned, the renderer will work around the issue by releasing the decoder and
...
...
@@ -1272,20 +1291,23 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
}
/**
* Returns whether the decoder is known to handle the propagation of the
*
{@link
MediaCodec#BUFFER_FLAG_END_OF_STREAM} flag incorrectly on the host device.
*
<p>
* If true is returned, the renderer will work around the issue by approximating end of stream
* Returns whether the decoder is known to handle the propagation of the
{@link
* MediaCodec#BUFFER_FLAG_END_OF_STREAM} flag incorrectly on the host device.
*
*
<p>
If true is returned, the renderer will work around the issue by approximating end of stream
* behavior without relying on the flag being propagated through to an output buffer by the
* underlying decoder.
*
* @param
name The name of the decoder
.
* @param
codecInfo Information about the {@link MediaCodec}
.
* @return True if the decoder is known to handle {@link MediaCodec#BUFFER_FLAG_END_OF_STREAM}
* propagation incorrectly on the host device. False otherwise.
*/
private
static
boolean
codecNeedsEosPropagationWorkaround
(
String
name
)
{
return
Util
.
SDK_INT
<=
17
&&
(
"OMX.rk.video_decoder.avc"
.
equals
(
name
)
||
"OMX.allwinner.video.decoder.avc"
.
equals
(
name
));
private
static
boolean
codecNeedsEosPropagationWorkaround
(
MediaCodecInfo
codecInfo
)
{
String
name
=
codecInfo
.
name
;
return
(
Util
.
SDK_INT
<=
17
&&
(
"OMX.rk.video_decoder.avc"
.
equals
(
name
)
||
"OMX.allwinner.video.decoder.avc"
.
equals
(
name
)))
||
(
"Amazon"
.
equals
(
Util
.
MANUFACTURER
)
&&
"AFTS"
.
equals
(
Util
.
MODEL
)
&&
codecInfo
.
secure
);
}
/**
...
...
library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/InternalFrame.java
0 → 100644
View file @
8cbcfd66
/*
* Copyright (C) 2018 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
.
metadata
.
id3
;
import
android.os.Parcel
;
import
android.support.annotation.Nullable
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.Util
;
/** Internal ID3 frame that is intended for use by the player. */
public
final
class
InternalFrame
extends
Id3Frame
{
public
static
final
String
ID
=
"----"
;
public
final
String
domain
;
public
final
String
description
;
public
final
String
text
;
public
InternalFrame
(
String
domain
,
String
description
,
String
text
)
{
super
(
ID
);
this
.
domain
=
domain
;
this
.
description
=
description
;
this
.
text
=
text
;
}
/* package */
InternalFrame
(
Parcel
in
)
{
super
(
ID
);
domain
=
Assertions
.
checkNotNull
(
in
.
readString
());
description
=
Assertions
.
checkNotNull
(
in
.
readString
());
text
=
Assertions
.
checkNotNull
(
in
.
readString
());
}
@Override
public
boolean
equals
(
@Nullable
Object
obj
)
{
if
(
this
==
obj
)
{
return
true
;
}
if
(
obj
==
null
||
getClass
()
!=
obj
.
getClass
())
{
return
false
;
}
InternalFrame
other
=
(
InternalFrame
)
obj
;
return
Util
.
areEqual
(
description
,
other
.
description
)
&&
Util
.
areEqual
(
domain
,
other
.
domain
)
&&
Util
.
areEqual
(
text
,
other
.
text
);
}
@Override
public
int
hashCode
()
{
int
result
=
17
;
result
=
31
*
result
+
(
domain
!=
null
?
domain
.
hashCode
()
:
0
);
result
=
31
*
result
+
(
description
!=
null
?
description
.
hashCode
()
:
0
);
result
=
31
*
result
+
(
text
!=
null
?
text
.
hashCode
()
:
0
);
return
result
;
}
@Override
public
String
toString
()
{
return
id
+
": domain="
+
domain
+
", description="
+
description
;
}
// Parcelable implementation.
@Override
public
void
writeToParcel
(
Parcel
dest
,
int
flags
)
{
dest
.
writeString
(
id
);
dest
.
writeString
(
domain
);
dest
.
writeString
(
text
);
}
public
static
final
Creator
<
InternalFrame
>
CREATOR
=
new
Creator
<
InternalFrame
>()
{
@Override
public
InternalFrame
createFromParcel
(
Parcel
in
)
{
return
new
InternalFrame
(
in
);
}
@Override
public
InternalFrame
[]
newArray
(
int
size
)
{
return
new
InternalFrame
[
size
];
}
};
}
library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java
View file @
8cbcfd66
...
...
@@ -86,6 +86,7 @@ public abstract class DownloadService extends Service {
private
DownloadManagerListener
downloadManagerListener
;
private
int
lastStartId
;
private
boolean
startedInForeground
;
private
boolean
taskRemoved
;
/**
* Creates a DownloadService with {@link #DEFAULT_FOREGROUND_NOTIFICATION_UPDATE_INTERVAL}.
...
...
@@ -219,12 +220,17 @@ public abstract class DownloadService extends Service {
@Override
public
int
onStartCommand
(
Intent
intent
,
int
flags
,
int
startId
)
{
lastStartId
=
startId
;
taskRemoved
=
false
;
String
intentAction
=
null
;
if
(
intent
!=
null
)
{
intentAction
=
intent
.
getAction
();
startedInForeground
|=
intent
.
getBooleanExtra
(
KEY_FOREGROUND
,
false
)
||
ACTION_RESTART
.
equals
(
intentAction
);
}
// intentAction is null if the service is restarted or no action is specified.
if
(
intentAction
==
null
)
{
intentAction
=
ACTION_INIT
;
}
logd
(
"onStartCommand action: "
+
intentAction
+
" startId: "
+
startId
);
switch
(
intentAction
)
{
case
ACTION_INIT:
...
...
@@ -261,6 +267,12 @@ public abstract class DownloadService extends Service {
}
@Override
public
void
onTaskRemoved
(
Intent
rootIntent
)
{
logd
(
"onTaskRemoved rootIntent: "
+
rootIntent
);
taskRemoved
=
true
;
}
@Override
public
void
onDestroy
()
{
logd
(
"onDestroy"
);
foregroundNotificationUpdater
.
stopPeriodicUpdates
();
...
...
@@ -353,8 +365,13 @@ public abstract class DownloadService extends Service {
if
(
startedInForeground
&&
Util
.
SDK_INT
>=
26
)
{
foregroundNotificationUpdater
.
showNotificationIfNotAlready
();
}
boolean
stopSelfResult
=
stopSelfResult
(
lastStartId
);
logd
(
"stopSelf("
+
lastStartId
+
") result: "
+
stopSelfResult
);
if
(
Util
.
SDK_INT
<
28
&&
taskRemoved
)
{
// See [Internal: b/74248644].
stopSelf
();
logd
(
"stopSelf()"
);
}
else
{
boolean
stopSelfResult
=
stopSelfResult
(
lastStartId
);
logd
(
"stopSelf("
+
lastStartId
+
") result: "
+
stopSelfResult
);
}
}
private
void
logd
(
String
message
)
{
...
...
library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java
View file @
8cbcfd66
...
...
@@ -344,6 +344,14 @@ public final class AdPlaybackState {
return
new
AdPlaybackState
(
adGroupTimesUs
,
adGroups
,
adResumePositionUs
,
contentDurationUs
);
}
/** Returns an instance with the specified ad marked as skipped. */
@CheckResult
public
AdPlaybackState
withSkippedAd
(
int
adGroupIndex
,
int
adIndexInAdGroup
)
{
AdGroup
[]
adGroups
=
Arrays
.
copyOf
(
this
.
adGroups
,
this
.
adGroups
.
length
);
adGroups
[
adGroupIndex
]
=
adGroups
[
adGroupIndex
].
withAdState
(
AD_STATE_SKIPPED
,
adIndexInAdGroup
);
return
new
AdPlaybackState
(
adGroupTimesUs
,
adGroups
,
adResumePositionUs
,
contentDurationUs
);
}
/** Returns an instance with the specified ad marked as having a load error. */
@CheckResult
public
AdPlaybackState
withAdLoadError
(
int
adGroupIndex
,
int
adIndexInAdGroup
)
{
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java
View file @
8cbcfd66
...
...
@@ -21,10 +21,10 @@ import android.text.Layout.Alignment;
import
android.text.SpannableString
;
import
android.text.SpannableStringBuilder
;
import
android.text.Spanned
;
import
android.text.style.CharacterStyle
;
import
android.text.style.ForegroundColorSpan
;
import
android.text.style.StyleSpan
;
import
android.text.style.UnderlineSpan
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.Format
;
import
com.google.android.exoplayer2.text.Cue
;
import
com.google.android.exoplayer2.text.Subtitle
;
...
...
@@ -55,15 +55,13 @@ public final class Cea608Decoder extends CeaDecoder {
private
static
final
int
[]
ROW_INDICES
=
new
int
[]
{
11
,
1
,
3
,
12
,
14
,
5
,
7
,
9
};
private
static
final
int
[]
COLUMN_INDICES
=
new
int
[]
{
0
,
4
,
8
,
12
,
16
,
20
,
24
,
28
};
private
static
final
int
[]
COLORS
=
new
int
[]
{
Color
.
WHITE
,
Color
.
GREEN
,
Color
.
BLUE
,
Color
.
CYAN
,
Color
.
RED
,
Color
.
YELLOW
,
Color
.
MAGENTA
,
};
private
static
final
int
[]
STYLE_COLORS
=
new
int
[]
{
Color
.
WHITE
,
Color
.
GREEN
,
Color
.
BLUE
,
Color
.
CYAN
,
Color
.
RED
,
Color
.
YELLOW
,
Color
.
MAGENTA
};
private
static
final
int
STYLE_ITALICS
=
0x07
;
private
static
final
int
STYLE_UNCHANGED
=
0x08
;
// The default number of rows to display in roll-up captions mode.
private
static
final
int
DEFAULT_CAPTIONS_ROW_COUNT
=
4
;
...
...
@@ -377,18 +375,10 @@ public final class Cea608Decoder extends CeaDecoder {
// 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
;
currentCueBuilder
.
setUnderline
(
isUnderlined
);
int
attribute
=
(
cc2
>>
1
)
&
0x0F
;
if
(
attribute
==
0x07
)
{
currentCueBuilder
.
setMidrowStyle
(
new
StyleSpan
(
Typeface
.
ITALIC
),
2
);
currentCueBuilder
.
setMidrowStyle
(
new
ForegroundColorSpan
(
Color
.
WHITE
),
1
);
}
else
{
currentCueBuilder
.
setMidrowStyle
(
new
ForegroundColorSpan
(
COLORS
[
attribute
]),
1
);
}
// cc2 - 0|0|1|0|STYLE|U
boolean
underline
=
(
cc2
&
0x01
)
==
0x01
;
int
style
=
(
cc2
>>
1
)
&
0x07
;
currentCueBuilder
.
setStyle
(
style
,
underline
);
}
private
void
handlePreambleAddressCode
(
byte
cc1
,
byte
cc2
)
{
...
...
@@ -414,22 +404,18 @@ public final class Cea608Decoder extends CeaDecoder {
currentCueBuilder
.
setRow
(
row
);
}
if
((
cc2
&
0x01
)
==
0x01
)
{
currentCueBuilder
.
setPreambleStyle
(
new
UnderlineSpan
());
}
// cc2 - 0|1|N|0|STYLE|U
// cc2 - 0|1|N|1|CURSR|U
int
attribute
=
cc2
>>
1
&
0x0F
;
if
(
attribute
<=
0x07
)
{
if
(
attribute
==
0x07
)
{
currentCueBuilder
.
setPreambleStyle
(
new
StyleSpan
(
Typeface
.
ITALIC
));
currentCueBuilder
.
setPreambleStyle
(
new
ForegroundColorSpan
(
Color
.
WHITE
));
}
else
{
currentCueBuilder
.
setPreambleStyle
(
new
ForegroundColorSpan
(
COLORS
[
attribute
])
);
}
}
else
{
currentCueBuilder
.
setIndent
(
COLUMN_INDICES
[
attribute
&
0x07
]);
boolean
isCursor
=
(
cc2
&
0x10
)
==
0x10
;
boolean
underline
=
(
cc2
&
0x01
)
==
0x01
;
int
cursorOrStyle
=
(
cc2
>>
1
)
&
0x07
;
// We need to call setStyle even for the isCursor case, to update the underline bit.
// STYLE_UNCHANGED is used for this case.
currentCueBuilder
.
setStyle
(
isCursor
?
STYLE_UNCHANGED
:
cursorOrStyle
,
underline
);
if
(
isCursor
)
{
currentCueBuilder
.
setIndent
(
COLUMN_INDICES
[
cursorOrStyle
]);
}
}
...
...
@@ -585,44 +571,37 @@ public final class Cea608Decoder extends CeaDecoder {
private
static
class
CueBuilder
{
private
static
final
int
POSITION_UNSET
=
-
1
;
// 608 captions define a 15 row by 32 column screen grid. These constants convert from 608
// positions to normalized screen position.
private
static
final
int
SCREEN_CHARWIDTH
=
32
;
private
static
final
int
BASE_ROW
=
15
;
private
final
List
<
CharacterStyle
>
preambleStyles
;
private
final
List
<
CueStyle
>
midrowStyles
;
private
final
List
<
CueStyle
>
cueStyles
;
private
final
List
<
SpannableString
>
rolledUpCaptions
;
private
final
S
pannableS
tringBuilder
captionStringBuilder
;
private
final
StringBuilder
captionStringBuilder
;
private
int
row
;
private
int
indent
;
private
int
tabOffset
;
private
int
captionMode
;
private
int
captionRowCount
;
private
int
underlineStartPosition
;
public
CueBuilder
(
int
captionMode
,
int
captionRowCount
)
{
preambleStyles
=
new
ArrayList
<>();
midrowStyles
=
new
ArrayList
<>();
cueStyles
=
new
ArrayList
<>();
rolledUpCaptions
=
new
ArrayList
<>();
captionStringBuilder
=
new
S
pannableS
tringBuilder
();
captionStringBuilder
=
new
StringBuilder
();
reset
(
captionMode
);
setCaptionRowCount
(
captionRowCount
);
}
public
void
reset
(
int
captionMode
)
{
this
.
captionMode
=
captionMode
;
preambleStyles
.
clear
();
midrowStyles
.
clear
();
cueStyles
.
clear
();
rolledUpCaptions
.
clear
();
captionStringBuilder
.
clear
(
);
captionStringBuilder
.
setLength
(
0
);
row
=
BASE_ROW
;
indent
=
0
;
tabOffset
=
0
;
underlineStartPosition
=
POSITION_UNSET
;
}
public
void
setCaptionRowCount
(
int
captionRowCount
)
{
...
...
@@ -630,7 +609,8 @@ public final class Cea608Decoder extends CeaDecoder {
}
public
boolean
isEmpty
()
{
return
preambleStyles
.
isEmpty
()
&&
midrowStyles
.
isEmpty
()
&&
rolledUpCaptions
.
isEmpty
()
return
cueStyles
.
isEmpty
()
&&
rolledUpCaptions
.
isEmpty
()
&&
captionStringBuilder
.
length
()
==
0
;
}
...
...
@@ -638,6 +618,16 @@ public final class Cea608Decoder extends CeaDecoder {
int
length
=
captionStringBuilder
.
length
();
if
(
length
>
0
)
{
captionStringBuilder
.
delete
(
length
-
1
,
length
);
// Decrement style start positions if necessary.
for
(
int
i
=
cueStyles
.
size
()
-
1
;
i
>=
0
;
i
--)
{
CueStyle
style
=
cueStyles
.
get
(
i
);
if
(
style
.
start
==
length
)
{
style
.
start
--;
}
else
{
// All earlier cues must have style.start < length.
break
;
}
}
}
}
...
...
@@ -651,11 +641,8 @@ public final class Cea608Decoder extends CeaDecoder {
public
void
rollUp
()
{
rolledUpCaptions
.
add
(
buildSpannableString
());
captionStringBuilder
.
clear
();
preambleStyles
.
clear
();
midrowStyles
.
clear
();
underlineStartPosition
=
POSITION_UNSET
;
captionStringBuilder
.
setLength
(
0
);
cueStyles
.
clear
();
int
numRows
=
Math
.
min
(
captionRowCount
,
row
);
while
(
rolledUpCaptions
.
size
()
>=
numRows
)
{
rolledUpCaptions
.
remove
(
0
);
...
...
@@ -670,23 +657,8 @@ public final class Cea608Decoder extends CeaDecoder {
tabOffset
=
tabs
;
}
public
void
setPreambleStyle
(
CharacterStyle
style
)
{
preambleStyles
.
add
(
style
);
}
public
void
setMidrowStyle
(
CharacterStyle
style
,
int
nextStyleIncrement
)
{
midrowStyles
.
add
(
new
CueStyle
(
style
,
captionStringBuilder
.
length
(),
nextStyleIncrement
));
}
public
void
setUnderline
(
boolean
enabled
)
{
if
(
enabled
)
{
underlineStartPosition
=
captionStringBuilder
.
length
();
}
else
if
(
underlineStartPosition
!=
POSITION_UNSET
)
{
// underline spans won't overlap, so it's safe to modify the builder directly with them
captionStringBuilder
.
setSpan
(
new
UnderlineSpan
(),
underlineStartPosition
,
captionStringBuilder
.
length
(),
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
);
underlineStartPosition
=
POSITION_UNSET
;
}
public
void
setStyle
(
int
style
,
boolean
underline
)
{
cueStyles
.
add
(
new
CueStyle
(
style
,
underline
,
captionStringBuilder
.
length
()));
}
public
void
append
(
char
text
)
{
...
...
@@ -694,31 +666,69 @@ public final class Cea608Decoder extends CeaDecoder {
}
public
SpannableString
buildSpannableString
()
{
int
length
=
captionStringBuilder
.
length
();
SpannableStringBuilder
builder
=
new
SpannableStringBuilder
(
captionStringBuilder
);
int
length
=
builder
.
length
();
int
underlineStartPosition
=
C
.
INDEX_UNSET
;
int
italicStartPosition
=
C
.
INDEX_UNSET
;
int
colorStartPosition
=
0
;
int
color
=
Color
.
WHITE
;
boolean
nextItalic
=
false
;
int
nextColor
=
Color
.
WHITE
;
for
(
int
i
=
0
;
i
<
cueStyles
.
size
();
i
++)
{
CueStyle
cueStyle
=
cueStyles
.
get
(
i
);
boolean
underline
=
cueStyle
.
underline
;
int
style
=
cueStyle
.
style
;
if
(
style
!=
STYLE_UNCHANGED
)
{
// If the style is a color then italic is cleared.
nextItalic
=
style
==
STYLE_ITALICS
;
// If the style is italic then the color is left unchanged.
nextColor
=
style
==
STYLE_ITALICS
?
nextColor
:
STYLE_COLORS
[
style
];
}
// preamble styles apply to the entire cue
for
(
int
i
=
0
;
i
<
preambleStyles
.
size
();
i
++)
{
captionStringBuilder
.
setSpan
(
preambleStyles
.
get
(
i
),
0
,
length
,
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
);
}
int
position
=
cueStyle
.
start
;
int
nextPosition
=
(
i
+
1
)
<
cueStyles
.
size
()
?
cueStyles
.
get
(
i
+
1
).
start
:
length
;
if
(
position
==
nextPosition
)
{
// There are more cueStyles to process at the current position.
continue
;
}
// midrow styles only apply to part of the cue, and after preamble styles
for
(
int
i
=
0
;
i
<
midrowStyles
.
size
();
i
++)
{
CueStyle
cueStyle
=
midrowStyles
.
get
(
i
);
int
end
=
(
i
<
midrowStyles
.
size
()
-
cueStyle
.
nextStyleIncrement
)
?
midrowStyles
.
get
(
i
+
cueStyle
.
nextStyleIncrement
).
start
:
length
;
captionStringBuilder
.
setSpan
(
cueStyle
.
style
,
cueStyle
.
start
,
end
,
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
);
// Process changes to underline up to the current position.
if
(
underlineStartPosition
!=
C
.
INDEX_UNSET
&&
!
underline
)
{
setUnderlineSpan
(
builder
,
underlineStartPosition
,
position
);
underlineStartPosition
=
C
.
INDEX_UNSET
;
}
else
if
(
underlineStartPosition
==
C
.
INDEX_UNSET
&&
underline
)
{
underlineStartPosition
=
position
;
}
// Process changes to italic up to the current position.
if
(
italicStartPosition
!=
C
.
INDEX_UNSET
&&
!
nextItalic
)
{
setItalicSpan
(
builder
,
italicStartPosition
,
position
);
italicStartPosition
=
C
.
INDEX_UNSET
;
}
else
if
(
italicStartPosition
==
C
.
INDEX_UNSET
&&
nextItalic
)
{
italicStartPosition
=
position
;
}
// Process changes to color up to the current position.
if
(
nextColor
!=
color
)
{
setColorSpan
(
builder
,
colorStartPosition
,
position
,
color
);
color
=
nextColor
;
colorStartPosition
=
position
;
}
}
// special case for midrow underlines that went to the end of the cue
if
(
underlineStartPosition
!=
POSITION_UNSET
)
{
captionStringBuilder
.
setSpan
(
new
UnderlineSpan
(),
underlineStartPosition
,
length
,
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
);
// Add any final spans.
if
(
underlineStartPosition
!=
C
.
INDEX_UNSET
&&
underlineStartPosition
!=
length
)
{
setUnderlineSpan
(
builder
,
underlineStartPosition
,
length
);
}
if
(
italicStartPosition
!=
C
.
INDEX_UNSET
&&
italicStartPosition
!=
length
)
{
setItalicSpan
(
builder
,
italicStartPosition
,
length
);
}
if
(
colorStartPosition
!=
length
)
{
setColorSpan
(
builder
,
colorStartPosition
,
length
,
color
);
}
return
new
SpannableString
(
captionStringB
uilder
);
return
new
SpannableString
(
b
uilder
);
}
public
Cue
build
()
{
...
...
@@ -788,16 +798,34 @@ public final class Cea608Decoder extends CeaDecoder {
return
captionStringBuilder
.
toString
();
}
private
static
void
setUnderlineSpan
(
SpannableStringBuilder
builder
,
int
start
,
int
end
)
{
builder
.
setSpan
(
new
UnderlineSpan
(),
start
,
end
,
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
);
}
private
static
void
setItalicSpan
(
SpannableStringBuilder
builder
,
int
start
,
int
end
)
{
builder
.
setSpan
(
new
StyleSpan
(
Typeface
.
ITALIC
),
start
,
end
,
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
);
}
private
static
void
setColorSpan
(
SpannableStringBuilder
builder
,
int
start
,
int
end
,
int
color
)
{
if
(
color
==
Color
.
WHITE
)
{
// White is treated as the default color (i.e. no span is attached).
return
;
}
builder
.
setSpan
(
new
ForegroundColorSpan
(
color
),
start
,
end
,
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
);
}
private
static
class
CueStyle
{
public
final
CharacterStyle
style
;
public
final
int
start
;
public
final
int
nextStyleIncrement
;
public
final
int
style
;
public
final
boolean
underline
;
public
int
start
;
public
CueStyle
(
CharacterStyle
style
,
int
start
,
int
nextStyleIncremen
t
)
{
public
CueStyle
(
int
style
,
boolean
underline
,
int
star
t
)
{
this
.
style
=
style
;
this
.
underline
=
underline
;
this
.
start
=
start
;
this
.
nextStyleIncrement
=
nextStyleIncrement
;
}
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java
View file @
8cbcfd66
...
...
@@ -153,7 +153,8 @@ import java.util.concurrent.atomic.AtomicReference;
public
class
DefaultTrackSelector
extends
MappingTrackSelector
{
/**
* A builder for {@link Parameters}.
* A builder for {@link Parameters}. See the {@link Parameters} documentation for explanations of
* the parameters that can be configured using this builder.
*/
public
static
final
class
ParametersBuilder
{
...
...
@@ -177,9 +178,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
private
boolean
viewportOrientationMayChange
;
private
int
tunnelingAudioSessionId
;
/**
* Creates a builder obtaining the initial values from {@link Parameters#DEFAULT}.
*/
/** Creates a builder with default initial values. */
public
ParametersBuilder
()
{
this
(
Parameters
.
DEFAULT
);
}
...
...
@@ -343,15 +342,15 @@ public class DefaultTrackSelector extends MappingTrackSelector {
}
/**
* Equivalent to
invoking {@link #setViewportSize} with the viewport size obtained from
* {@link Util#getPhysicalDisplaySize(Context)}.
* Equivalent to
calling {@link #setViewportSize(int, int, boolean)} with the viewport size
*
obtained from
{@link Util#getPhysicalDisplaySize(Context)}.
*
* @param context
The context to obtain the viewport size from
.
* @param viewportOrientationMayChange See {@link #viewportOrientationMayChange}.
* @param context
Any context
.
* @param viewportOrientationMayChange See {@link
Parameters
#viewportOrientationMayChange}.
* @return This builder.
*/
public
ParametersBuilder
setViewportSizeToPhysicalDisplaySize
(
Context
context
,
boolean
viewportOrientationMayChange
)
{
public
ParametersBuilder
setViewportSizeToPhysicalDisplaySize
(
Context
context
,
boolean
viewportOrientationMayChange
)
{
// Assume the viewport is fullscreen.
Point
viewportSize
=
Util
.
getPhysicalDisplaySize
(
context
);
return
setViewportSize
(
viewportSize
.
x
,
viewportSize
.
y
,
viewportOrientationMayChange
);
...
...
@@ -368,13 +367,16 @@ public class DefaultTrackSelector extends MappingTrackSelector {
}
/**
* See {@link Parameters#viewportWidth}, {@link Parameters#maxVideoHeight} and
*
{@link
Parameters#viewportOrientationMayChange}.
* See {@link Parameters#viewportWidth}, {@link Parameters#maxVideoHeight} and
{@link
* Parameters#viewportOrientationMayChange}.
*
* @param viewportWidth See {@link Parameters#viewportWidth}.
* @param viewportHeight See {@link Parameters#viewportHeight}.
* @param viewportOrientationMayChange See {@link Parameters#viewportOrientationMayChange}.
* @return This builder.
*/
public
ParametersBuilder
setViewportSize
(
int
viewportWidth
,
int
viewportHeight
,
boolean
viewportOrientationMayChange
)
{
public
ParametersBuilder
setViewportSize
(
int
viewportWidth
,
int
viewportHeight
,
boolean
viewportOrientationMayChange
)
{
this
.
viewportWidth
=
viewportWidth
;
this
.
viewportHeight
=
viewportHeight
;
this
.
viewportOrientationMayChange
=
viewportOrientationMayChange
;
...
...
@@ -485,8 +487,10 @@ public class DefaultTrackSelector extends MappingTrackSelector {
}
/**
* Enables or disables tunneling. To enable tunneling, pass an audio session id to use when in
* tunneling mode. Session ids can be generated using {@link
* See {@link Parameters#tunnelingAudioSessionId}.
*
* <p>Enables or disables tunneling. To enable tunneling, pass an audio session id to use when
* in tunneling mode. Session ids can be generated using {@link
* C#generateAudioSessionIdV21(Context)}. To disable tunneling pass {@link
* C#AUDIO_SESSION_ID_UNSET}. Tunneling will only be activated if it's both enabled and
* supported by the audio and video renderers for the selected tracks.
...
...
@@ -540,25 +544,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
/** Constraint parameters for {@link DefaultTrackSelector}. */
public
static
final
class
Parameters
implements
Parcelable
{
/**
* An instance with default values:
*
* <ul>
* <li>No preferred audio language.
* <li>No preferred text language.
* <li>Text tracks with undetermined language are not selected if no track with {@link
* #preferredTextLanguage} is available.
* <li>All selection flags are considered for text track selections.
* <li>Lowest bitrate track selections are not forced.
* <li>Adaptation between different mime types is not allowed.
* <li>Non seamless adaptation is allowed.
* <li>No max limit for video width/height.
* <li>No max video bitrate.
* <li>Video constraints are exceeded if no supported selection can be made otherwise.
* <li>Renderer capabilities are exceeded if no supported selection can be made.
* <li>No viewport constraints.
* </ul>
*/
/** An instance with default values. */
public
static
final
Parameters
DEFAULT
=
new
Parameters
();
// Per renderer overrides.
...
...
@@ -568,105 +554,131 @@ public class DefaultTrackSelector extends MappingTrackSelector {
// Audio
/**
* 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.
* The preferred language for audio and 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. The default value is
* {@code null}.
*/
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.
* default track if there is one, or no track otherwise.
The default value is {@code null}.
*/
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.
* Whether a text track with undetermined language should be selected if no track with {@link
* #preferredTextLanguage} is available, or if {@link #preferredTextLanguage} is unset. The
* default value is {@code false}.
*/
public
final
boolean
selectUndeterminedTextLanguage
;
/**
* Bitmask of selection flags that are disabled for text track selections. See {@link
* C.SelectionFlags}.
* C.SelectionFlags}.
The default value is {@code 0} (i.e. no flags).
*/
public
final
int
disabledTextTrackSelectionFlags
;
// Video
/**
* Maximum allowed video width.
* Maximum allowed video width. The default value is {@link Integer#MAX_VALUE} (i.e. no
* constraint).
*
* <p>To constrain adaptive video track selections to be suitable for a given viewport (the
* region of the display within which video will be played), use ({@link #viewportWidth}, {@link
* #viewportHeight} and {@link #viewportOrientationMayChange}) instead.
*/
public
final
int
maxVideoWidth
;
/**
* Maximum allowed video height.
* Maximum allowed video height. The default value is {@link Integer#MAX_VALUE} (i.e. no
* constraint).
*
* <p>To constrain adaptive video track selections to be suitable for a given viewport (the
* region of the display within which video will be played), use ({@link #viewportWidth}, {@link
* #viewportHeight} and {@link #viewportOrientationMayChange}) instead.
*/
public
final
int
maxVideoHeight
;
/**
* Maximum video bitrate.
* Maximum video bitrate.
The default value is {@link Integer#MAX_VALUE} (i.e. no constraint).
*/
public
final
int
maxVideoBitrate
;
/**
* Whether to exceed video constraints when no selection can be made otherwise.
* Whether to exceed the {@link #maxVideoWidth}, {@link #maxVideoHeight} and {@link
* #maxVideoBitrate} constraints when no selection can be made otherwise. The default value is
* {@code true}.
*/
public
final
boolean
exceedVideoConstraintsIfNecessary
;
/**
* Viewport width in pixels. Constrains video tracks selections for adaptive playbacks so that
* only tracks suitable for the viewport are selected.
* Viewport width in pixels. Constrains video track selections for adaptive content so that only
* tracks suitable for the viewport are selected. The default value is {@link Integer#MAX_VALUE}
* (i.e. no constraint).
*/
public
final
int
viewportWidth
;
/**
* Viewport height in pixels. Constrains video tracks selections for adaptive playbacks so that
* only tracks suitable for the viewport are selected.
* Viewport height in pixels. Constrains video track selections for adaptive content so that
* only tracks suitable for the viewport are selected. The default value is {@link
* Integer#MAX_VALUE} (i.e. no constraint).
*/
public
final
int
viewportHeight
;
/**
* Whether the viewport orientation may change during playback. Constrains video tracks
* selections for adaptive playbacks so that only tracks suitable for the viewport are selected.
* Whether the viewport orientation may change during playback. Constrains video track
* selections for adaptive content so that only tracks suitable for the viewport are selected.
* The default value is {@code true}.
*/
public
final
boolean
viewportOrientationMayChange
;
// General
/**
* Whether to force selection of the single lowest bitrate audio and video tracks that comply
* with all other constraints.
* with all other constraints.
The default value is {@code false}.
*/
public
final
boolean
forceLowestBitrate
;
/**
* Whether to allow adaptive selections containing mixed mime types.
* Whether to allow adaptive selections containing mixed mime types. The default value is {@code
* false}.
*/
public
final
boolean
allowMixedMimeAdaptiveness
;
/**
* Whether to allow adaptive selections where adaptation may not be completely seamless.
* Whether to allow adaptive selections where adaptation may not be completely seamless. The
* default value is {@code true}.
*/
public
final
boolean
allowNonSeamlessAdaptiveness
;
/**
* Whether to exceed renderer capabilities when no selection can be made otherwise.
*
* <p>This parameter applies when all of the tracks available for a renderer exceed the
* renderer's reported capabilities. If the parameter is {@code true} then the lowest quality
* track will still be selected. Playback may succeed if the renderer has under-reported its
* true capabilities. If {@code false} then no track will be selected. The default value is
* {@code true}.
*/
public
final
boolean
exceedRendererCapabilitiesIfNecessary
;
/**
* The audio session id to use when tunneling, or {@link C#AUDIO_SESSION_ID_UNSET} if tunneling
* is not to be enabled.
* is disabled. The default value is {@link C#AUDIO_SESSION_ID_UNSET} (i.e. tunneling is
* disabled).
*/
public
final
int
tunnelingAudioSessionId
;
private
Parameters
()
{
this
(
new
SparseArray
<
Map
<
TrackGroupArray
,
SelectionOverride
>>(),
new
SparseBooleanArray
(),
null
,
null
,
false
,
0
,
false
,
false
,
true
,
Integer
.
MAX_VALUE
,
Integer
.
MAX_VALUE
,
Integer
.
MAX_VALUE
,
true
,
true
,
Integer
.
MAX_VALUE
,
Integer
.
MAX_VALUE
,
true
,
C
.
AUDIO_SESSION_ID_UNSET
);
/* selectionOverrides= */
new
SparseArray
<
Map
<
TrackGroupArray
,
SelectionOverride
>>(),
/* rendererDisabledFlags= */
new
SparseBooleanArray
(),
/* preferredAudioLanguage= */
null
,
/* preferredTextLanguage= */
null
,
/* selectUndeterminedTextLanguage= */
false
,
/* disabledTextTrackSelectionFlags= */
0
,
/* forceLowestBitrate= */
false
,
/* allowMixedMimeAdaptiveness= */
false
,
/* allowNonSeamlessAdaptiveness= */
true
,
/* maxVideoWidth= */
Integer
.
MAX_VALUE
,
/* maxVideoHeight= */
Integer
.
MAX_VALUE
,
/* maxVideoBitrate= */
Integer
.
MAX_VALUE
,
/* exceedVideoConstraintsIfNecessary= */
true
,
/* exceedRendererCapabilitiesIfNecessary= */
true
,
/* viewportWidth= */
Integer
.
MAX_VALUE
,
/* viewportHeight= */
Integer
.
MAX_VALUE
,
/* viewportOrientationMayChange= */
true
,
/* tunnelingAudioSessionId= */
C
.
AUDIO_SESSION_ID_UNSET
);
}
/* package */
Parameters
(
...
...
library/core/src/main/java/com/google/android/exoplayer2/util/TimestampAdjuster.java
View file @
8cbcfd66
...
...
@@ -30,7 +30,8 @@ public final class TimestampAdjuster {
public
static
final
long
DO_NOT_OFFSET
=
Long
.
MAX_VALUE
;
/**
* The value one greater than the largest representable (33 bit) MPEG-2 TS presentation timestamp.
* The value one greater than the largest representable (33 bit) MPEG-2 TS 90 kHz clock
* presentation timestamp.
*/
private
static
final
long
MAX_PTS_PLUS_ONE
=
0x200000000
L
;
...
...
@@ -38,13 +39,13 @@ public final class TimestampAdjuster {
private
long
timestampOffsetUs
;
// Volatile to allow isInitialized to be called on a different thread to adjustSampleTimestamp.
private
volatile
long
lastSampleTimestamp
;
private
volatile
long
lastSampleTimestamp
Us
;
/**
* @param firstSampleTimestampUs See {@link #setFirstSampleTimestampUs(long)}.
*/
public
TimestampAdjuster
(
long
firstSampleTimestampUs
)
{
lastSampleTimestamp
=
C
.
TIME_UNSET
;
lastSampleTimestamp
Us
=
C
.
TIME_UNSET
;
setFirstSampleTimestampUs
(
firstSampleTimestampUs
);
}
...
...
@@ -56,30 +57,24 @@ public final class TimestampAdjuster {
* {@link #DO_NOT_OFFSET} if presentation timestamps should not be offset.
*/
public
synchronized
void
setFirstSampleTimestampUs
(
long
firstSampleTimestampUs
)
{
Assertions
.
checkState
(
lastSampleTimestamp
==
C
.
TIME_UNSET
);
Assertions
.
checkState
(
lastSampleTimestamp
Us
==
C
.
TIME_UNSET
);
this
.
firstSampleTimestampUs
=
firstSampleTimestampUs
;
}
/**
* Returns the first adjusted sample timestamp in microseconds.
*
* @return The first adjusted sample timestamp in microseconds.
*/
/** Returns the last value passed to {@link #setFirstSampleTimestampUs(long)}. */
public
long
getFirstSampleTimestampUs
()
{
return
firstSampleTimestampUs
;
}
/**
* Returns the last adjusted timestamp. If no timestamp has been adjusted, returns
* {@code firstSampleTimestampUs} as provided to the constructor. If this value is
* {@link #DO_NOT_OFFSET}, returns {@link C#TIME_UNSET}.
*
* @return The last adjusted timestamp. If not present, {@code firstSampleTimestampUs} is
* returned unless equal to {@link #DO_NOT_OFFSET}, in which case {@link C#TIME_UNSET} is
* returned.
* Returns the last value obtained from {@link #adjustSampleTimestamp}. If {@link
* #adjustSampleTimestamp} has not been called, returns the result of calling {@link
* #getFirstSampleTimestampUs()}. If this value is {@link #DO_NOT_OFFSET}, returns {@link
* C#TIME_UNSET}.
*/
public
long
getLastAdjustedTimestampUs
()
{
return
lastSampleTimestamp
!=
C
.
TIME_UNSET
?
lastSampleTimestamp
return
lastSampleTimestampUs
!=
C
.
TIME_UNSET
?
(
lastSampleTimestampUs
+
timestampOffsetUs
)
:
firstSampleTimestampUs
!=
DO_NOT_OFFSET
?
firstSampleTimestampUs
:
C
.
TIME_UNSET
;
}
...
...
@@ -93,44 +88,47 @@ public final class TimestampAdjuster {
* be offset.
*/
public
long
getTimestampOffsetUs
()
{
return
firstSampleTimestampUs
==
DO_NOT_OFFSET
?
0
:
lastSampleTimestamp
==
C
.
TIME_UNSET
?
C
.
TIME_UNSET
:
timestampOffsetUs
;
return
firstSampleTimestampUs
==
DO_NOT_OFFSET
?
0
:
lastSampleTimestampUs
==
C
.
TIME_UNSET
?
C
.
TIME_UNSET
:
timestampOffsetUs
;
}
/**
* Resets the instance to its initial state.
*/
public
void
reset
()
{
lastSampleTimestamp
=
C
.
TIME_UNSET
;
lastSampleTimestamp
Us
=
C
.
TIME_UNSET
;
}
/**
* Scales and offsets an MPEG-2 TS presentation timestamp considering wraparound.
*
* @param pts
The
MPEG-2 TS presentation timestamp.
* @param pts
90Khz A 90 kHz clock
MPEG-2 TS presentation timestamp.
* @return The adjusted timestamp in microseconds.
*/
public
long
adjustTsTimestamp
(
long
pts
)
{
if
(
pts
==
C
.
TIME_UNSET
)
{
public
long
adjustTsTimestamp
(
long
pts
90Khz
)
{
if
(
pts
90Khz
==
C
.
TIME_UNSET
)
{
return
C
.
TIME_UNSET
;
}
if
(
lastSampleTimestamp
!=
C
.
TIME_UNSET
)
{
if
(
lastSampleTimestamp
Us
!=
C
.
TIME_UNSET
)
{
// The wrap count for the current PTS may be closestWrapCount or (closestWrapCount - 1),
// and we need to snap to the one closest to lastSampleTimestamp.
long
lastPts
=
usToPts
(
lastSampleTimestamp
);
// and we need to snap to the one closest to lastSampleTimestamp
Us
.
long
lastPts
=
usToPts
(
lastSampleTimestamp
Us
);
long
closestWrapCount
=
(
lastPts
+
(
MAX_PTS_PLUS_ONE
/
2
))
/
MAX_PTS_PLUS_ONE
;
long
ptsWrapBelow
=
pts
+
(
MAX_PTS_PLUS_ONE
*
(
closestWrapCount
-
1
));
long
ptsWrapAbove
=
pts
+
(
MAX_PTS_PLUS_ONE
*
closestWrapCount
);
pts
=
Math
.
abs
(
ptsWrapBelow
-
lastPts
)
<
Math
.
abs
(
ptsWrapAbove
-
lastPts
)
?
ptsWrapBelow
:
ptsWrapAbove
;
long
ptsWrapBelow
=
pts90Khz
+
(
MAX_PTS_PLUS_ONE
*
(
closestWrapCount
-
1
));
long
ptsWrapAbove
=
pts90Khz
+
(
MAX_PTS_PLUS_ONE
*
closestWrapCount
);
pts90Khz
=
Math
.
abs
(
ptsWrapBelow
-
lastPts
)
<
Math
.
abs
(
ptsWrapAbove
-
lastPts
)
?
ptsWrapBelow
:
ptsWrapAbove
;
}
return
adjustSampleTimestamp
(
ptsToUs
(
pts
));
return
adjustSampleTimestamp
(
ptsToUs
(
pts
90Khz
));
}
/**
* Offsets a
sample
timestamp in microseconds.
* Offsets a timestamp in microseconds.
*
* @param timeUs The timestamp
of a sample to adjust
.
* @param timeUs The timestamp
to adjust in microseconds
.
* @return The adjusted timestamp in microseconds.
*/
public
long
adjustSampleTimestamp
(
long
timeUs
)
{
...
...
@@ -138,15 +136,15 @@ public final class TimestampAdjuster {
return
C
.
TIME_UNSET
;
}
// Record the adjusted PTS to adjust for wraparound next time.
if
(
lastSampleTimestamp
!=
C
.
TIME_UNSET
)
{
lastSampleTimestamp
=
timeUs
;
if
(
lastSampleTimestamp
Us
!=
C
.
TIME_UNSET
)
{
lastSampleTimestamp
Us
=
timeUs
;
}
else
{
if
(
firstSampleTimestampUs
!=
DO_NOT_OFFSET
)
{
// Calculate the timestamp offset.
timestampOffsetUs
=
firstSampleTimestampUs
-
timeUs
;
}
synchronized
(
this
)
{
lastSampleTimestamp
=
timeUs
;
lastSampleTimestamp
Us
=
timeUs
;
// Notify threads waiting for this adjuster to be initialized.
notifyAll
();
}
...
...
@@ -160,15 +158,15 @@ public final class TimestampAdjuster {
* @throws InterruptedException If the thread was interrupted.
*/
public
synchronized
void
waitUntilInitialized
()
throws
InterruptedException
{
while
(
lastSampleTimestamp
==
C
.
TIME_UNSET
)
{
while
(
lastSampleTimestamp
Us
==
C
.
TIME_UNSET
)
{
wait
();
}
}
/**
* Converts a
value in MPEG-2 timestamp units to the corresponding value
in microseconds.
* Converts a
90 kHz clock timestamp to a timestamp
in microseconds.
*
* @param pts A
value in MPEG-2 timestamp units
.
* @param pts A
90 kHz clock timestamp
.
* @return The corresponding value in microseconds.
*/
public
static
long
ptsToUs
(
long
pts
)
{
...
...
@@ -176,10 +174,10 @@ public final class TimestampAdjuster {
}
/**
* Converts a
value in microseconds to the corresponding values in MPEG-2 timestamp units
.
* Converts a
timestamp in microseconds to a 90 kHz clock timestamp
.
*
* @param us A value in microseconds.
* @return The corresponding value
in MPEG-2 timestamp units
.
* @return The corresponding value
as a 90 kHz clock timestamp
.
*/
public
static
long
usToPts
(
long
us
)
{
return
(
us
*
90000
)
/
C
.
MICROS_PER_SECOND
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/util/XmlPullParserUtil.java
View file @
8cbcfd66
...
...
@@ -56,8 +56,7 @@ public final class XmlPullParserUtil {
* @return Whether the current event is a start tag with the specified name.
* @throws XmlPullParserException If an error occurs querying the parser.
*/
public
static
boolean
isStartTag
(
XmlPullParser
xpp
,
String
name
)
throws
XmlPullParserException
{
public
static
boolean
isStartTag
(
XmlPullParser
xpp
,
String
name
)
throws
XmlPullParserException
{
return
isStartTag
(
xpp
)
&&
xpp
.
getName
().
equals
(
name
);
}
...
...
@@ -73,21 +72,58 @@ public final class XmlPullParserUtil {
}
/**
* Returns whether the current event is a start tag with the specified name. If the current event
* has a raw name then its prefix is stripped before matching.
*
* @param xpp The {@link XmlPullParser} to query.
* @param name The specified name.
* @return Whether the current event is a start tag with the specified name.
* @throws XmlPullParserException If an error occurs querying the parser.
*/
public
static
boolean
isStartTagIgnorePrefix
(
XmlPullParser
xpp
,
String
name
)
throws
XmlPullParserException
{
return
isStartTag
(
xpp
)
&&
stripPrefix
(
xpp
.
getName
()).
equals
(
name
);
}
/**
* Returns the value of an attribute of the current start tag.
*
* @param xpp The {@link XmlPullParser} to query.
* @param attributeName The name of the attribute.
* @return The value of the attribute, or null if the current event is not a start tag or if no
*
no
such attribute was found.
* such attribute was found.
*/
public
static
String
getAttributeValue
(
XmlPullParser
xpp
,
String
attributeName
)
{
int
attributeCount
=
xpp
.
getAttributeCount
();
for
(
int
i
=
0
;
i
<
attributeCount
;
i
++)
{
if
(
attributeName
.
equals
(
xpp
.
getAttributeName
(
i
)))
{
if
(
xpp
.
getAttributeName
(
i
).
equals
(
attributeName
))
{
return
xpp
.
getAttributeValue
(
i
);
}
}
return
null
;
}
/**
* Returns the value of an attribute of the current start tag. Any raw attribute names in the
* current start tag have their prefixes stripped before matching.
*
* @param xpp The {@link XmlPullParser} to query.
* @param attributeName The name of the attribute.
* @return The value of the attribute, or null if the current event is not a start tag or if no
* such attribute was found.
*/
public
static
String
getAttributeValueIgnorePrefix
(
XmlPullParser
xpp
,
String
attributeName
)
{
int
attributeCount
=
xpp
.
getAttributeCount
();
for
(
int
i
=
0
;
i
<
attributeCount
;
i
++)
{
if
(
stripPrefix
(
xpp
.
getAttributeName
(
i
)).
equals
(
attributeName
))
{
return
xpp
.
getAttributeValue
(
i
);
}
}
return
null
;
}
private
static
String
stripPrefix
(
String
name
)
{
int
prefixSeparatorIndex
=
name
.
indexOf
(
':'
);
return
prefixSeparatorIndex
==
-
1
?
name
:
name
.
substring
(
prefixSeparatorIndex
+
1
);
}
}
library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java
View file @
8cbcfd66
...
...
@@ -84,6 +84,9 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
// pending output streams that have fewer frames than the codec latency.
private
static
final
int
MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT
=
10
;
private
static
boolean
evaluatedDeviceNeedsSetOutputSurfaceWorkaround
;
private
static
boolean
deviceNeedsSetOutputSurfaceWorkaround
;
private
final
Context
context
;
private
final
VideoFrameReleaseTimeHelper
frameReleaseTimeHelper
;
private
final
EventDispatcher
eventDispatcher
;
...
...
@@ -459,7 +462,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
if
(
areAdaptationCompatible
(
codecInfo
.
adaptive
,
oldFormat
,
newFormat
)
&&
newFormat
.
width
<=
codecMaxValues
.
width
&&
newFormat
.
height
<=
codecMaxValues
.
height
&&
getMaxInputSize
(
newFormat
)
<=
codecMaxValues
.
inputSize
)
{
&&
getMaxInputSize
(
codecInfo
,
newFormat
)
<=
codecMaxValues
.
inputSize
)
{
return
oldFormat
.
initializationDataEquals
(
newFormat
)
?
KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION
:
KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION
;
...
...
@@ -981,7 +984,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
throws
DecoderQueryException
{
int
maxWidth
=
format
.
width
;
int
maxHeight
=
format
.
height
;
int
maxInputSize
=
getMaxInputSize
(
format
);
int
maxInputSize
=
getMaxInputSize
(
codecInfo
,
format
);
if
(
streamFormats
.
length
==
1
)
{
// The single entry in streamFormats must correspond to the format for which the codec is
// being configured.
...
...
@@ -994,7 +997,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
(
streamFormat
.
width
==
Format
.
NO_VALUE
||
streamFormat
.
height
==
Format
.
NO_VALUE
);
maxWidth
=
Math
.
max
(
maxWidth
,
streamFormat
.
width
);
maxHeight
=
Math
.
max
(
maxHeight
,
streamFormat
.
height
);
maxInputSize
=
Math
.
max
(
maxInputSize
,
getMaxInputSize
(
streamFormat
));
maxInputSize
=
Math
.
max
(
maxInputSize
,
getMaxInputSize
(
codecInfo
,
streamFormat
));
}
}
if
(
haveUnknownDimensions
)
{
...
...
@@ -1004,7 +1007,9 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
maxWidth
=
Math
.
max
(
maxWidth
,
codecMaxSize
.
x
);
maxHeight
=
Math
.
max
(
maxHeight
,
codecMaxSize
.
y
);
maxInputSize
=
Math
.
max
(
maxInputSize
,
getMaxInputSize
(
format
.
sampleMimeType
,
maxWidth
,
maxHeight
));
Math
.
max
(
maxInputSize
,
getMaxInputSize
(
codecInfo
,
format
.
sampleMimeType
,
maxWidth
,
maxHeight
));
Log
.
w
(
TAG
,
"Codec max resolution adjusted to: "
+
maxWidth
+
"x"
+
maxHeight
);
}
}
...
...
@@ -1053,13 +1058,14 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
}
/**
* Returns a maximum input buffer size for a given format.
* Returns a maximum input buffer size for a given
codec and
format.
*
* @param codecInfo Information about the {@link MediaCodec} being configured.
* @param format The format.
* @return A maximum input buffer size in bytes, or {@link Format#NO_VALUE} if a maximum could not
* be determined.
*/
private
static
int
getMaxInputSize
(
Format
format
)
{
private
static
int
getMaxInputSize
(
MediaCodecInfo
codecInfo
,
Format
format
)
{
if
(
format
.
maxInputSize
!=
Format
.
NO_VALUE
)
{
// The format defines an explicit maximum input size. Add the total size of initialization
// data buffers, as they may need to be queued in the same input buffer as the largest sample.
...
...
@@ -1072,20 +1078,22 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
}
else
{
// Calculated maximum input sizes are overestimates, so it's not necessary to add the size of
// initialization data.
return
getMaxInputSize
(
format
.
sampleMimeType
,
format
.
width
,
format
.
height
);
return
getMaxInputSize
(
codecInfo
,
format
.
sampleMimeType
,
format
.
width
,
format
.
height
);
}
}
/**
* Returns a maximum input size for a given mime type, width and height.
* Returns a maximum input size for a given
codec,
mime type, width and height.
*
* @param codecInfo Information about the {@link MediaCodec} being configured.
* @param sampleMimeType The format mime type.
* @param width The width in pixels.
* @param height The height in pixels.
* @return A maximum input size in bytes, or {@link Format#NO_VALUE} if a maximum could not be
* determined.
*/
private
static
int
getMaxInputSize
(
String
sampleMimeType
,
int
width
,
int
height
)
{
private
static
int
getMaxInputSize
(
MediaCodecInfo
codecInfo
,
String
sampleMimeType
,
int
width
,
int
height
)
{
if
(
width
==
Format
.
NO_VALUE
||
height
==
Format
.
NO_VALUE
)
{
// We can't infer a maximum input size without video dimensions.
return
Format
.
NO_VALUE
;
...
...
@@ -1101,9 +1109,12 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
minCompressionRatio
=
2
;
break
;
case
MimeTypes
.
VIDEO_H264
:
if
(
"BRAVIA 4K 2015"
.
equals
(
Util
.
MODEL
))
{
// The Sony BRAVIA 4k TV has input buffers that are too small for the calculated 4k video
// maximum input size, so use the default value.
if
(
"BRAVIA 4K 2015"
.
equals
(
Util
.
MODEL
)
// Sony Bravia 4K
||
(
"Amazon"
.
equals
(
Util
.
MANUFACTURER
)
&&
(
"KFSOWI"
.
equals
(
Util
.
MODEL
)
// Kindle Soho
||
(
"AFTS"
.
equals
(
Util
.
MODEL
)
&&
codecInfo
.
secure
))))
{
// Fire TV Gen 2
// Use the default value for cases where platform limitations may prevent buffers of the
// calculated maximum input size from being allocated.
return
Format
.
NO_VALUE
;
}
// Round up width/height to an integer number of macroblocks.
...
...
@@ -1163,14 +1174,36 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
return
Util
.
SDK_INT
<=
22
&&
"foster"
.
equals
(
Util
.
DEVICE
)
&&
"NVIDIA"
.
equals
(
Util
.
MANUFACTURER
);
}
/*
* TODO:
*
* 1. Validate that Android device certification now ensures correct behavior, and add a
* corresponding SDK_INT upper bound for applying the workaround (probably SDK_INT < 26).
* 2. Determine a complete list of affected devices.
* 3. Some of the devices in this list only fail to support setOutputSurface when switching from
* a SurfaceView provided Surface to a Surface of another type (e.g. TextureView/DummySurface),
* and vice versa. One hypothesis is that setOutputSurface fails when the surfaces have
* different pixel formats. If we can find a way to query the Surface instances to determine
* whether this case applies, then we'll be able to provide a more targeted workaround.
*/
/**
* Returns whether the
device
is known to implement {@link MediaCodec#setOutputSurface(Surface)}
* Returns whether the
codec
is known to implement {@link MediaCodec#setOutputSurface(Surface)}
* incorrectly.
* <p>
* If true is returned then we fall back to releasing and re-instantiating the codec instead.
*
* <p>If true is returned then we fall back to releasing and re-instantiating the codec instead.
*
* @param name The name of the codec.
* @return True if the device is known to implement {@link MediaCodec#setOutputSurface(Surface)}
* incorrectly.
*/
private
static
boolean
codecNeedsSetOutputSurfaceWorkaround
(
String
name
)
{
// Work around https://github.com/google/ExoPlayer/issues/3236,
protected
boolean
codecNeedsSetOutputSurfaceWorkaround
(
String
name
)
{
if
(
Util
.
SDK_INT
>=
27
||
name
.
startsWith
(
"OMX.google"
))
{
// Devices running API level 27 or later should also be unaffected. Google OMX decoders are
// not known to have this issue on any API level.
return
false
;
}
// Work around:
// https://github.com/google/ExoPlayer/issues/3236,
// https://github.com/google/ExoPlayer/issues/3355,
// https://github.com/google/ExoPlayer/issues/3439,
// https://github.com/google/ExoPlayer/issues/3724,
...
...
@@ -1179,28 +1212,150 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
// 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/4315.
return
((
"deb"
.
equals
(
Util
.
DEVICE
)
// Nexus 7 (2013)
||
"flo"
.
equals
(
Util
.
DEVICE
)
// Nexus 7 (2013)
||
"mido"
.
equals
(
Util
.
DEVICE
)
// Redmi Note 4
||
"santoni"
.
equals
(
Util
.
DEVICE
))
// Redmi 4X
&&
"OMX.qcom.video.decoder.avc"
.
equals
(
name
))
||
((
"tcl_eu"
.
equals
(
Util
.
DEVICE
)
// TCL Percee TV
||
"SVP-DTV15"
.
equals
(
Util
.
DEVICE
)
// Sony Bravia 4K 2015
||
"BRAVIA_ATV2"
.
equals
(
Util
.
DEVICE
)
// Sony Bravia 4K GB
||
Util
.
DEVICE
.
startsWith
(
"panell_"
)
// Motorola Moto C Plus
||
"F3311"
.
equals
(
Util
.
DEVICE
)
// Sony Xperia E5
||
"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)
||
"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
&&
"OMX.k3.video.decoder.avc"
.
equals
(
name
))
||
((
"HUAWEI VNS-L21"
.
equals
(
Util
.
MODEL
))
// Huawei P9 Lite
&&
"OMX.IMG.MSVDX.Decoder.AVC"
.
equals
(
name
));
// https://github.com/google/ExoPlayer/issues/4315,
// https://github.com/google/ExoPlayer/issues/4419,
// https://github.com/google/ExoPlayer/issues/4460,
// https://github.com/google/ExoPlayer/issues/4468.
synchronized
(
MediaCodecVideoRenderer
.
class
)
{
if
(!
evaluatedDeviceNeedsSetOutputSurfaceWorkaround
)
{
switch
(
Util
.
DEVICE
)
{
case
"1601"
:
case
"1713"
:
case
"1714"
:
case
"A10-70F"
:
case
"A1601"
:
case
"A2016a40"
:
case
"A7000-a"
:
case
"A7000plus"
:
case
"A7010a48"
:
case
"A7020a48"
:
case
"AquaPowerM"
:
case
"Aura_Note_2"
:
case
"BLACK-1X"
:
case
"BRAVIA_ATV2"
:
case
"C1"
:
case
"ComioS1"
:
case
"CP8676_I02"
:
case
"CPH1609"
:
case
"CPY83_I00"
:
case
"cv1"
:
case
"cv3"
:
case
"deb"
:
case
"E5643"
:
case
"ELUGA_A3_Pro"
:
case
"ELUGA_Note"
:
case
"ELUGA_Prim"
:
case
"ELUGA_Ray_X"
:
case
"EverStar_S"
:
case
"F3111"
:
case
"F3113"
:
case
"F3116"
:
case
"F3211"
:
case
"F3213"
:
case
"F3215"
:
case
"F3311"
:
case
"flo"
:
case
"GiONEE_CBL7513"
:
case
"GiONEE_GBL7319"
:
case
"GIONEE_GBL7360"
:
case
"GIONEE_SWW1609"
:
case
"GIONEE_SWW1627"
:
case
"GIONEE_SWW1631"
:
case
"GIONEE_WBL5708"
:
case
"GIONEE_WBL7365"
:
case
"GIONEE_WBL7519"
:
case
"griffin"
:
case
"htc_e56ml_dtul"
:
case
"hwALE-H"
:
case
"HWBLN-H"
:
case
"HWCAM-H"
:
case
"HWVNS-H"
:
case
"iball8735_9806"
:
case
"Infinix-X572"
:
case
"iris60"
:
case
"itel_S41"
:
case
"j2xlteins"
:
case
"JGZ"
:
case
"K50a40"
:
case
"le_x6"
:
case
"LS-5017"
:
case
"M5c"
:
case
"manning"
:
case
"marino_f"
:
case
"MEIZU_M5"
:
case
"mh"
:
case
"mido"
:
case
"MX6"
:
case
"namath"
:
case
"nicklaus_f"
:
case
"NX541J"
:
case
"NX573J"
:
case
"OnePlus5T"
:
case
"p212"
:
case
"P681"
:
case
"P85"
:
case
"panell_d"
:
case
"panell_dl"
:
case
"panell_ds"
:
case
"panell_dt"
:
case
"PB2-670M"
:
case
"PGN528"
:
case
"PGN610"
:
case
"PGN611"
:
case
"Phantom6"
:
case
"Pixi4-7_3G"
:
case
"Pixi5-10_4G"
:
case
"PLE"
:
case
"PRO7S"
:
case
"Q350"
:
case
"Q4260"
:
case
"Q427"
:
case
"Q4310"
:
case
"Q5"
:
case
"QM16XE_U"
:
case
"QX1"
:
case
"santoni"
:
case
"Slate_Pro"
:
case
"SVP-DTV15"
:
case
"s905x018"
:
case
"taido_row"
:
case
"TB3-730F"
:
case
"TB3-730X"
:
case
"TB3-850F"
:
case
"TB3-850M"
:
case
"tcl_eu"
:
case
"V1"
:
case
"V23GB"
:
case
"V5"
:
case
"vernee_M5"
:
case
"watson"
:
case
"whyred"
:
case
"woods_f"
:
case
"woods_fn"
:
case
"X3_HK"
:
case
"XE2X"
:
case
"XT1663"
:
case
"Z12_PRO"
:
case
"Z80"
:
deviceNeedsSetOutputSurfaceWorkaround
=
true
;
break
;
default
:
// Do nothing.
break
;
}
switch
(
Util
.
MODEL
)
{
case
"AFTA"
:
case
"AFTN"
:
deviceNeedsSetOutputSurfaceWorkaround
=
true
;
break
;
default
:
// Do nothing.
break
;
}
evaluatedDeviceNeedsSetOutputSurfaceWorkaround
=
true
;
}
}
return
deviceNeedsSetOutputSurfaceWorkaround
;
}
protected
static
final
class
CodecMaxValues
{
...
...
library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java
View file @
8cbcfd66
...
...
@@ -18,6 +18,7 @@ package com.google.android.exoplayer2;
import
static
com
.
google
.
common
.
truth
.
Truth
.
assertThat
;
import
static
org
.
junit
.
Assert
.
fail
;
import
android.support.annotation.Nullable
;
import
android.view.Surface
;
import
com.google.android.exoplayer2.Player.DefaultEventListener
;
import
com.google.android.exoplayer2.Player.EventListener
;
...
...
@@ -1981,6 +1982,44 @@ public final class ExoPlayerTest {
}
@Test
public
void
testRepeatedSeeksToUnpreparedPeriodInSameWindowKeepsWindowSequenceNumber
()
throws
Exception
{
Timeline
timeline
=
new
FakeTimeline
(
new
TimelineWindowDefinition
(
/* periodCount= */
2
,
/* id= */
0
,
/* isSeekable= */
true
,
/* isDynamic= */
false
,
/* durationUs= */
10
*
C
.
MICROS_PER_SECOND
));
FakeMediaSource
mediaSource
=
new
FakeMediaSource
(
timeline
,
/* manifest= */
null
);
ActionSchedule
actionSchedule
=
new
ActionSchedule
.
Builder
(
"testSeekToUnpreparedPeriod"
)
.
pause
()
.
waitForPlaybackState
(
Player
.
STATE_READY
)
.
seek
(
/* windowIndex= */
0
,
/* positionMs= */
9999
)
.
seek
(
/* windowIndex= */
0
,
/* positionMs= */
1
)
.
seek
(
/* windowIndex= */
0
,
/* positionMs= */
9999
)
.
play
()
.
build
();
ExoPlayerTestRunner
testRunner
=
new
ExoPlayerTestRunner
.
Builder
()
.
setMediaSource
(
mediaSource
)
.
setActionSchedule
(
actionSchedule
)
.
build
()
.
start
()
.
blockUntilEnded
(
TIMEOUT_MS
);
testRunner
.
assertPlayedPeriodIndices
(
0
,
1
,
0
,
1
);
assertThat
(
mediaSource
.
getCreatedMediaPeriods
())
.
containsAllOf
(
new
MediaPeriodId
(
/* periodIndex= */
0
,
/* windowSequenceNumber= */
0
),
new
MediaPeriodId
(
/* periodIndex= */
1
,
/* windowSequenceNumber= */
0
));
assertThat
(
mediaSource
.
getCreatedMediaPeriods
())
.
doesNotContain
(
new
MediaPeriodId
(
/* periodIndex= */
1
,
/* windowSequenceNumber= */
1
));
}
@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.
...
...
@@ -2040,7 +2079,7 @@ public final class ExoPlayerTest {
final
EventListener
eventListener
=
new
DefaultEventListener
()
{
@Override
public
void
onTimelineChanged
(
Timeline
timeline
,
Object
manifest
,
int
reason
)
{
public
void
onTimelineChanged
(
Timeline
timeline
,
@Nullable
Object
manifest
,
int
reason
)
{
if
(
timeline
.
isEmpty
())
{
playerReference
.
get
().
setPlayWhenReady
(
/* playWhenReady= */
false
);
}
...
...
library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java
View file @
8cbcfd66
...
...
@@ -90,6 +90,19 @@ public final class AdPlaybackStateTest {
}
@Test
public
void
testGetFirstAdIndexToPlaySkipsSkippedAd
()
{
state
=
state
.
withAdCount
(
/* adGroupIndex= */
0
,
/* adCount= */
3
);
state
=
state
.
withAdUri
(
/* adGroupIndex= */
0
,
/* adIndexInAdGroup= */
0
,
TEST_URI
);
state
=
state
.
withAdUri
(
/* adGroupIndex= */
0
,
/* adIndexInAdGroup= */
2
,
TEST_URI
);
state
=
state
.
withSkippedAd
(
/* adGroupIndex= */
0
,
/* adIndexInAdGroup= */
0
);
assertThat
(
state
.
adGroups
[
0
].
getFirstAdIndexToPlay
()).
isEqualTo
(
1
);
assertThat
(
state
.
adGroups
[
0
].
states
[
1
]).
isEqualTo
(
AdPlaybackState
.
AD_STATE_UNAVAILABLE
);
assertThat
(
state
.
adGroups
[
0
].
states
[
2
]).
isEqualTo
(
AdPlaybackState
.
AD_STATE_AVAILABLE
);
}
@Test
public
void
testGetFirstAdIndexToPlaySkipsErrorAds
()
{
state
=
state
.
withAdCount
(
/* adGroupIndex= */
0
,
/* adCount= */
3
);
state
=
state
.
withAdUri
(
/* adGroupIndex= */
0
,
/* adIndexInAdGroup= */
0
,
TEST_URI
);
...
...
library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java
View file @
8cbcfd66
...
...
@@ -18,7 +18,6 @@ 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
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.Format
;
...
...
@@ -62,7 +61,6 @@ import java.util.List;
/* package */
final
int
id
;
private
final
DashChunkSource
.
Factory
chunkSourceFactory
;
private
final
int
minLoadableRetryCount
;
private
final
EventDispatcher
eventDispatcher
;
private
final
long
elapsedRealtimeOffset
;
private
final
LoaderErrorThrower
manifestLoaderErrorThrower
;
private
final
Allocator
allocator
;
...
...
@@ -73,6 +71,7 @@ import java.util.List;
private
final
IdentityHashMap
<
ChunkSampleStream
<
DashChunkSource
>,
PlayerTrackEmsgHandler
>
trackEmsgHandlerBySampleStream
;
private
EventDispatcher
eventDispatcher
;
private
@Nullable
Callback
callback
;
private
ChunkSampleStream
<
DashChunkSource
>[]
sampleStreams
;
private
EventSampleStream
[]
eventSampleStreams
;
...
...
@@ -127,6 +126,13 @@ import java.util.List;
*/
public
void
updateManifest
(
DashManifest
manifest
,
int
periodIndex
)
{
this
.
manifest
=
manifest
;
if
(
this
.
periodIndex
!=
periodIndex
)
{
eventDispatcher
=
eventDispatcher
.
withParameters
(
/* windowIndex= */
0
,
eventDispatcher
.
mediaPeriodId
.
copyWithPeriodIndex
(
periodIndex
),
manifest
.
getPeriod
(
periodIndex
).
startMs
);
}
this
.
periodIndex
=
periodIndex
;
playerEmsgHandler
.
updateManifest
(
manifest
);
if
(
sampleStreams
!=
null
)
{
...
...
@@ -139,7 +145,10 @@ import java.util.List;
for
(
EventSampleStream
eventSampleStream
:
eventSampleStreams
)
{
for
(
EventStream
eventStream
:
eventStreams
)
{
if
(
eventStream
.
id
().
equals
(
eventSampleStream
.
eventStreamId
()))
{
eventSampleStream
.
updateEventStream
(
eventStream
,
manifest
.
dynamic
);
int
lastPeriodIndex
=
manifest
.
getPeriodCount
()
-
1
;
eventSampleStream
.
updateEventStream
(
eventStream
,
/* eventStreamAppendable= */
manifest
.
dynamic
&&
periodIndex
==
lastPeriodIndex
);
break
;
}
}
...
...
@@ -186,126 +195,34 @@ import java.util.List;
@Override
public
long
selectTracks
(
TrackSelection
[]
selections
,
boolean
[]
mayRetainStreamFlags
,
SampleStream
[]
streams
,
boolean
[]
streamResetFlags
,
long
positionUs
)
{
SparseArray
<
ChunkSampleStream
<
DashChunkSource
>>
primarySampleStreams
=
new
SparseArray
<>();
List
<
EventSampleStream
>
eventSampleStreamList
=
new
ArrayList
<>();
selectPrimarySampleStreams
(
selections
,
mayRetainStreamFlags
,
streams
,
streamResetFlags
,
positionUs
,
primarySampleStreams
);
selectEventSampleStreams
(
selections
,
mayRetainStreamFlags
,
streams
,
streamResetFlags
,
eventSampleStreamList
);
selectEmbeddedSampleStreams
(
selections
,
mayRetainStreamFlags
,
streams
,
streamResetFlags
,
positionUs
,
primarySampleStreams
);
sampleStreams
=
newSampleStreamArray
(
primarySampleStreams
.
size
());
for
(
int
i
=
0
;
i
<
sampleStreams
.
length
;
i
++)
{
sampleStreams
[
i
]
=
primarySampleStreams
.
valueAt
(
i
);
int
[]
streamIndexToTrackGroupIndex
=
getStreamIndexToTrackGroupIndex
(
selections
);
releaseDisabledStreams
(
selections
,
mayRetainStreamFlags
,
streams
);
releaseOrphanEmbeddedStreams
(
selections
,
streams
,
streamIndexToTrackGroupIndex
);
selectNewStreams
(
selections
,
streams
,
streamResetFlags
,
positionUs
,
streamIndexToTrackGroupIndex
);
ArrayList
<
ChunkSampleStream
<
DashChunkSource
>>
sampleStreamList
=
new
ArrayList
<>();
ArrayList
<
EventSampleStream
>
eventSampleStreamList
=
new
ArrayList
<>();
for
(
SampleStream
sampleStream
:
streams
)
{
if
(
sampleStream
instanceof
ChunkSampleStream
)
{
@SuppressWarnings
(
"unchecked"
)
ChunkSampleStream
<
DashChunkSource
>
stream
=
(
ChunkSampleStream
<
DashChunkSource
>)
sampleStream
;
sampleStreamList
.
add
(
stream
);
}
else
if
(
sampleStream
instanceof
EventSampleStream
)
{
eventSampleStreamList
.
add
((
EventSampleStream
)
sampleStream
);
}
}
sampleStreams
=
newSampleStreamArray
(
sampleStreamList
.
size
());
sampleStreamList
.
toArray
(
sampleStreams
);
eventSampleStreams
=
new
EventSampleStream
[
eventSampleStreamList
.
size
()];
eventSampleStreamList
.
toArray
(
eventSampleStreams
);
compositeSequenceableLoader
=
compositeSequenceableLoaderFactory
.
createCompositeSequenceableLoader
(
sampleStreams
);
return
positionUs
;
}
private
void
selectPrimarySampleStreams
(
TrackSelection
[]
selections
,
boolean
[]
mayRetainStreamFlags
,
SampleStream
[]
streams
,
boolean
[]
streamResetFlags
,
long
positionUs
,
SparseArray
<
ChunkSampleStream
<
DashChunkSource
>>
primarySampleStreams
)
{
for
(
int
i
=
0
;
i
<
selections
.
length
;
i
++)
{
if
(
streams
[
i
]
instanceof
ChunkSampleStream
)
{
@SuppressWarnings
(
"unchecked"
)
ChunkSampleStream
<
DashChunkSource
>
stream
=
(
ChunkSampleStream
<
DashChunkSource
>)
streams
[
i
];
if
(
selections
[
i
]
==
null
||
!
mayRetainStreamFlags
[
i
])
{
stream
.
release
(
this
);
streams
[
i
]
=
null
;
}
else
{
int
trackGroupIndex
=
trackGroups
.
indexOf
(
selections
[
i
].
getTrackGroup
());
primarySampleStreams
.
put
(
trackGroupIndex
,
stream
);
}
}
if
(
streams
[
i
]
==
null
&&
selections
[
i
]
!=
null
)
{
int
trackGroupIndex
=
trackGroups
.
indexOf
(
selections
[
i
].
getTrackGroup
());
TrackGroupInfo
trackGroupInfo
=
trackGroupInfos
[
trackGroupIndex
];
if
(
trackGroupInfo
.
trackGroupCategory
==
TrackGroupInfo
.
CATEGORY_PRIMARY
)
{
ChunkSampleStream
<
DashChunkSource
>
stream
=
buildSampleStream
(
trackGroupInfo
,
selections
[
i
],
positionUs
);
primarySampleStreams
.
put
(
trackGroupIndex
,
stream
);
streams
[
i
]
=
stream
;
streamResetFlags
[
i
]
=
true
;
}
}
}
}
private
void
selectEventSampleStreams
(
TrackSelection
[]
selections
,
boolean
[]
mayRetainStreamFlags
,
SampleStream
[]
streams
,
boolean
[]
streamResetFlags
,
List
<
EventSampleStream
>
eventSampleStreamsList
)
{
for
(
int
i
=
0
;
i
<
selections
.
length
;
i
++)
{
if
(
streams
[
i
]
instanceof
EventSampleStream
)
{
EventSampleStream
stream
=
(
EventSampleStream
)
streams
[
i
];
if
(
selections
[
i
]
==
null
||
!
mayRetainStreamFlags
[
i
])
{
streams
[
i
]
=
null
;
}
else
{
eventSampleStreamsList
.
add
(
stream
);
}
}
if
(
streams
[
i
]
==
null
&&
selections
[
i
]
!=
null
)
{
int
trackGroupIndex
=
trackGroups
.
indexOf
(
selections
[
i
].
getTrackGroup
());
TrackGroupInfo
trackGroupInfo
=
trackGroupInfos
[
trackGroupIndex
];
if
(
trackGroupInfo
.
trackGroupCategory
==
TrackGroupInfo
.
CATEGORY_MANIFEST_EVENTS
)
{
EventStream
eventStream
=
eventStreams
.
get
(
trackGroupInfo
.
eventStreamGroupIndex
);
Format
format
=
selections
[
i
].
getTrackGroup
().
getFormat
(
0
);
EventSampleStream
stream
=
new
EventSampleStream
(
eventStream
,
format
,
manifest
.
dynamic
);
streams
[
i
]
=
stream
;
streamResetFlags
[
i
]
=
true
;
eventSampleStreamsList
.
add
(
stream
);
}
}
}
}
private
void
selectEmbeddedSampleStreams
(
TrackSelection
[]
selections
,
boolean
[]
mayRetainStreamFlags
,
SampleStream
[]
streams
,
boolean
[]
streamResetFlags
,
long
positionUs
,
SparseArray
<
ChunkSampleStream
<
DashChunkSource
>>
primarySampleStreams
)
{
for
(
int
i
=
0
;
i
<
selections
.
length
;
i
++)
{
if
((
streams
[
i
]
instanceof
EmbeddedSampleStream
||
streams
[
i
]
instanceof
EmptySampleStream
)
&&
(
selections
[
i
]
==
null
||
!
mayRetainStreamFlags
[
i
]))
{
// The stream is for an embedded track and is either no longer selected or needs replacing.
releaseIfEmbeddedSampleStream
(
streams
[
i
]);
streams
[
i
]
=
null
;
}
// We need to consider replacing the stream even if it's non-null because the primary stream
// may have been replaced, selected or deselected.
if
(
selections
[
i
]
!=
null
)
{
int
trackGroupIndex
=
trackGroups
.
indexOf
(
selections
[
i
].
getTrackGroup
());
TrackGroupInfo
trackGroupInfo
=
trackGroupInfos
[
trackGroupIndex
];
if
(
trackGroupInfo
.
trackGroupCategory
==
TrackGroupInfo
.
CATEGORY_EMBEDDED
)
{
ChunkSampleStream
<?>
primaryStream
=
primarySampleStreams
.
get
(
trackGroupInfo
.
primaryTrackGroupIndex
);
SampleStream
stream
=
streams
[
i
];
boolean
mayRetainStream
=
primaryStream
==
null
?
stream
instanceof
EmptySampleStream
:
(
stream
instanceof
EmbeddedSampleStream
&&
((
EmbeddedSampleStream
)
stream
).
parent
==
primaryStream
);
if
(!
mayRetainStream
)
{
releaseIfEmbeddedSampleStream
(
stream
);
streams
[
i
]
=
primaryStream
==
null
?
new
EmptySampleStream
()
:
primaryStream
.
selectEmbeddedTrack
(
positionUs
,
trackGroupInfo
.
trackType
);
streamResetFlags
[
i
]
=
true
;
}
}
}
}
}
@Override
public
void
discardBuffer
(
long
positionUs
,
boolean
toKeyframe
)
{
for
(
ChunkSampleStream
<
DashChunkSource
>
sampleStream
:
sampleStreams
)
{
...
...
@@ -372,6 +289,124 @@ import java.util.List;
// Internal methods.
private
int
[]
getStreamIndexToTrackGroupIndex
(
TrackSelection
[]
selections
)
{
int
[]
streamIndexToTrackGroupIndex
=
new
int
[
selections
.
length
];
for
(
int
i
=
0
;
i
<
selections
.
length
;
i
++)
{
if
(
selections
[
i
]
!=
null
)
{
streamIndexToTrackGroupIndex
[
i
]
=
trackGroups
.
indexOf
(
selections
[
i
].
getTrackGroup
());
}
else
{
streamIndexToTrackGroupIndex
[
i
]
=
C
.
INDEX_UNSET
;
}
}
return
streamIndexToTrackGroupIndex
;
}
private
void
releaseDisabledStreams
(
TrackSelection
[]
selections
,
boolean
[]
mayRetainStreamFlags
,
SampleStream
[]
streams
)
{
for
(
int
i
=
0
;
i
<
selections
.
length
;
i
++)
{
if
(
selections
[
i
]
==
null
||
!
mayRetainStreamFlags
[
i
])
{
if
(
streams
[
i
]
instanceof
ChunkSampleStream
)
{
@SuppressWarnings
(
"unchecked"
)
ChunkSampleStream
<
DashChunkSource
>
stream
=
(
ChunkSampleStream
<
DashChunkSource
>)
streams
[
i
];
stream
.
release
(
this
);
}
else
if
(
streams
[
i
]
instanceof
EmbeddedSampleStream
)
{
((
EmbeddedSampleStream
)
streams
[
i
]).
release
();
}
streams
[
i
]
=
null
;
}
}
}
private
void
releaseOrphanEmbeddedStreams
(
TrackSelection
[]
selections
,
SampleStream
[]
streams
,
int
[]
streamIndexToTrackGroupIndex
)
{
for
(
int
i
=
0
;
i
<
selections
.
length
;
i
++)
{
if
(
streams
[
i
]
instanceof
EmptySampleStream
||
streams
[
i
]
instanceof
EmbeddedSampleStream
)
{
// We need to release an embedded stream if the corresponding primary stream is released.
int
primaryStreamIndex
=
getPrimaryStreamIndex
(
i
,
streamIndexToTrackGroupIndex
);
boolean
mayRetainStream
;
if
(
primaryStreamIndex
==
C
.
INDEX_UNSET
)
{
// If the corresponding primary stream is not selected, we may retain an existing
// EmptySampleStream.
mayRetainStream
=
streams
[
i
]
instanceof
EmptySampleStream
;
}
else
{
// If the corresponding primary stream is selected, we may retain the embedded stream if
// the stream's parent still matches.
mayRetainStream
=
(
streams
[
i
]
instanceof
EmbeddedSampleStream
)
&&
((
EmbeddedSampleStream
)
streams
[
i
]).
parent
==
streams
[
primaryStreamIndex
];
}
if
(!
mayRetainStream
)
{
if
(
streams
[
i
]
instanceof
EmbeddedSampleStream
)
{
((
EmbeddedSampleStream
)
streams
[
i
]).
release
();
}
streams
[
i
]
=
null
;
}
}
}
}
private
void
selectNewStreams
(
TrackSelection
[]
selections
,
SampleStream
[]
streams
,
boolean
[]
streamResetFlags
,
long
positionUs
,
int
[]
streamIndexToTrackGroupIndex
)
{
// Create newly selected primary and event streams.
for
(
int
i
=
0
;
i
<
selections
.
length
;
i
++)
{
if
(
streams
[
i
]
==
null
&&
selections
[
i
]
!=
null
)
{
streamResetFlags
[
i
]
=
true
;
int
trackGroupIndex
=
streamIndexToTrackGroupIndex
[
i
];
TrackGroupInfo
trackGroupInfo
=
trackGroupInfos
[
trackGroupIndex
];
if
(
trackGroupInfo
.
trackGroupCategory
==
TrackGroupInfo
.
CATEGORY_PRIMARY
)
{
streams
[
i
]
=
buildSampleStream
(
trackGroupInfo
,
selections
[
i
],
positionUs
);
}
else
if
(
trackGroupInfo
.
trackGroupCategory
==
TrackGroupInfo
.
CATEGORY_MANIFEST_EVENTS
)
{
EventStream
eventStream
=
eventStreams
.
get
(
trackGroupInfo
.
eventStreamGroupIndex
);
Format
format
=
selections
[
i
].
getTrackGroup
().
getFormat
(
0
);
streams
[
i
]
=
new
EventSampleStream
(
eventStream
,
format
,
manifest
.
dynamic
);
}
}
}
// Create newly selected embedded streams from the corresponding primary stream. Note that this
// second pass is needed because the primary stream may not have been created yet in a first
// pass if the index of the primary stream is greater than the index of the embedded stream.
for
(
int
i
=
0
;
i
<
selections
.
length
;
i
++)
{
if
(
streams
[
i
]
==
null
&&
selections
[
i
]
!=
null
)
{
int
trackGroupIndex
=
streamIndexToTrackGroupIndex
[
i
];
TrackGroupInfo
trackGroupInfo
=
trackGroupInfos
[
trackGroupIndex
];
if
(
trackGroupInfo
.
trackGroupCategory
==
TrackGroupInfo
.
CATEGORY_EMBEDDED
)
{
int
primaryStreamIndex
=
getPrimaryStreamIndex
(
i
,
streamIndexToTrackGroupIndex
);
if
(
primaryStreamIndex
==
C
.
INDEX_UNSET
)
{
// If an embedded track is selected without the corresponding primary track, create an
// empty sample stream instead.
streams
[
i
]
=
new
EmptySampleStream
();
}
else
{
streams
[
i
]
=
((
ChunkSampleStream
)
streams
[
primaryStreamIndex
])
.
selectEmbeddedTrack
(
positionUs
,
trackGroupInfo
.
trackType
);
}
}
}
}
}
private
int
getPrimaryStreamIndex
(
int
embeddedStreamIndex
,
int
[]
streamIndexToTrackGroupIndex
)
{
int
embeddedTrackGroupIndex
=
streamIndexToTrackGroupIndex
[
embeddedStreamIndex
];
if
(
embeddedTrackGroupIndex
==
C
.
INDEX_UNSET
)
{
return
C
.
INDEX_UNSET
;
}
int
primaryTrackGroupIndex
=
trackGroupInfos
[
embeddedTrackGroupIndex
].
primaryTrackGroupIndex
;
for
(
int
i
=
0
;
i
<
streamIndexToTrackGroupIndex
.
length
;
i
++)
{
int
trackGroupIndex
=
streamIndexToTrackGroupIndex
[
i
];
if
(
trackGroupIndex
==
primaryTrackGroupIndex
&&
trackGroupInfos
[
trackGroupIndex
].
trackGroupCategory
==
TrackGroupInfo
.
CATEGORY_PRIMARY
)
{
return
i
;
}
}
return
C
.
INDEX_UNSET
;
}
private
static
Pair
<
TrackGroupArray
,
TrackGroupInfo
[]>
buildTrackGroups
(
List
<
AdaptationSet
>
adaptationSets
,
List
<
EventStream
>
eventStreams
)
{
int
[][]
groupedAdaptationSetIndices
=
getGroupedAdaptationSetIndices
(
adaptationSets
);
...
...
@@ -624,12 +659,6 @@ import java.util.List;
return
new
ChunkSampleStream
[
length
];
}
private
static
void
releaseIfEmbeddedSampleStream
(
SampleStream
sampleStream
)
{
if
(
sampleStream
instanceof
EmbeddedSampleStream
)
{
((
EmbeddedSampleStream
)
sampleStream
).
release
();
}
}
private
static
final
class
TrackGroupInfo
{
@Retention
(
RetentionPolicy
.
SOURCE
)
...
...
library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java
View file @
8cbcfd66
...
...
@@ -37,6 +37,7 @@ import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispat
import
com.google.android.exoplayer2.source.SequenceableLoader
;
import
com.google.android.exoplayer2.source.ads.AdsMediaSource
;
import
com.google.android.exoplayer2.source.dash.PlayerEmsgHandler.PlayerEmsgCallback
;
import
com.google.android.exoplayer2.source.dash.manifest.AdaptationSet
;
import
com.google.android.exoplayer2.source.dash.manifest.DashManifest
;
import
com.google.android.exoplayer2.source.dash.manifest.DashManifestParser
;
import
com.google.android.exoplayer2.source.dash.manifest.UtcTimingElement
;
...
...
@@ -974,8 +975,25 @@ public final class DashMediaSource extends BaseMediaSource {
long
availableEndTimeUs
=
Long
.
MAX_VALUE
;
boolean
isIndexExplicit
=
false
;
boolean
seenEmptyIndex
=
false
;
boolean
haveAudioVideoAdaptationSets
=
false
;
for
(
int
i
=
0
;
i
<
adaptationSetCount
;
i
++)
{
DashSegmentIndex
index
=
period
.
adaptationSets
.
get
(
i
).
representations
.
get
(
0
).
getIndex
();
int
type
=
period
.
adaptationSets
.
get
(
i
).
type
;
if
(
type
==
C
.
TRACK_TYPE_AUDIO
||
type
==
C
.
TRACK_TYPE_VIDEO
)
{
haveAudioVideoAdaptationSets
=
true
;
break
;
}
}
for
(
int
i
=
0
;
i
<
adaptationSetCount
;
i
++)
{
AdaptationSet
adaptationSet
=
period
.
adaptationSets
.
get
(
i
);
// Exclude text adaptation sets from duration calculations, if we have at least one audio
// or video adaptation set. See: https://github.com/google/ExoPlayer/issues/4029
if
(
haveAudioVideoAdaptationSets
&&
adaptationSet
.
type
==
C
.
TRACK_TYPE_TEXT
)
{
continue
;
}
DashSegmentIndex
index
=
adaptationSet
.
representations
.
get
(
0
).
getIndex
();
if
(
index
==
null
)
{
return
new
PeriodSeekInfo
(
true
,
0
,
durationUs
);
}
...
...
library/dash/src/main/java/com/google/android/exoplayer2/source/dash/EventSampleStream.java
View file @
8cbcfd66
...
...
@@ -36,37 +36,53 @@ import java.io.IOException;
private
final
EventMessageEncoder
eventMessageEncoder
;
private
long
[]
eventTimesUs
;
private
boolean
eventStream
Updat
able
;
private
boolean
eventStream
Append
able
;
private
EventStream
eventStream
;
private
boolean
isFormatSentDownstream
;
private
int
currentIndex
;
private
long
pendingSeekPositionUs
;
EventSampleStream
(
EventStream
eventStream
,
Format
upstreamFormat
,
boolean
eventStreamUpdatable
)
{
public
EventSampleStream
(
EventStream
eventStream
,
Format
upstreamFormat
,
boolean
eventStreamAppendable
)
{
this
.
upstreamFormat
=
upstreamFormat
;
this
.
eventStream
=
eventStream
;
eventMessageEncoder
=
new
EventMessageEncoder
();
pendingSeekPositionUs
=
C
.
TIME_UNSET
;
eventTimesUs
=
eventStream
.
presentationTimesUs
;
updateEventStream
(
eventStream
,
eventStream
Updat
able
);
updateEventStream
(
eventStream
,
eventStream
Append
able
);
}
void
updateEventStream
(
EventStream
eventStream
,
boolean
eventStreamUpdatable
)
{
public
String
eventStreamId
()
{
return
eventStream
.
id
();
}
public
void
updateEventStream
(
EventStream
eventStream
,
boolean
eventStreamAppendable
)
{
long
lastReadPositionUs
=
currentIndex
==
0
?
C
.
TIME_UNSET
:
eventTimesUs
[
currentIndex
-
1
];
this
.
eventStream
Updatable
=
eventStreamUpdat
able
;
this
.
eventStream
Appendable
=
eventStreamAppend
able
;
this
.
eventStream
=
eventStream
;
this
.
eventTimesUs
=
eventStream
.
presentationTimesUs
;
if
(
pendingSeekPositionUs
!=
C
.
TIME_UNSET
)
{
seekToUs
(
pendingSeekPositionUs
);
}
else
if
(
lastReadPositionUs
!=
C
.
TIME_UNSET
)
{
currentIndex
=
Util
.
binarySearchCeil
(
eventTimesUs
,
lastReadPositionUs
,
false
,
false
);
currentIndex
=
Util
.
binarySearchCeil
(
eventTimesUs
,
lastReadPositionUs
,
/* inclusive= */
false
,
/* stayInBounds= */
false
);
}
}
String
eventStreamId
()
{
return
eventStream
.
id
();
/**
* Seeks to the specified position in microseconds.
*
* @param positionUs The seek position in microseconds.
*/
public
void
seekToUs
(
long
positionUs
)
{
currentIndex
=
Util
.
binarySearchCeil
(
eventTimesUs
,
positionUs
,
/* inclusive= */
true
,
/* stayInBounds= */
false
);
boolean
isPendingSeek
=
eventStreamAppendable
&&
currentIndex
==
eventTimesUs
.
length
;
pendingSeekPositionUs
=
isPendingSeek
?
positionUs
:
C
.
TIME_UNSET
;
}
@Override
...
...
@@ -88,7 +104,7 @@ import java.io.IOException;
return
C
.
RESULT_FORMAT_READ
;
}
if
(
currentIndex
==
eventTimesUs
.
length
)
{
if
(!
eventStream
Updat
able
)
{
if
(!
eventStream
Append
able
)
{
buffer
.
setFlags
(
C
.
BUFFER_FLAG_END_OF_STREAM
);
return
C
.
RESULT_BUFFER_READ
;
}
else
{
...
...
@@ -118,15 +134,4 @@ import java.io.IOException;
return
skipped
;
}
/**
* Seeks to the specified position in microseconds.
*
* @param positionUs The seek position in microseconds.
*/
public
void
seekToUs
(
long
positionUs
)
{
currentIndex
=
Util
.
binarySearchCeil
(
eventTimesUs
,
positionUs
,
true
,
false
);
boolean
isPendingSeek
=
eventStreamUpdatable
&&
currentIndex
==
eventTimesUs
.
length
;
pendingSeekPositionUs
=
isPendingSeek
?
positionUs
:
C
.
TIME_UNSET
;
}
}
library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java
View file @
8cbcfd66
...
...
@@ -355,6 +355,7 @@ public class DashManifestParser extends DefaultHandler
protected
Pair
<
String
,
SchemeData
>
parseContentProtection
(
XmlPullParser
xpp
)
throws
XmlPullParserException
,
IOException
{
String
schemeType
=
null
;
String
licenseServerUrl
=
null
;
byte
[]
data
=
null
;
UUID
uuid
=
null
;
boolean
requiresSecureDecoder
=
false
;
...
...
@@ -364,7 +365,7 @@ public class DashManifestParser extends DefaultHandler
switch
(
Util
.
toLowerInvariant
(
schemeIdUri
))
{
case
"urn:mpeg:dash:mp4protection:2011"
:
schemeType
=
xpp
.
getAttributeValue
(
null
,
"value"
);
String
defaultKid
=
xpp
.
getAttributeValue
(
null
,
"cenc:
default_KID"
);
String
defaultKid
=
XmlPullParserUtil
.
getAttributeValueIgnorePrefix
(
xpp
,
"
default_KID"
);
if
(!
TextUtils
.
isEmpty
(
defaultKid
)
&&
!
"00000000-0000-0000-0000-000000000000"
.
equals
(
defaultKid
))
{
String
[]
defaultKidStrings
=
defaultKid
.
split
(
"\\s+"
);
...
...
@@ -389,11 +390,14 @@ public class DashManifestParser extends DefaultHandler
do
{
xpp
.
next
();
if
(
XmlPullParserUtil
.
isStartTag
(
xpp
,
"widevine:license"
))
{
if
(
XmlPullParserUtil
.
isStartTag
(
xpp
,
"ms:laurl"
))
{
licenseServerUrl
=
xpp
.
getAttributeValue
(
null
,
"licenseUrl"
);
}
else
if
(
XmlPullParserUtil
.
isStartTag
(
xpp
,
"widevine:license"
))
{
String
robustnessLevel
=
xpp
.
getAttributeValue
(
null
,
"robustness_level"
);
requiresSecureDecoder
=
robustnessLevel
!=
null
&&
robustnessLevel
.
startsWith
(
"HW"
);
}
else
if
(
data
==
null
)
{
if
(
XmlPullParserUtil
.
isStartTag
(
xpp
,
"cenc:pssh"
)
&&
xpp
.
next
()
==
XmlPullParser
.
TEXT
)
{
if
(
XmlPullParserUtil
.
isStartTagIgnorePrefix
(
xpp
,
"pssh"
)
&&
xpp
.
next
()
==
XmlPullParser
.
TEXT
)
{
// The cenc:pssh element is defined in 23001-7:2015.
data
=
Base64
.
decode
(
xpp
.
getText
(),
Base64
.
DEFAULT
);
uuid
=
PsshAtomUtil
.
parseUuid
(
data
);
...
...
@@ -409,8 +413,11 @@ public class DashManifestParser extends DefaultHandler
}
}
}
while
(!
XmlPullParserUtil
.
isEndTag
(
xpp
,
"ContentProtection"
));
SchemeData
schemeData
=
uuid
!=
null
?
new
SchemeData
(
uuid
,
MimeTypes
.
VIDEO_MP4
,
data
,
requiresSecureDecoder
)
:
null
;
SchemeData
schemeData
=
uuid
!=
null
?
new
SchemeData
(
uuid
,
licenseServerUrl
,
MimeTypes
.
VIDEO_MP4
,
data
,
requiresSecureDecoder
)
:
null
;
return
Pair
.
create
(
schemeType
,
schemeData
);
}
...
...
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java
View file @
8cbcfd66
...
...
@@ -401,7 +401,7 @@ public final class HlsMediaSource extends BaseMediaSource
@Override
public
void
releaseSourceInternal
()
{
if
(
playlistTracker
!=
null
)
{
playlistTracker
.
release
();
playlistTracker
.
stop
();
}
}
...
...
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java
View file @
8cbcfd66
...
...
@@ -136,6 +136,7 @@ import java.util.Arrays;
// Accessed only by the loading thread.
private
boolean
tracksEnded
;
private
long
sampleOffsetUs
;
private
int
chunkUid
;
/**
* @param trackType The type of the track. One of the {@link C} {@code TRACK_TYPE_*} constants.
...
...
@@ -650,6 +651,7 @@ import java.util.Arrays;
audioSampleQueueMappingDone
=
false
;
videoSampleQueueMappingDone
=
false
;
}
this
.
chunkUid
=
chunkUid
;
for
(
SampleQueue
sampleQueue
:
sampleQueues
)
{
sampleQueue
.
sourceId
(
chunkUid
);
}
...
...
@@ -704,6 +706,7 @@ import java.util.Arrays;
}
}
SampleQueue
trackOutput
=
new
SampleQueue
(
allocator
);
trackOutput
.
sourceId
(
chunkUid
);
trackOutput
.
setSampleOffsetUs
(
sampleOffsetUs
);
trackOutput
.
setUpstreamFormatChangeListener
(
this
);
sampleQueueTrackIds
=
Arrays
.
copyOf
(
sampleQueueTrackIds
,
trackCount
+
1
);
...
...
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java
View file @
8cbcfd66
...
...
@@ -105,7 +105,7 @@ public final class DefaultHlsPlaylistTracker
}
@Override
public
void
release
()
{
public
void
stop
()
{
primaryHlsUrl
=
null
;
primaryUrlSnapshot
=
null
;
masterPlaylist
=
null
;
...
...
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java
View file @
8cbcfd66
...
...
@@ -100,8 +100,8 @@ public interface HlsPlaylistTracker {
/**
* Starts the playlist tracker.
*
* <p>Must be called from the playback thread. A tracker may be restarted after a {@link
*
#release()}
call.
* <p>Must be called from the playback thread. A tracker may be restarted after a {@link
#stop()}
* call.
*
* @param initialPlaylistUri Uri of the HLS stream. Can point to a media playlist or a master
* playlist.
...
...
@@ -111,8 +111,12 @@ public interface HlsPlaylistTracker {
void
start
(
Uri
initialPlaylistUri
,
EventDispatcher
eventDispatcher
,
PrimaryPlaylistListener
listener
);
/** Releases all acquired resources. Must be called once per {@link #start} call. */
void
release
();
/**
* Stops the playlist tracker and releases any acquired resources.
*
* <p>Must be called once per {@link #start} call.
*/
void
stop
();
/**
* Registers a listener to receive events from the playlist tracker.
...
...
library/hls/src/test/java/com/google/android/exoplayer2/source/hls/Aes128DataSourceTest.java
View file @
8cbcfd66
...
...
@@ -22,6 +22,8 @@ import com.google.android.exoplayer2.C;
import
com.google.android.exoplayer2.upstream.DataSource
;
import
com.google.android.exoplayer2.upstream.DataSpec
;
import
java.io.IOException
;
import
org.junit.Ignore
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
import
org.robolectric.RobolectricTestRunner
;
...
...
@@ -30,6 +32,7 @@ import org.robolectric.RobolectricTestRunner;
@RunWith
(
RobolectricTestRunner
.
class
)
public
class
Aes128DataSourceTest
{
@Ignore
@Test
public
void
test_OpenCallsUpstreamOpen_CloseCallsUpstreamClose
()
throws
IOException
{
UpstreamDataSource
upstream
=
new
UpstreamDataSource
();
...
...
@@ -44,6 +47,7 @@ public class Aes128DataSourceTest {
assertThat
(
upstream
.
opened
).
isFalse
();
}
@Ignore
@Test
public
void
test_OpenCallsUpstreamThrowingOpen_CloseCallsUpstreamClose
()
throws
IOException
{
UpstreamDataSource
upstream
=
...
...
library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java
View file @
8cbcfd66
...
...
@@ -174,6 +174,12 @@ public class DefaultTimeBar extends View implements TimeBar {
private
static
final
long
STOP_SCRUBBING_TIMEOUT_MS
=
1000
;
private
static
final
int
DEFAULT_INCREMENT_COUNT
=
20
;
/**
* The name of the Android SDK view that most closely resembles this custom view. Used as the
* class name for accessibility.
*/
private
static
final
String
ACCESSIBILITY_CLASS_NAME
=
"android.widget.SeekBar"
;
private
final
Rect
seekBounds
;
private
final
Rect
progressBar
;
private
final
Rect
bufferedBar
;
...
...
@@ -184,7 +190,7 @@ public class DefaultTimeBar extends View implements TimeBar {
private
final
Paint
adMarkerPaint
;
private
final
Paint
playedAdMarkerPaint
;
private
final
Paint
scrubberPaint
;
private
final
Drawable
scrubberDrawable
;
private
final
@Nullable
Drawable
scrubberDrawable
;
private
final
int
barHeight
;
private
final
int
touchTargetHeight
;
private
final
int
adMarkerWidth
;
...
...
@@ -197,12 +203,12 @@ public class DefaultTimeBar extends View implements TimeBar {
private
final
Formatter
formatter
;
private
final
Runnable
stopScrubbingRunnable
;
private
final
CopyOnWriteArraySet
<
OnScrubListener
>
listeners
;
private
final
int
[]
locationOnScreen
;
private
final
Point
touchPosition
;
private
int
keyCountIncrement
;
private
long
keyTimeIncrement
;
private
int
lastCoarseScrubXPosition
;
private
int
[]
locationOnScreen
;
private
Point
touchPosition
;
private
boolean
scrubbing
;
private
long
scrubPosition
;
...
...
@@ -210,12 +216,10 @@ public class DefaultTimeBar extends View implements TimeBar {
private
long
position
;
private
long
bufferedPosition
;
private
int
adGroupCount
;
private
long
[]
adGroupTimesMs
;
private
boolean
[]
playedAdGroups
;
private
@Nullable
long
[]
adGroupTimesMs
;
private
@Nullable
boolean
[]
playedAdGroups
;
/**
* Creates a new time bar.
*/
/** Creates a new time bar. */
public
DefaultTimeBar
(
Context
context
,
AttributeSet
attrs
)
{
super
(
context
,
attrs
);
seekBounds
=
new
Rect
();
...
...
@@ -230,6 +234,8 @@ public class DefaultTimeBar extends View implements TimeBar {
scrubberPaint
=
new
Paint
();
scrubberPaint
.
setAntiAlias
(
true
);
listeners
=
new
CopyOnWriteArraySet
<>();
locationOnScreen
=
new
int
[
2
];
touchPosition
=
new
Point
();
// Calculate the dimensions and paints for drawn elements.
Resources
res
=
context
.
getResources
();
...
...
@@ -593,14 +599,14 @@ public class DefaultTimeBar extends View implements TimeBar {
if
(
event
.
getEventType
()
==
AccessibilityEvent
.
TYPE_VIEW_SELECTED
)
{
event
.
getText
().
add
(
getProgressText
());
}
event
.
setClassName
(
DefaultTimeBar
.
class
.
getName
()
);
event
.
setClassName
(
ACCESSIBILITY_CLASS_NAME
);
}
@TargetApi
(
21
)
@Override
public
void
onInitializeAccessibilityNodeInfo
(
AccessibilityNodeInfo
info
)
{
super
.
onInitializeAccessibilityNodeInfo
(
info
);
info
.
setClassName
(
DefaultTimeBar
.
class
.
getCanonicalName
()
);
info
.
setClassName
(
ACCESSIBILITY_CLASS_NAME
);
info
.
setContentDescription
(
getProgressText
());
if
(
duration
<=
0
)
{
return
;
...
...
@@ -616,7 +622,7 @@ public class DefaultTimeBar extends View implements TimeBar {
@TargetApi
(
16
)
@Override
public
boolean
performAccessibilityAction
(
int
action
,
Bundle
args
)
{
public
boolean
performAccessibilityAction
(
int
action
,
@Nullable
Bundle
args
)
{
if
(
super
.
performAccessibilityAction
(
action
,
args
))
{
return
true
;
}
...
...
@@ -693,10 +699,6 @@ public class DefaultTimeBar extends View implements TimeBar {
}
private
Point
resolveRelativeTouchPosition
(
MotionEvent
motionEvent
)
{
if
(
locationOnScreen
==
null
)
{
locationOnScreen
=
new
int
[
2
];
touchPosition
=
new
Point
();
}
getLocationOnScreen
(
locationOnScreen
);
touchPosition
.
set
(
((
int
)
motionEvent
.
getRawX
())
-
locationOnScreen
[
0
],
...
...
@@ -736,6 +738,11 @@ public class DefaultTimeBar extends View implements TimeBar {
if
(
scrubberBar
.
width
()
>
0
)
{
canvas
.
drawRect
(
scrubberBar
.
left
,
barTop
,
scrubberBar
.
right
,
barBottom
,
playedPaint
);
}
if
(
adGroupCount
==
0
)
{
return
;
}
long
[]
adGroupTimesMs
=
Assertions
.
checkNotNull
(
this
.
adGroupTimesMs
);
boolean
[]
playedAdGroups
=
Assertions
.
checkNotNull
(
this
.
playedAdGroups
);
int
adMarkerOffset
=
adMarkerWidth
/
2
;
for
(
int
i
=
0
;
i
<
adGroupCount
;
i
++)
{
long
adGroupTimeMs
=
Util
.
constrainValue
(
adGroupTimesMs
[
i
],
0
,
duration
);
...
...
library/ui/src/main/java/com/google/android/exoplayer2/ui/DownloadNotificationUtil.java
View file @
8cbcfd66
...
...
@@ -55,10 +55,18 @@ public final class DownloadNotificationUtil {
int
downloadTaskCount
=
0
;
boolean
allDownloadPercentagesUnknown
=
true
;
boolean
haveDownloadedBytes
=
false
;
boolean
haveDownloadTasks
=
false
;
boolean
haveRemoveTasks
=
false
;
for
(
TaskState
taskState
:
taskStates
)
{
if
(
taskState
.
action
.
isRemoveAction
||
taskState
.
state
!=
TaskState
.
STATE_STARTED
)
{
if
(
taskState
.
state
!=
TaskState
.
STATE_STARTED
&&
taskState
.
state
!=
TaskState
.
STATE_COMPLETED
)
{
continue
;
}
if
(
taskState
.
action
.
isRemoveAction
)
{
haveRemoveTasks
=
true
;
continue
;
}
haveDownloadTasks
=
true
;
if
(
taskState
.
downloadPercentage
!=
C
.
PERCENTAGE_UNSET
)
{
allDownloadPercentagesUnknown
=
false
;
totalPercentage
+=
taskState
.
downloadPercentage
;
...
...
@@ -67,18 +75,20 @@ public final class DownloadNotificationUtil {
downloadTaskCount
++;
}
boolean
haveDownloadTasks
=
downloadTaskCount
>
0
;
int
titleStringId
=
haveDownloadTasks
?
R
.
string
.
exo_download_downloading
:
(
taskStates
.
length
>
0
?
R
.
string
.
exo_download_removing
:
NULL_STRING_ID
);
:
(
haveRemoveTasks
?
R
.
string
.
exo_download_removing
:
NULL_STRING_ID
);
NotificationCompat
.
Builder
notificationBuilder
=
newNotificationBuilder
(
context
,
smallIcon
,
channelId
,
contentIntent
,
message
,
titleStringId
);
int
progress
=
haveDownloadTasks
?
(
int
)
(
totalPercentage
/
downloadTaskCount
)
:
0
;
boolean
indeterminate
=
!
haveDownloadTasks
||
(
allDownloadPercentagesUnknown
&&
haveDownloadedBytes
);
int
progress
=
0
;
boolean
indeterminate
=
true
;
if
(
haveDownloadTasks
)
{
progress
=
(
int
)
(
totalPercentage
/
downloadTaskCount
);
indeterminate
=
allDownloadPercentagesUnknown
&&
haveDownloadedBytes
;
}
notificationBuilder
.
setProgress
(
/* max= */
100
,
progress
,
indeterminate
);
notificationBuilder
.
setOngoing
(
true
);
notificationBuilder
.
setShowWhen
(
false
);
...
...
library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java
View file @
8cbcfd66
...
...
@@ -1088,7 +1088,7 @@ public class PlayerControlView extends FrameLayout {
@Override
public
void
onTimelineChanged
(
Timeline
timeline
,
Object
manifest
,
@Player
.
TimelineChangeReason
int
reason
)
{
Timeline
timeline
,
@Nullable
Object
manifest
,
@Player
.
TimelineChangeReason
int
reason
)
{
updateNavigation
();
updateTimeBarMode
();
updateProgress
();
...
...
library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java
View file @
8cbcfd66
...
...
@@ -949,7 +949,7 @@ public class PlayerNotificationManager {
}
@Override
public
void
onTimelineChanged
(
Timeline
timeline
,
Object
manifest
,
int
reason
)
{
public
void
onTimelineChanged
(
Timeline
timeline
,
@Nullable
Object
manifest
,
int
reason
)
{
if
(
player
==
null
||
player
.
getPlaybackState
()
==
Player
.
STATE_IDLE
)
{
return
;
}
...
...
library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java
View file @
8cbcfd66
...
...
@@ -696,6 +696,11 @@ public class PlayerView extends FrameLayout {
return
useController
&&
controller
.
dispatchMediaKeyEvent
(
event
);
}
/** Returns whether the controller is currently visible. */
public
boolean
isControllerVisible
()
{
return
controller
!=
null
&&
controller
.
isVisible
();
}
/**
* Shows the playback controls. Does nothing if playback controls are disabled.
*
...
...
library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java
View file @
8cbcfd66
...
...
@@ -28,6 +28,7 @@ import android.graphics.Rect;
import
android.graphics.RectF
;
import
android.text.Layout.Alignment
;
import
android.text.SpannableStringBuilder
;
import
android.text.Spanned
;
import
android.text.StaticLayout
;
import
android.text.TextPaint
;
import
android.text.TextUtils
;
...
...
@@ -89,7 +90,8 @@ import com.google.android.exoplayer2.util.Util;
private
int
edgeColor
;
@CaptionStyleCompat
.
EdgeType
private
int
edgeType
;
private
float
textSizePx
;
private
float
defaultTextSizePx
;
private
float
cueTextSizePx
;
private
float
bottomPaddingFraction
;
private
int
parentLeft
;
private
int
parentTop
;
...
...
@@ -130,8 +132,8 @@ import com.google.android.exoplayer2.util.Util;
/**
* Draws the provided {@link Cue} into a canvas with the specified styling.
*
<p>
* A call to this method is able to use cached results of calculations made during the previous
*
*
<p>
A call to this method is able to use cached results of calculations made during the previous
* call, and so an instance of this class is able to optimize repeated calls to this method in
* which the same parameters are passed.
*
...
...
@@ -140,7 +142,8 @@ import com.google.android.exoplayer2.util.Util;
* @param applyEmbeddedFontSizes If {@code applyEmbeddedStyles} is true, defines whether font
* sizes embedded within the cue should be applied. Otherwise, it is ignored.
* @param style The style to use when drawing the cue text.
* @param textSizePx The text size to use when drawing the cue text, in pixels.
* @param defaultTextSizePx The default text size to use when drawing the text, in pixels.
* @param cueTextSizePx The embedded text size of this cue, in pixels.
* @param bottomPaddingFraction The bottom padding fraction to apply when {@link Cue#line} is
* {@link Cue#DIMEN_UNSET}, as a fraction of the viewport height
* @param canvas The canvas into which to draw.
...
...
@@ -149,9 +152,19 @@ import com.google.android.exoplayer2.util.Util;
* @param cueBoxRight The right position of the enclosing cue box.
* @param cueBoxBottom The bottom position of the enclosing cue box.
*/
public
void
draw
(
Cue
cue
,
boolean
applyEmbeddedStyles
,
boolean
applyEmbeddedFontSizes
,
CaptionStyleCompat
style
,
float
textSizePx
,
float
bottomPaddingFraction
,
Canvas
canvas
,
int
cueBoxLeft
,
int
cueBoxTop
,
int
cueBoxRight
,
int
cueBoxBottom
)
{
public
void
draw
(
Cue
cue
,
boolean
applyEmbeddedStyles
,
boolean
applyEmbeddedFontSizes
,
CaptionStyleCompat
style
,
float
defaultTextSizePx
,
float
cueTextSizePx
,
float
bottomPaddingFraction
,
Canvas
canvas
,
int
cueBoxLeft
,
int
cueBoxTop
,
int
cueBoxRight
,
int
cueBoxBottom
)
{
boolean
isTextCue
=
cue
.
bitmap
==
null
;
int
windowColor
=
Color
.
BLACK
;
if
(
isTextCue
)
{
...
...
@@ -180,7 +193,8 @@ import com.google.android.exoplayer2.util.Util;
&&
this
.
edgeType
==
style
.
edgeType
&&
this
.
edgeColor
==
style
.
edgeColor
&&
Util
.
areEqual
(
this
.
textPaint
.
getTypeface
(),
style
.
typeface
)
&&
this
.
textSizePx
==
textSizePx
&&
this
.
defaultTextSizePx
==
defaultTextSizePx
&&
this
.
cueTextSizePx
==
cueTextSizePx
&&
this
.
bottomPaddingFraction
==
bottomPaddingFraction
&&
this
.
parentLeft
==
cueBoxLeft
&&
this
.
parentTop
==
cueBoxTop
...
...
@@ -209,7 +223,8 @@ import com.google.android.exoplayer2.util.Util;
this
.
edgeType
=
style
.
edgeType
;
this
.
edgeColor
=
style
.
edgeColor
;
this
.
textPaint
.
setTypeface
(
style
.
typeface
);
this
.
textSizePx
=
textSizePx
;
this
.
defaultTextSizePx
=
defaultTextSizePx
;
this
.
cueTextSizePx
=
cueTextSizePx
;
this
.
bottomPaddingFraction
=
bottomPaddingFraction
;
this
.
parentLeft
=
cueBoxLeft
;
this
.
parentTop
=
cueBoxTop
;
...
...
@@ -228,8 +243,8 @@ import com.google.android.exoplayer2.util.Util;
int
parentWidth
=
parentRight
-
parentLeft
;
int
parentHeight
=
parentBottom
-
parentTop
;
textPaint
.
setTextSize
(
t
extSizePx
);
int
textPaddingX
=
(
int
)
(
t
extSizePx
*
INNER_PADDING_RATIO
+
0.5f
);
textPaint
.
setTextSize
(
defaultT
extSizePx
);
int
textPaddingX
=
(
int
)
(
defaultT
extSizePx
*
INNER_PADDING_RATIO
+
0.5f
);
int
availableWidth
=
parentWidth
-
textPaddingX
*
2
;
if
(
cueSize
!=
Cue
.
DIMEN_UNSET
)
{
...
...
@@ -240,14 +255,12 @@ import com.google.android.exoplayer2.util.Util;
return
;
}
CharSequence
cueText
=
this
.
cueText
;
// Remove embedded styling or font size if requested.
CharSequence
cueText
;
if
(
applyEmbeddedFontSizes
&&
applyEmbeddedStyles
)
{
cueText
=
this
.
cueText
;
}
else
if
(!
applyEmbeddedStyles
)
{
cueText
=
this
.
cueText
.
toString
();
// Equivalent to erasing all spans.
}
else
{
SpannableStringBuilder
newCueText
=
new
SpannableStringBuilder
(
this
.
cueText
);
if
(!
applyEmbeddedStyles
)
{
cueText
=
cueText
.
toString
();
// Equivalent to erasing all spans.
}
else
if
(!
applyEmbeddedFontSizes
)
{
SpannableStringBuilder
newCueText
=
new
SpannableStringBuilder
(
cueText
);
int
cueLength
=
newCueText
.
length
();
AbsoluteSizeSpan
[]
absSpans
=
newCueText
.
getSpans
(
0
,
cueLength
,
AbsoluteSizeSpan
.
class
);
RelativeSizeSpan
[]
relSpans
=
newCueText
.
getSpans
(
0
,
cueLength
,
RelativeSizeSpan
.
class
);
...
...
@@ -258,6 +271,19 @@ import com.google.android.exoplayer2.util.Util;
newCueText
.
removeSpan
(
relSpan
);
}
cueText
=
newCueText
;
}
else
{
// Apply embedded styles & font size.
if
(
cueTextSizePx
>
0
)
{
// Use a SpannableStringBuilder encompassing the whole cue text to apply the default
// cueTextSizePx.
SpannableStringBuilder
newCueText
=
new
SpannableStringBuilder
(
cueText
);
newCueText
.
setSpan
(
new
AbsoluteSizeSpan
((
int
)
cueTextSizePx
),
/* start= */
0
,
/* end= */
newCueText
.
length
(),
Spanned
.
SPAN_PRIORITY
);
cueText
=
newCueText
;
}
}
Alignment
textAlignment
=
cueTextAlignment
==
null
?
Alignment
.
ALIGN_CENTER
:
cueTextAlignment
;
...
...
library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java
View file @
8cbcfd66
...
...
@@ -269,15 +269,15 @@ public final class SubtitleView extends View implements TextOutput {
for
(
int
i
=
0
;
i
<
cueCount
;
i
++)
{
Cue
cue
=
cues
.
get
(
i
);
float
textSizePx
=
resolveTextSizeForCue
(
cue
,
rawViewHeight
,
viewHeightMinusPadding
,
defaultViewTextSizePx
);
float
cueTextSizePx
=
resolveCueTextSize
(
cue
,
rawViewHeight
,
viewHeightMinusPadding
);
SubtitlePainter
painter
=
painters
.
get
(
i
);
painter
.
draw
(
cue
,
applyEmbeddedStyles
,
applyEmbeddedFontSizes
,
style
,
textSizePx
,
defaultViewTextSizePx
,
cueTextSizePx
,
bottomPaddingFraction
,
canvas
,
left
,
...
...
@@ -287,14 +287,13 @@ public final class SubtitleView extends View implements TextOutput {
}
}
private
float
resolveTextSizeForCue
(
Cue
cue
,
int
rawViewHeight
,
int
viewHeightMinusPadding
,
float
defaultViewTextSizePx
)
{
private
float
resolveCueTextSize
(
Cue
cue
,
int
rawViewHeight
,
int
viewHeightMinusPadding
)
{
if
(
cue
.
textSizeType
==
Cue
.
TYPE_UNSET
||
cue
.
textSize
==
Cue
.
DIMEN_UNSET
)
{
return
defaultViewTextSizePx
;
return
0
;
}
float
defaultCueTextSizePx
=
resolveTextSize
(
cue
.
textSizeType
,
cue
.
textSize
,
rawViewHeight
,
viewHeightMinusPadding
);
return
defaultCueTextSizePx
>
0
?
defaultCueTextSizePx
:
defaultViewTextSizePx
;
return
Math
.
max
(
defaultCueTextSizePx
,
0
)
;
}
private
float
resolveTextSize
(
...
...
library/ui/src/main/res/drawable-ldpi/exo_controls_fullscreen_enter.png
View file @
8cbcfd66
107 Bytes
|
W:
|
H:
139 Bytes
|
W:
|
H:
2-up
Swipe
Onion skin
library/ui/src/main/res/drawable-ldpi/exo_controls_fullscreen_exit.png
View file @
8cbcfd66
105 Bytes
|
W:
|
H:
146 Bytes
|
W:
|
H:
2-up
Swipe
Onion skin
testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java
View file @
8cbcfd66
...
...
@@ -575,7 +575,9 @@ public abstract class Action {
new
Player
.
DefaultEventListener
()
{
@Override
public
void
onTimelineChanged
(
Timeline
timeline
,
Object
manifest
,
@Player
.
TimelineChangeReason
int
reason
)
{
Timeline
timeline
,
@Nullable
Object
manifest
,
@Player
.
TimelineChangeReason
int
reason
)
{
if
(
expectedTimeline
==
null
||
timeline
.
equals
(
expectedTimeline
))
{
player
.
removeListener
(
this
);
nextAction
.
schedule
(
player
,
trackSelector
,
surface
,
handler
);
...
...
testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java
View file @
8cbcfd66
...
...
@@ -601,8 +601,8 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener
// Player.EventListener
@Override
public
void
onTimelineChanged
(
Timeline
timeline
,
Object
manifest
,
@Player
.
TimelineChangeReason
int
reason
)
{
public
void
onTimelineChanged
(
Timeline
timeline
,
@Nullable
Object
manifest
,
@Player
.
TimelineChangeReason
int
reason
)
{
timelines
.
add
(
timeline
);
manifests
.
add
(
manifest
);
timelineChangeReasons
.
add
(
reason
);
...
...
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