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
7e7a33a8
authored
Sep 07, 2020
by
Oliver Woodman
Browse files
Options
_('Browse Files')
Download
Plain Diff
Merge commit '
99dbb764
' into dev-v2-r2.12.0
parents
9778898b
99dbb764
Hide whitespace changes
Inline
Side-by-side
Showing
38 changed files
with
787 additions
and
404 deletions
RELEASENOTES.md
demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java
demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java
extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java
extensions/jobdispatcher/README.md
extensions/media2/src/androidTest/java/com/google/android/exoplayer2/ext/media2/SessionPlayerConnectorTest.java
extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerCommandQueue.java
extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerHandler.java
extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerWrapper.java
extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionPlayerConnector.java
library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java
library/common/src/main/java/com/google/android/exoplayer2/util/Util.java
library/common/src/test/java/com/google/android/exoplayer2/util/MimeTypesTest.java
library/core/src/main/java/com/google/android/exoplayer2/Player.java
library/core/src/main/java/com/google/android/exoplayer2/Timeline.java
library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSessionEventListener.java
library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java
library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java
library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadRequest.java
library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java
library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceEventListener.java
library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java
library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java
library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java
library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuerTest.java
library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java
library/dash/README.md
library/hls/README.md
library/smoothstreaming/README.md
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/StyledPlayerControlView.java
library/ui/src/main/res/layout/exo_styled_settings_list_item.xml
library/ui/src/main/res/layout/exo_styled_sub_settings_list_item.xml
playbacktests/build.gradle
playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashWidevineOfflineTest.java
playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DebugRenderersFactory.java
testutils/src/main/java/com/google/android/exoplayer2/testutil/TestExoPlayer.java
RELEASENOTES.md
View file @
7e7a33a8
...
@@ -137,7 +137,7 @@
...
@@ -137,7 +137,7 @@
*
Recreate the decoder when handling and swallowing decode errors in
*
Recreate the decoder when handling and swallowing decode errors in
`TextRenderer`
. This fixes a case where playback would never end when
`TextRenderer`
. This fixes a case where playback would never end when
playing content with malformed subtitles
playing content with malformed subtitles
(
[
#7590
](
https://github.com/google/ExoPlayer/issues/790
)
).
(
[
#7590
](
https://github.com/google/ExoPlayer/issues/7
5
90
)
).
*
Only apply
`CaptionManager`
font scaling in
*
Only apply
`CaptionManager`
font scaling in
`SubtitleView.setUserDefaultTextSize`
if the
`CaptionManager`
is
`SubtitleView.setUserDefaultTextSize`
if the
`CaptionManager`
is
enabled.
enabled.
...
@@ -315,6 +315,8 @@
...
@@ -315,6 +315,8 @@
*
Add
`ImaAdsLoader.Builder.setCompanionAdSlots`
so it's possible to set
*
Add
`ImaAdsLoader.Builder.setCompanionAdSlots`
so it's possible to set
companion ad slots without accessing the
`AdDisplayContainer`
.
companion ad slots without accessing the
`AdDisplayContainer`
.
*
Add missing notification of
`VideoAdPlayerCallback.onLoaded`
.
*
Add missing notification of
`VideoAdPlayerCallback.onLoaded`
.
*
Fix handling of incompatible VPAID ads
(
[
#7832
](
https://github.com/google/ExoPlayer/issues/7832
)
).
*
Demo app:
*
Demo app:
*
Replace the
`extensions`
variant with
`decoderExtensions`
and update the
*
Replace the
`extensions`
variant with
`decoderExtensions`
and update the
demo app use the Cronet and IMA extensions by default.
demo app use the Cronet and IMA extensions by default.
...
...
demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java
View file @
7e7a33a8
...
@@ -16,10 +16,12 @@
...
@@ -16,10 +16,12 @@
package
com
.
google
.
android
.
exoplayer2
.
demo
;
package
com
.
google
.
android
.
exoplayer2
.
demo
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkNotNull
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkNotNull
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkStateNotNull
;
import
android.content.Context
;
import
android.content.Context
;
import
android.content.DialogInterface
;
import
android.content.DialogInterface
;
import
android.net.Uri
;
import
android.net.Uri
;
import
android.os.AsyncTask
;
import
android.widget.Toast
;
import
android.widget.Toast
;
import
androidx.annotation.NonNull
;
import
androidx.annotation.NonNull
;
import
androidx.annotation.Nullable
;
import
androidx.annotation.Nullable
;
...
@@ -170,6 +172,7 @@ public class DownloadTracker {
...
@@ -170,6 +172,7 @@ public class DownloadTracker {
private
TrackSelectionDialog
trackSelectionDialog
;
private
TrackSelectionDialog
trackSelectionDialog
;
private
MappedTrackInfo
mappedTrackInfo
;
private
MappedTrackInfo
mappedTrackInfo
;
private
WidevineOfflineLicenseFetchTask
widevineOfflineLicenseFetchTask
;
@Nullable
private
byte
[]
keySetId
;
@Nullable
private
byte
[]
keySetId
;
public
StartDownloadDialogHelper
(
public
StartDownloadDialogHelper
(
...
@@ -185,6 +188,9 @@ public class DownloadTracker {
...
@@ -185,6 +188,9 @@ public class DownloadTracker {
if
(
trackSelectionDialog
!=
null
)
{
if
(
trackSelectionDialog
!=
null
)
{
trackSelectionDialog
.
dismiss
();
trackSelectionDialog
.
dismiss
();
}
}
if
(
widevineOfflineLicenseFetchTask
!=
null
)
{
widevineOfflineLicenseFetchTask
.
cancel
(
false
);
}
}
}
// DownloadHelper.Callback implementation.
// DownloadHelper.Callback implementation.
...
@@ -192,59 +198,32 @@ public class DownloadTracker {
...
@@ -192,59 +198,32 @@ public class DownloadTracker {
@Override
@Override
public
void
onPrepared
(
@NonNull
DownloadHelper
helper
)
{
public
void
onPrepared
(
@NonNull
DownloadHelper
helper
)
{
@Nullable
Format
format
=
getFirstFormatWithDrmInitData
(
helper
);
@Nullable
Format
format
=
getFirstFormatWithDrmInitData
(
helper
);
if
(
format
!=
null
)
{
if
(
format
==
null
)
{
if
(
Util
.
SDK_INT
<
18
)
{
onDownloadPrepared
(
helper
);
Toast
.
makeText
(
context
,
R
.
string
.
error_drm_unsupported_before_api_18
,
Toast
.
LENGTH_LONG
)
return
;
.
show
();
Log
.
e
(
TAG
,
"Downloading DRM protected content is not supported on API versions below 18"
);
return
;
}
// TODO(internal b/163107948): Support cases where DrmInitData are not in the manifest.
if
(!
hasSchemaData
(
format
.
drmInitData
))
{
Toast
.
makeText
(
context
,
R
.
string
.
download_start_error_offline_license
,
Toast
.
LENGTH_LONG
)
.
show
();
Log
.
e
(
TAG
,
"Downloading content where DRM scheme data is not located in the manifest is not"
+
" supported"
);
return
;
}
try
{
// TODO(internal b/163107948): Download the license on another thread to keep the UI
// thread unblocked.
fetchOfflineLicense
(
format
);
}
catch
(
DrmSession
.
DrmSessionException
e
)
{
Toast
.
makeText
(
context
,
R
.
string
.
download_start_error_offline_license
,
Toast
.
LENGTH_LONG
)
.
show
();
Log
.
e
(
TAG
,
"Failed to fetch offline DRM license"
,
e
);
return
;
}
}
}
if
(
helper
.
getPeriodCount
()
==
0
)
{
// The content is DRM protected. We need to acquire an offline license.
Log
.
d
(
TAG
,
"No periods found. Downloading entire stream."
);
if
(
Util
.
SDK_INT
<
18
)
{
startDownload
();
Toast
.
makeText
(
context
,
R
.
string
.
error_drm_unsupported_before_api_18
,
Toast
.
LENGTH_LONG
)
downloadHelper
.
release
();
.
show
();
Log
.
e
(
TAG
,
"Downloading DRM protected content is not supported on API versions below 18"
);
return
;
return
;
}
}
// TODO(internal b/163107948): Support cases where DrmInitData are not in the manifest.
mappedTrackInfo
=
downloadHelper
.
getMappedTrackInfo
(
/* periodIndex= */
0
);
if
(!
hasSchemaData
(
format
.
drmInitData
))
{
if
(!
TrackSelectionDialog
.
willHaveContent
(
mappedTrackInfo
))
{
Toast
.
makeText
(
context
,
R
.
string
.
download_start_error_offline_license
,
Toast
.
LENGTH_LONG
)
Log
.
d
(
TAG
,
"No dialog content. Downloading entire stream."
);
.
show
();
startDownload
();
Log
.
e
(
downloadHelper
.
release
();
TAG
,
"Downloading content where DRM scheme data is not located in the manifest is not"
+
" supported"
);
return
;
return
;
}
}
trackSelectionDialog
=
widevineOfflineLicenseFetchTask
=
TrackSelectionDialog
.
createForMappedTrackInfoAndParameters
(
new
WidevineOfflineLicenseFetchTask
(
/* titleId= */
R
.
string
.
exo_download_description
,
format
,
mediaItem
.
playbackProperties
.
drmConfiguration
.
licenseUri
,
this
,
helper
);
mappedTrackInfo
,
widevineOfflineLicenseFetchTask
.
execute
();
trackSelectorParameters
,
/* allowAdaptiveSelections =*/
false
,
/* allowMultipleOverrides= */
true
,
/* onClickListener= */
this
,
/* onDismissListener= */
this
);
trackSelectionDialog
.
show
(
fragmentManager
,
/* tag= */
null
);
}
}
@Override
@Override
...
@@ -292,6 +271,44 @@ public class DownloadTracker {
...
@@ -292,6 +271,44 @@ public class DownloadTracker {
// Internal methods.
// Internal methods.
private
void
onOfflineLicenseFetched
(
DownloadHelper
helper
,
byte
[]
keySetId
)
{
this
.
keySetId
=
keySetId
;
onDownloadPrepared
(
helper
);
}
private
void
onOfflineLicenseFetchedError
(
DrmSession
.
DrmSessionException
e
)
{
Toast
.
makeText
(
context
,
R
.
string
.
download_start_error_offline_license
,
Toast
.
LENGTH_LONG
)
.
show
();
Log
.
e
(
TAG
,
"Failed to fetch offline DRM license"
,
e
);
}
private
void
onDownloadPrepared
(
DownloadHelper
helper
)
{
if
(
helper
.
getPeriodCount
()
==
0
)
{
Log
.
d
(
TAG
,
"No periods found. Downloading entire stream."
);
startDownload
();
downloadHelper
.
release
();
return
;
}
mappedTrackInfo
=
downloadHelper
.
getMappedTrackInfo
(
/* periodIndex= */
0
);
if
(!
TrackSelectionDialog
.
willHaveContent
(
mappedTrackInfo
))
{
Log
.
d
(
TAG
,
"No dialog content. Downloading entire stream."
);
startDownload
();
downloadHelper
.
release
();
return
;
}
trackSelectionDialog
=
TrackSelectionDialog
.
createForMappedTrackInfoAndParameters
(
/* titleId= */
R
.
string
.
exo_download_description
,
mappedTrackInfo
,
trackSelectorParameters
,
/* allowAdaptiveSelections =*/
false
,
/* allowMultipleOverrides= */
true
,
/* onClickListener= */
this
,
/* onDismissListener= */
this
);
trackSelectionDialog
.
show
(
fragmentManager
,
/* tag= */
null
);
}
private
void
startDownload
()
{
private
void
startDownload
()
{
startDownload
(
buildDownloadRequest
());
startDownload
(
buildDownloadRequest
());
}
}
...
@@ -306,15 +323,54 @@ public class DownloadTracker {
...
@@ -306,15 +323,54 @@ public class DownloadTracker {
.
getDownloadRequest
(
Util
.
getUtf8Bytes
(
checkNotNull
(
mediaItem
.
mediaMetadata
.
title
)))
.
getDownloadRequest
(
Util
.
getUtf8Bytes
(
checkNotNull
(
mediaItem
.
mediaMetadata
.
title
)))
.
copyWithKeySetId
(
keySetId
);
.
copyWithKeySetId
(
keySetId
);
}
}
}
@RequiresApi
(
18
)
/** Downloads a Widevine offline license in a background thread. */
private
void
fetchOfflineLicense
(
Format
format
)
throws
DrmSession
.
DrmSessionException
{
@RequiresApi
(
18
)
private
final
class
WidevineOfflineLicenseFetchTask
extends
AsyncTask
<
Void
,
Void
,
Void
>
{
private
final
Format
format
;
private
final
Uri
licenseUri
;
private
final
StartDownloadDialogHelper
dialogHelper
;
private
final
DownloadHelper
downloadHelper
;
@Nullable
private
byte
[]
keySetId
;
@Nullable
private
DrmSession
.
DrmSessionException
drmSessionException
;
public
WidevineOfflineLicenseFetchTask
(
Format
format
,
Uri
licenseUri
,
StartDownloadDialogHelper
dialogHelper
,
DownloadHelper
downloadHelper
)
{
this
.
format
=
format
;
this
.
licenseUri
=
licenseUri
;
this
.
dialogHelper
=
dialogHelper
;
this
.
downloadHelper
=
downloadHelper
;
}
@Override
protected
Void
doInBackground
(
Void
...
voids
)
{
OfflineLicenseHelper
offlineLicenseHelper
=
OfflineLicenseHelper
offlineLicenseHelper
=
OfflineLicenseHelper
.
newWidevineInstance
(
OfflineLicenseHelper
.
newWidevineInstance
(
mediaItem
.
playbackProperties
.
drmConfiguration
.
licenseUri
.
toString
(),
licenseUri
.
toString
(),
httpDataSourceFactory
,
httpDataSourceFactory
,
new
DrmSessionEventListener
.
EventDispatcher
());
new
DrmSessionEventListener
.
EventDispatcher
());
keySetId
=
offlineLicenseHelper
.
downloadLicense
(
format
);
try
{
keySetId
=
offlineLicenseHelper
.
downloadLicense
(
format
);
}
catch
(
DrmSession
.
DrmSessionException
e
)
{
drmSessionException
=
e
;
}
finally
{
offlineLicenseHelper
.
release
();
}
return
null
;
}
@Override
protected
void
onPostExecute
(
Void
aVoid
)
{
if
(
drmSessionException
!=
null
)
{
dialogHelper
.
onOfflineLicenseFetchedError
(
drmSessionException
);
}
else
{
dialogHelper
.
onOfflineLicenseFetched
(
downloadHelper
,
checkStateNotNull
(
keySetId
));
}
}
}
}
}
...
...
demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java
View file @
7e7a33a8
...
@@ -271,13 +271,14 @@ public class PlayerActivity extends AppCompatActivity
...
@@ -271,13 +271,14 @@ public class PlayerActivity extends AppCompatActivity
setContentView
(
R
.
layout
.
player_activity
);
setContentView
(
R
.
layout
.
player_activity
);
}
}
protected
void
initializePlayer
()
{
/** @return Whether initialization was successful. */
protected
boolean
initializePlayer
()
{
if
(
player
==
null
)
{
if
(
player
==
null
)
{
Intent
intent
=
getIntent
();
Intent
intent
=
getIntent
();
mediaItems
=
createMediaItems
(
intent
);
mediaItems
=
createMediaItems
(
intent
);
if
(
mediaItems
.
isEmpty
())
{
if
(
mediaItems
.
isEmpty
())
{
return
;
return
false
;
}
}
boolean
preferExtensionDecoders
=
boolean
preferExtensionDecoders
=
...
@@ -297,9 +298,9 @@ public class PlayerActivity extends AppCompatActivity
...
@@ -297,9 +298,9 @@ public class PlayerActivity extends AppCompatActivity
.
setTrackSelector
(
trackSelector
)
.
setTrackSelector
(
trackSelector
)
.
build
();
.
build
();
player
.
addListener
(
new
PlayerEventListener
());
player
.
addListener
(
new
PlayerEventListener
());
player
.
addAnalyticsListener
(
new
EventLogger
(
trackSelector
));
player
.
setAudioAttributes
(
AudioAttributes
.
DEFAULT
,
/* handleAudioFocus= */
true
);
player
.
setAudioAttributes
(
AudioAttributes
.
DEFAULT
,
/* handleAudioFocus= */
true
);
player
.
setPlayWhenReady
(
startAutoPlay
);
player
.
setPlayWhenReady
(
startAutoPlay
);
player
.
addAnalyticsListener
(
new
EventLogger
(
trackSelector
));
playerView
.
setPlayer
(
player
);
playerView
.
setPlayer
(
player
);
playerView
.
setPlaybackPreparer
(
this
);
playerView
.
setPlaybackPreparer
(
this
);
debugViewHelper
=
new
DebugTextViewHelper
(
player
,
debugTextView
);
debugViewHelper
=
new
DebugTextViewHelper
(
player
,
debugTextView
);
...
@@ -312,6 +313,7 @@ public class PlayerActivity extends AppCompatActivity
...
@@ -312,6 +313,7 @@ public class PlayerActivity extends AppCompatActivity
player
.
setMediaItems
(
mediaItems
,
/* resetPosition= */
!
haveStartPosition
);
player
.
setMediaItems
(
mediaItems
,
/* resetPosition= */
!
haveStartPosition
);
player
.
prepare
();
player
.
prepare
();
updateButtonVisibility
();
updateButtonVisibility
();
return
true
;
}
}
private
List
<
MediaItem
>
createMediaItems
(
Intent
intent
)
{
private
List
<
MediaItem
>
createMediaItems
(
Intent
intent
)
{
...
@@ -548,17 +550,7 @@ public class PlayerActivity extends AppCompatActivity
...
@@ -548,17 +550,7 @@ public class PlayerActivity extends AppCompatActivity
@Nullable
@Nullable
DownloadRequest
downloadRequest
=
DownloadRequest
downloadRequest
=
downloadTracker
.
getDownloadRequest
(
checkNotNull
(
item
.
playbackProperties
).
uri
);
downloadTracker
.
getDownloadRequest
(
checkNotNull
(
item
.
playbackProperties
).
uri
);
if
(
downloadRequest
!=
null
)
{
mediaItems
.
add
(
downloadRequest
!=
null
?
downloadRequest
.
toMediaItem
()
:
item
);
MediaItem
mediaItem
=
item
.
buildUpon
()
.
setStreamKeys
(
downloadRequest
.
streamKeys
)
.
setCustomCacheKey
(
downloadRequest
.
customCacheKey
)
.
setDrmKeySetId
(
downloadRequest
.
keySetId
)
.
build
();
mediaItems
.
add
(
mediaItem
);
}
else
{
mediaItems
.
add
(
item
);
}
}
}
return
mediaItems
;
return
mediaItems
;
}
}
...
...
extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java
View file @
7e7a33a8
...
@@ -1096,6 +1096,11 @@ public final class ImaAdsLoader
...
@@ -1096,6 +1096,11 @@ public final class ImaAdsLoader
if
(
imaAdInfo
!=
null
)
{
if
(
imaAdInfo
!=
null
)
{
adPlaybackState
=
adPlaybackState
.
withSkippedAdGroup
(
imaAdInfo
.
adGroupIndex
);
adPlaybackState
=
adPlaybackState
.
withSkippedAdGroup
(
imaAdInfo
.
adGroupIndex
);
updateAdPlaybackState
();
updateAdPlaybackState
();
}
else
if
(
adPlaybackState
.
adGroupCount
==
1
&&
adPlaybackState
.
adGroupTimesUs
[
0
]
==
0
)
{
// For incompatible VPAID ads with one preroll, content is resumed immediately. In this case
// we haven't received ad info (the ad never loaded), but there is only one ad group to skip.
adPlaybackState
=
adPlaybackState
.
withSkippedAdGroup
(
/* adGroupIndex= */
0
);
updateAdPlaybackState
();
}
}
}
}
...
...
extensions/jobdispatcher/README.md
View file @
7e7a33a8
# ExoPlayer Firebase JobDispatcher extension #
# ExoPlayer Firebase JobDispatcher extension #
**
DEPRECATED - Please use
[
WorkManager extension
][]
or
[
PlatformScheduler
][]
**This extension is deprecated. Use the [WorkManager extension][] instead.**
instead.
**
This extension provides a Scheduler implementation which uses
[
Firebase JobDispatcher
][]
.
This extension provides a Scheduler implementation which uses
[
Firebase JobDispatcher
][]
.
[
WorkManager extension
]:
https://github.com/google/ExoPlayer/blob/release-v2/extensions/workmanager/README.md
[
WorkManager extension
]:
https://github.com/google/ExoPlayer/blob/release-v2/extensions/workmanager/README.md
[
PlatformScheduler
]:
https://github.com/google/ExoPlayer/blob/release-v2/library/core/src/main/java/com/google/android/exoplayer2/scheduler/PlatformScheduler.java
[
Firebase JobDispatcher
]:
https://github.com/firebase/firebase-jobdispatcher-android
[
Firebase JobDispatcher
]:
https://github.com/firebase/firebase-jobdispatcher-android
## Getting the extension ##
## Getting the extension ##
...
...
extensions/media2/src/androidTest/java/com/google/android/exoplayer2/ext/media2/SessionPlayerConnectorTest.java
View file @
7e7a33a8
...
@@ -185,17 +185,20 @@ public class SessionPlayerConnectorTest {
...
@@ -185,17 +185,20 @@ public class SessionPlayerConnectorTest {
}
}
};
};
SimpleExoPlayer
simpleExoPlayer
=
null
;
SimpleExoPlayer
simpleExoPlayer
=
null
;
SessionPlayerConnector
playerConnector
=
null
;
try
{
try
{
simpleExoPlayer
=
simpleExoPlayer
=
new
SimpleExoPlayer
.
Builder
(
context
)
new
SimpleExoPlayer
.
Builder
(
context
)
.
setLooper
(
Looper
.
myLooper
())
.
setLooper
(
Looper
.
myLooper
())
.
build
();
.
build
();
try
(
SessionPlayerConnector
player
=
playerConnector
=
new
SessionPlayerConnector
(
new
SessionPlayerConnector
(
simpleExoPlayer
,
new
DefaultMediaItemConverter
());
simpleExoPlayer
,
new
DefaultMediaItemConverter
(),
controlDispatcher
))
{
playerConnector
.
setControlDispatcher
(
controlDispatcher
);
assertPlayerResult
(
player
.
play
(),
RESULT_INFO_SKIPPED
);
assertPlayerResult
(
playerConnector
.
play
(),
RESULT_INFO_SKIPPED
);
}
}
finally
{
}
finally
{
if
(
playerConnector
!=
null
)
{
playerConnector
.
close
();
}
if
(
simpleExoPlayer
!=
null
)
{
if
(
simpleExoPlayer
!=
null
)
{
simpleExoPlayer
.
release
();
simpleExoPlayer
.
release
();
}
}
...
...
extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerCommandQueue.java
View file @
7e7a33a8
...
@@ -15,8 +15,10 @@
...
@@ -15,8 +15,10 @@
*/
*/
package
com
.
google
.
android
.
exoplayer2
.
ext
.
media2
;
package
com
.
google
.
android
.
exoplayer2
.
ext
.
media2
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Util
.
postOrRun
;
import
static
java
.
util
.
concurrent
.
TimeUnit
.
MILLISECONDS
;
import
static
java
.
util
.
concurrent
.
TimeUnit
.
MILLISECONDS
;
import
android.os.Handler
;
import
androidx.annotation.GuardedBy
;
import
androidx.annotation.GuardedBy
;
import
androidx.annotation.IntDef
;
import
androidx.annotation.IntDef
;
import
androidx.annotation.Nullable
;
import
androidx.annotation.Nullable
;
...
@@ -129,7 +131,7 @@ import java.util.concurrent.Callable;
...
@@ -129,7 +131,7 @@ import java.util.concurrent.Callable;
// Should be only used on the handler.
// Should be only used on the handler.
private
final
PlayerWrapper
player
;
private
final
PlayerWrapper
player
;
private
final
Player
Handler
handler
;
private
final
Handler
handler
;
private
final
Object
lock
;
private
final
Object
lock
;
@GuardedBy
(
"lock"
)
@GuardedBy
(
"lock"
)
...
@@ -141,7 +143,7 @@ import java.util.concurrent.Callable;
...
@@ -141,7 +143,7 @@ import java.util.concurrent.Callable;
// Should be only used on the handler.
// Should be only used on the handler.
@Nullable
private
AsyncPlayerCommandResult
pendingAsyncPlayerCommandResult
;
@Nullable
private
AsyncPlayerCommandResult
pendingAsyncPlayerCommandResult
;
public
PlayerCommandQueue
(
PlayerWrapper
player
,
Player
Handler
handler
)
{
public
PlayerCommandQueue
(
PlayerWrapper
player
,
Handler
handler
)
{
this
.
player
=
player
;
this
.
player
=
player
;
this
.
handler
=
handler
;
this
.
handler
=
handler
;
lock
=
new
Object
();
lock
=
new
Object
();
...
@@ -209,7 +211,7 @@ import java.util.concurrent.Callable;
...
@@ -209,7 +211,7 @@ import java.util.concurrent.Callable;
}
}
processPendingCommandOnHandler
();
processPendingCommandOnHandler
();
},
},
handler:
:
postOrRun
);
(
runnable
)
->
postOrRun
(
handler
,
runnable
)
);
if
(
DEBUG
)
{
if
(
DEBUG
)
{
Log
.
d
(
TAG
,
"adding "
+
playerCommand
);
Log
.
d
(
TAG
,
"adding "
+
playerCommand
);
}
}
...
@@ -220,7 +222,8 @@ import java.util.concurrent.Callable;
...
@@ -220,7 +222,8 @@ import java.util.concurrent.Callable;
}
}
public
void
notifyCommandError
()
{
public
void
notifyCommandError
()
{
handler
.
postOrRun
(
postOrRun
(
handler
,
()
->
{
()
->
{
@Nullable
AsyncPlayerCommandResult
pendingResult
=
pendingAsyncPlayerCommandResult
;
@Nullable
AsyncPlayerCommandResult
pendingResult
=
pendingAsyncPlayerCommandResult
;
if
(
pendingResult
==
null
)
{
if
(
pendingResult
==
null
)
{
...
@@ -243,7 +246,8 @@ import java.util.concurrent.Callable;
...
@@ -243,7 +246,8 @@ import java.util.concurrent.Callable;
if
(
DEBUG
)
{
if
(
DEBUG
)
{
Log
.
d
(
TAG
,
"notifyCommandCompleted, completedCommandCode="
+
completedCommandCode
);
Log
.
d
(
TAG
,
"notifyCommandCompleted, completedCommandCode="
+
completedCommandCode
);
}
}
handler
.
postOrRun
(
postOrRun
(
handler
,
()
->
{
()
->
{
@Nullable
AsyncPlayerCommandResult
pendingResult
=
pendingAsyncPlayerCommandResult
;
@Nullable
AsyncPlayerCommandResult
pendingResult
=
pendingAsyncPlayerCommandResult
;
if
(
pendingResult
==
null
||
pendingResult
.
commandCode
!=
completedCommandCode
)
{
if
(
pendingResult
==
null
||
pendingResult
.
commandCode
!=
completedCommandCode
)
{
...
@@ -267,7 +271,7 @@ import java.util.concurrent.Callable;
...
@@ -267,7 +271,7 @@ import java.util.concurrent.Callable;
}
}
private
void
processPendingCommand
()
{
private
void
processPendingCommand
()
{
handler
.
postOrRun
(
this
::
processPendingCommandOnHandler
);
postOrRun
(
handler
,
this
::
processPendingCommandOnHandler
);
}
}
private
void
processPendingCommandOnHandler
()
{
private
void
processPendingCommandOnHandler
()
{
...
...
extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerHandler.java
deleted
100644 → 0
View file @
9778898b
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
.
ext
.
media2
;
import
android.os.Handler
;
import
android.os.Looper
;
/** A {@link Handler} that provides {@link #postOrRun(Runnable)}. */
/* package */
final
class
PlayerHandler
extends
Handler
{
public
PlayerHandler
(
Looper
looper
)
{
super
(
looper
);
}
/**
* Posts the {@link Runnable} if the calling thread differs with the {@link Looper} of this
* handler. Otherwise, runs the runnable directly.
*
* @param r A runnable to either post or run.
* @return {@code true} if it's successfully run. {@code false} otherwise.
*/
public
boolean
postOrRun
(
Runnable
r
)
{
if
(
Thread
.
currentThread
()
!=
getLooper
().
getThread
())
{
return
post
(
r
);
}
r
.
run
();
return
true
;
}
}
extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerWrapper.java
View file @
7e7a33a8
...
@@ -15,6 +15,9 @@
...
@@ -15,6 +15,9 @@
*/
*/
package
com
.
google
.
android
.
exoplayer2
.
ext
.
media2
;
package
com
.
google
.
android
.
exoplayer2
.
ext
.
media2
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Util
.
postOrRun
;
import
android.os.Handler
;
import
androidx.annotation.IntRange
;
import
androidx.annotation.IntRange
;
import
androidx.annotation.Nullable
;
import
androidx.annotation.Nullable
;
import
androidx.core.util.ObjectsCompat
;
import
androidx.core.util.ObjectsCompat
;
...
@@ -24,6 +27,7 @@ import androidx.media2.common.MediaMetadata;
...
@@ -24,6 +27,7 @@ import androidx.media2.common.MediaMetadata;
import
androidx.media2.common.SessionPlayer
;
import
androidx.media2.common.SessionPlayer
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.ControlDispatcher
;
import
com.google.android.exoplayer2.ControlDispatcher
;
import
com.google.android.exoplayer2.DefaultControlDispatcher
;
import
com.google.android.exoplayer2.ExoPlaybackException
;
import
com.google.android.exoplayer2.ExoPlaybackException
;
import
com.google.android.exoplayer2.MediaItem
;
import
com.google.android.exoplayer2.MediaItem
;
import
com.google.android.exoplayer2.PlaybackParameters
;
import
com.google.android.exoplayer2.PlaybackParameters
;
...
@@ -100,12 +104,11 @@ import java.util.List;
...
@@ -100,12 +104,11 @@ import java.util.List;
private
static
final
int
POLL_BUFFER_INTERVAL_MS
=
1000
;
private
static
final
int
POLL_BUFFER_INTERVAL_MS
=
1000
;
private
final
Listener
listener
;
private
final
Listener
listener
;
private
final
Player
Handler
handler
;
private
final
Handler
handler
;
private
final
Runnable
pollBufferRunnable
;
private
final
Runnable
pollBufferRunnable
;
private
final
Player
player
;
private
final
Player
player
;
private
final
MediaItemConverter
mediaItemConverter
;
private
final
MediaItemConverter
mediaItemConverter
;
private
final
ControlDispatcher
controlDispatcher
;
private
final
ComponentListener
componentListener
;
private
final
ComponentListener
componentListener
;
@Nullable
private
MediaMetadata
playlistMetadata
;
@Nullable
private
MediaMetadata
playlistMetadata
;
...
@@ -114,6 +117,7 @@ import java.util.List;
...
@@ -114,6 +117,7 @@ import java.util.List;
private
final
List
<
androidx
.
media2
.
common
.
MediaItem
>
media2Playlist
;
private
final
List
<
androidx
.
media2
.
common
.
MediaItem
>
media2Playlist
;
private
final
List
<
MediaItem
>
exoPlayerPlaylist
;
private
final
List
<
MediaItem
>
exoPlayerPlaylist
;
private
ControlDispatcher
controlDispatcher
;
private
boolean
prepared
;
private
boolean
prepared
;
private
boolean
rebuffering
;
private
boolean
rebuffering
;
private
int
currentWindowIndex
;
private
int
currentWindowIndex
;
...
@@ -125,18 +129,13 @@ import java.util.List;
...
@@ -125,18 +129,13 @@ import java.util.List;
* @param listener A {@link Listener}.
* @param listener A {@link Listener}.
* @param player The {@link Player}.
* @param player The {@link Player}.
* @param mediaItemConverter The {@link MediaItemConverter}.
* @param mediaItemConverter The {@link MediaItemConverter}.
* @param controlDispatcher A {@link ControlDispatcher}.
*/
*/
public
PlayerWrapper
(
public
PlayerWrapper
(
Listener
listener
,
Player
player
,
MediaItemConverter
mediaItemConverter
)
{
Listener
listener
,
Player
player
,
MediaItemConverter
mediaItemConverter
,
ControlDispatcher
controlDispatcher
)
{
this
.
listener
=
listener
;
this
.
listener
=
listener
;
this
.
player
=
player
;
this
.
player
=
player
;
this
.
mediaItemConverter
=
mediaItemConverter
;
this
.
mediaItemConverter
=
mediaItemConverter
;
this
.
controlDispatcher
=
controlDispatcher
;
controlDispatcher
=
new
DefaultControlDispatcher
();
componentListener
=
new
ComponentListener
();
componentListener
=
new
ComponentListener
();
player
.
addListener
(
componentListener
);
player
.
addListener
(
componentListener
);
@Nullable
Player
.
AudioComponent
audioComponent
=
player
.
getAudioComponent
();
@Nullable
Player
.
AudioComponent
audioComponent
=
player
.
getAudioComponent
();
...
@@ -144,7 +143,7 @@ import java.util.List;
...
@@ -144,7 +143,7 @@ import java.util.List;
audioComponent
.
addAudioListener
(
componentListener
);
audioComponent
.
addAudioListener
(
componentListener
);
}
}
handler
=
new
Player
Handler
(
player
.
getApplicationLooper
());
handler
=
new
Handler
(
player
.
getApplicationLooper
());
pollBufferRunnable
=
new
PollBufferRunnable
();
pollBufferRunnable
=
new
PollBufferRunnable
();
media2Playlist
=
new
ArrayList
<>();
media2Playlist
=
new
ArrayList
<>();
...
@@ -157,6 +156,10 @@ import java.util.List;
...
@@ -157,6 +156,10 @@ import java.util.List;
updatePlaylist
(
player
.
getCurrentTimeline
());
updatePlaylist
(
player
.
getCurrentTimeline
());
}
}
public
void
setControlDispatcher
(
ControlDispatcher
controlDispatcher
)
{
this
.
controlDispatcher
=
controlDispatcher
;
}
public
boolean
setMediaItem
(
androidx
.
media2
.
common
.
MediaItem
media2MediaItem
)
{
public
boolean
setMediaItem
(
androidx
.
media2
.
common
.
MediaItem
media2MediaItem
)
{
return
setPlaylist
(
Collections
.
singletonList
(
media2MediaItem
),
/* metadata= */
null
);
return
setPlaylist
(
Collections
.
singletonList
(
media2MediaItem
),
/* metadata= */
null
);
}
}
...
@@ -436,7 +439,7 @@ import java.util.List;
...
@@ -436,7 +439,7 @@ import java.util.List;
private
void
handlePlayerStateChanged
(
@Player
.
State
int
state
)
{
private
void
handlePlayerStateChanged
(
@Player
.
State
int
state
)
{
if
(
state
==
Player
.
STATE_READY
||
state
==
Player
.
STATE_BUFFERING
)
{
if
(
state
==
Player
.
STATE_READY
||
state
==
Player
.
STATE_BUFFERING
)
{
handler
.
postOrRun
(
pollBufferRunnable
);
postOrRun
(
handler
,
pollBufferRunnable
);
}
else
{
}
else
{
handler
.
removeCallbacks
(
pollBufferRunnable
);
handler
.
removeCallbacks
(
pollBufferRunnable
);
}
}
...
...
extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionPlayerConnector.java
View file @
7e7a33a8
...
@@ -15,6 +15,9 @@
...
@@ -15,6 +15,9 @@
*/
*/
package
com
.
google
.
android
.
exoplayer2
.
ext
.
media2
;
package
com
.
google
.
android
.
exoplayer2
.
ext
.
media2
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Util
.
postOrRun
;
import
android.os.Handler
;
import
androidx.annotation.FloatRange
;
import
androidx.annotation.FloatRange
;
import
androidx.annotation.GuardedBy
;
import
androidx.annotation.GuardedBy
;
import
androidx.annotation.IntRange
;
import
androidx.annotation.IntRange
;
...
@@ -64,7 +67,7 @@ public final class SessionPlayerConnector extends SessionPlayer {
...
@@ -64,7 +67,7 @@ public final class SessionPlayerConnector extends SessionPlayer {
private
static
final
int
END_OF_PLAYLIST
=
-
1
;
private
static
final
int
END_OF_PLAYLIST
=
-
1
;
private
final
Object
stateLock
=
new
Object
();
private
final
Object
stateLock
=
new
Object
();
private
final
Player
Handler
taskHandler
;
private
final
Handler
taskHandler
;
private
final
Executor
taskHandlerExecutor
;
private
final
Executor
taskHandlerExecutor
;
private
final
PlayerWrapper
player
;
private
final
PlayerWrapper
player
;
private
final
PlayerCommandQueue
playerCommandQueue
;
private
final
PlayerCommandQueue
playerCommandQueue
;
...
@@ -89,7 +92,7 @@ public final class SessionPlayerConnector extends SessionPlayer {
...
@@ -89,7 +92,7 @@ public final class SessionPlayerConnector extends SessionPlayer {
* @param player The player to wrap.
* @param player The player to wrap.
*/
*/
public
SessionPlayerConnector
(
Player
player
)
{
public
SessionPlayerConnector
(
Player
player
)
{
this
(
player
,
new
DefaultMediaItemConverter
()
,
new
DefaultControlDispatcher
()
);
this
(
player
,
new
DefaultMediaItemConverter
());
}
}
/**
/**
...
@@ -97,22 +100,28 @@ public final class SessionPlayerConnector extends SessionPlayer {
...
@@ -97,22 +100,28 @@ public final class SessionPlayerConnector extends SessionPlayer {
*
*
* @param player The player to wrap.
* @param player The player to wrap.
* @param mediaItemConverter The {@link MediaItemConverter}.
* @param mediaItemConverter The {@link MediaItemConverter}.
* @param controlDispatcher The {@link ControlDispatcher}.
*/
*/
public
SessionPlayerConnector
(
public
SessionPlayerConnector
(
Player
player
,
MediaItemConverter
mediaItemConverter
)
{
Player
player
,
MediaItemConverter
mediaItemConverter
,
ControlDispatcher
controlDispatcher
)
{
Assertions
.
checkNotNull
(
player
);
Assertions
.
checkNotNull
(
player
);
Assertions
.
checkNotNull
(
mediaItemConverter
);
Assertions
.
checkNotNull
(
mediaItemConverter
);
Assertions
.
checkNotNull
(
controlDispatcher
);
state
=
PLAYER_STATE_IDLE
;
state
=
PLAYER_STATE_IDLE
;
taskHandler
=
new
Player
Handler
(
player
.
getApplicationLooper
());
taskHandler
=
new
Handler
(
player
.
getApplicationLooper
());
taskHandlerExecutor
=
taskHandler:
:
postOrRun
;
taskHandlerExecutor
=
(
runnable
)
->
postOrRun
(
taskHandler
,
runnable
)
;
ExoPlayerWrapperListener
playerListener
=
new
ExoPlayerWrapperListener
();
this
.
player
=
new
PlayerWrapper
(
playerListener
,
player
,
mediaItemConverter
,
controlDispatch
er
);
this
.
player
=
new
PlayerWrapper
(
new
ExoPlayerWrapperListener
(),
player
,
mediaItemConvert
er
);
playerCommandQueue
=
new
PlayerCommandQueue
(
this
.
player
,
taskHandler
);
playerCommandQueue
=
new
PlayerCommandQueue
(
this
.
player
,
taskHandler
);
}
}
/**
* Sets the {@link ControlDispatcher}.
*
* @param controlDispatcher The {@link ControlDispatcher}.
*/
public
void
setControlDispatcher
(
ControlDispatcher
controlDispatcher
)
{
player
.
setControlDispatcher
(
controlDispatcher
);
}
@Override
@Override
public
ListenableFuture
<
PlayerResult
>
play
()
{
public
ListenableFuture
<
PlayerResult
>
play
()
{
return
playerCommandQueue
.
addCommand
(
return
playerCommandQueue
.
addCommand
(
...
@@ -598,7 +607,8 @@ public final class SessionPlayerConnector extends SessionPlayer {
...
@@ -598,7 +607,8 @@ public final class SessionPlayerConnector extends SessionPlayer {
private
<
T
>
T
runPlayerCallableBlocking
(
Callable
<
T
>
callable
)
{
private
<
T
>
T
runPlayerCallableBlocking
(
Callable
<
T
>
callable
)
{
SettableFuture
<
T
>
future
=
SettableFuture
.
create
();
SettableFuture
<
T
>
future
=
SettableFuture
.
create
();
boolean
success
=
boolean
success
=
taskHandler
.
postOrRun
(
postOrRun
(
taskHandler
,
()
->
{
()
->
{
try
{
try
{
future
.
set
(
callable
.
call
());
future
.
set
(
callable
.
call
());
...
...
library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java
View file @
7e7a33a8
...
@@ -170,15 +170,16 @@ public final class MimeTypes {
...
@@ -170,15 +170,16 @@ public final class MimeTypes {
}
}
/**
/**
* Returns true if it is known that all samples in a stream of the given MIME type a
re guaranteed
* Returns true if it is known that all samples in a stream of the given MIME type a
nd codec are
*
to be sync samples (i.e., {@link C#BUFFER_FLAG_KEY_FRAME} is guaranteed to be set on every
*
guaranteed to be sync samples (i.e., {@link C#BUFFER_FLAG_KEY_FRAME} is guaranteed to be set on
* sample).
*
every
sample).
*
*
* @param mimeType
A MIME type
.
* @param mimeType
The MIME type of the stream
.
* @
return True if it is known that all samples in a stream of the given MIME type are guaranteed
* @
param codec The RFC 6381 codec string of the stream, or {@code null} if unknown.
*
to be sync samples. False otherwise, including if {@code null} is passed
.
*
@return Whether it is known that all samples in the stream are guaranteed to be sync samples
.
*/
*/
public
static
boolean
allSamplesAreSyncSamples
(
@Nullable
String
mimeType
)
{
public
static
boolean
allSamplesAreSyncSamples
(
@Nullable
String
mimeType
,
@Nullable
String
codec
)
{
if
(
mimeType
==
null
)
{
if
(
mimeType
==
null
)
{
return
false
;
return
false
;
}
}
...
@@ -198,6 +199,20 @@ public final class MimeTypes {
...
@@ -198,6 +199,20 @@ public final class MimeTypes {
case
AUDIO_E_AC3:
case
AUDIO_E_AC3:
case
AUDIO_E_AC3_JOC:
case
AUDIO_E_AC3_JOC:
return
true
;
return
true
;
case
AUDIO_AAC:
if
(
codec
==
null
)
{
return
false
;
}
@Nullable
Mp4aObjectType
objectType
=
getObjectTypeFromMp4aRFC6381CodecString
(
codec
);
if
(
objectType
==
null
)
{
return
false
;
}
@C
.
Encoding
int
encoding
=
AacUtil
.
getEncodingForAudioObjectType
(
objectType
.
audioObjectTypeIndication
);
// xHE-AAC is an exception in which it's not true that all samples will be sync samples.
// Also return false for ENCODING_INVALID, which indicates we weren't able to parse the
// encoding from the codec string.
return
encoding
!=
C
.
ENCODING_INVALID
&&
encoding
!=
C
.
ENCODING_AAC_XHE
;
default
:
default
:
return
false
;
return
false
;
}
}
...
...
library/common/src/main/java/com/google/android/exoplayer2/util/Util.java
View file @
7e7a33a8
...
@@ -494,6 +494,24 @@ public final class Util {
...
@@ -494,6 +494,24 @@ public final class Util {
}
}
/**
/**
* Posts the {@link Runnable} if the calling thread differs with the {@link Looper} of the {@link
* Handler}. Otherwise, runs the {@link Runnable} directly.
*
* @param handler The handler to which the {@link Runnable} will be posted.
* @param runnable The runnable to either post or run.
* @return {@code true} if the {@link Runnable} was successfully posted to the {@link Handler} or
* run. {@code false} otherwise.
*/
public
static
boolean
postOrRun
(
Handler
handler
,
Runnable
runnable
)
{
if
(
handler
.
getLooper
()
==
Looper
.
myLooper
())
{
runnable
.
run
();
return
true
;
}
else
{
return
handler
.
post
(
runnable
);
}
}
/**
* Returns the {@link Looper} associated with the current thread, or the {@link Looper} of the
* Returns the {@link Looper} associated with the current thread, or the {@link Looper} of the
* application's main thread if the current thread doesn't have a {@link Looper}.
* application's main thread if the current thread doesn't have a {@link Looper}.
*/
*/
...
...
library/common/src/test/java/com/google/android/exoplayer2/util/MimeTypesTest.java
View file @
7e7a33a8
...
@@ -15,6 +15,8 @@
...
@@ -15,6 +15,8 @@
*/
*/
package
com
.
google
.
android
.
exoplayer2
.
util
;
package
com
.
google
.
android
.
exoplayer2
.
util
;
import
static
android
.
media
.
MediaCodecInfo
.
CodecProfileLevel
.
AACObjectHE
;
import
static
android
.
media
.
MediaCodecInfo
.
CodecProfileLevel
.
AACObjectXHE
;
import
static
com
.
google
.
common
.
truth
.
Truth
.
assertThat
;
import
static
com
.
google
.
common
.
truth
.
Truth
.
assertThat
;
import
androidx.annotation.Nullable
;
import
androidx.annotation.Nullable
;
...
@@ -171,4 +173,17 @@ public final class MimeTypesTest {
...
@@ -171,4 +173,17 @@ public final class MimeTypesTest {
assertThat
(
objectType
.
objectTypeIndication
).
isEqualTo
(
expectedObjectTypeIndicator
);
assertThat
(
objectType
.
objectTypeIndication
).
isEqualTo
(
expectedObjectTypeIndicator
);
assertThat
(
objectType
.
audioObjectTypeIndication
).
isEqualTo
(
expectedAudioObjectTypeIndicator
);
assertThat
(
objectType
.
audioObjectTypeIndication
).
isEqualTo
(
expectedAudioObjectTypeIndicator
);
}
}
@Test
public
void
allSamplesAreSyncSamples_forAac_usesCodec
()
{
assertThat
(
MimeTypes
.
allSamplesAreSyncSamples
(
MimeTypes
.
AUDIO_AAC
,
"mp4a.40."
+
AACObjectHE
))
.
isTrue
();
assertThat
(
MimeTypes
.
allSamplesAreSyncSamples
(
MimeTypes
.
AUDIO_AAC
,
"mp4a.40."
+
AACObjectXHE
))
.
isFalse
();
assertThat
(
MimeTypes
.
allSamplesAreSyncSamples
(
MimeTypes
.
AUDIO_AAC
,
"mp4a.40"
)).
isFalse
();
assertThat
(
MimeTypes
.
allSamplesAreSyncSamples
(
MimeTypes
.
AUDIO_AAC
,
"mp4a.40."
)).
isFalse
();
assertThat
(
MimeTypes
.
allSamplesAreSyncSamples
(
MimeTypes
.
AUDIO_AAC
,
"invalid"
)).
isFalse
();
assertThat
(
MimeTypes
.
allSamplesAreSyncSamples
(
MimeTypes
.
AUDIO_AAC
,
/* codec= */
null
))
.
isFalse
();
}
}
}
library/core/src/main/java/com/google/android/exoplayer2/Player.java
View file @
7e7a33a8
...
@@ -374,8 +374,6 @@ public interface Player {
...
@@ -374,8 +374,6 @@ public interface Player {
}
}
/** The device component of a {@link Player}. */
/** The device component of a {@link Player}. */
// Note: It's mostly from the androidx.media.VolumeProviderCompat and
// androidx.media.MediaControllerCompat.PlaybackInfo.
interface
DeviceComponent
{
interface
DeviceComponent
{
/** Adds a listener to receive device events. */
/** Adds a listener to receive device events. */
...
...
library/core/src/main/java/com/google/android/exoplayer2/Timeline.java
View file @
7e7a33a8
...
@@ -48,62 +48,74 @@ import com.google.android.exoplayer2.util.Util;
...
@@ -48,62 +48,74 @@ import com.google.android.exoplayer2.util.Util;
* <h3 id="single-file">Single media file or on-demand stream</h3>
* <h3 id="single-file">Single media file or on-demand stream</h3>
*
*
* <p style="align:center"><img src="doc-files/timeline-single-file.svg" alt="Example timeline for a
* <p style="align:center"><img src="doc-files/timeline-single-file.svg" alt="Example timeline for a
* single file"> A timeline for a single media file or on-demand stream consists of a single period
* single file">
* and window. The window spans the whole period, indicating that all parts of the media are
*
* available for playback. The window's default position is typically at the start of the period
* <p>A timeline for a single media file or on-demand stream consists of a single period and window.
* (indicated by the black dot in the figure above).
* The window spans the whole period, indicating that all parts of the media are available for
* playback. The window's default position is typically at the start of the period (indicated by the
* black dot in the figure above).
*
*
* <h3>Playlist of media files or on-demand streams</h3>
* <h3>Playlist of media files or on-demand streams</h3>
*
*
* <p style="align:center"><img src="doc-files/timeline-playlist.svg" alt="Example timeline for a
* <p style="align:center"><img src="doc-files/timeline-playlist.svg" alt="Example timeline for a
* playlist of files"> A timeline for a playlist of media files or on-demand streams consists of
* playlist of files">
* multiple periods, each with its own window. Each window spans the whole of the corresponding
*
* period, and typically has a default position at the start of the period. The properties of the
* <p>A timeline for a playlist of media files or on-demand streams consists of multiple periods,
* periods and windows (e.g. their durations and whether the window is seekable) will often only
* each with its own window. Each window spans the whole of the corresponding period, and typically
* become known when the player starts buffering the corresponding file or stream.
* has a default position at the start of the period. The properties of the periods and windows
* (e.g. their durations and whether the window is seekable) will often only become known when the
* player starts buffering the corresponding file or stream.
*
*
* <h3 id="live-limited">Live stream with limited availability</h3>
* <h3 id="live-limited">Live stream with limited availability</h3>
*
*
* <p style="align:center"><img src="doc-files/timeline-live-limited.svg" alt="Example timeline for
* <p style="align:center"><img src="doc-files/timeline-live-limited.svg" alt="Example timeline for
* a live stream with limited availability"> A timeline for a live stream consists of a period whose
* a live stream with limited availability">
* duration is unknown, since it's continually extending as more content is broadcast. If content
*
* only remains available for a limited period of time then the window may start at a non-zero
* <p>A timeline for a live stream consists of a period whose duration is unknown, since it's
* position, defining the region of content that can still be played. The window will have {@link
* continually extending as more content is broadcast. If content only remains available for a
* Window#isLive} set to true to indicate it's a live stream and {@link Window#isDynamic} set to
* limited period of time then the window may start at a non-zero position, defining the region of
* true as long as we expect changes to the live window. Its default position is typically near to
* content that can still be played. The window will have {@link Window#isLive} set to true to
* the live edge (indicated by the black dot in the figure above).
* indicate it's a live stream and {@link Window#isDynamic} set to true as long as we expect changes
* to the live window. Its default position is typically near to the live edge (indicated by the
* black dot in the figure above).
*
*
* <h3>Live stream with indefinite availability</h3>
* <h3>Live stream with indefinite availability</h3>
*
*
* <p style="align:center"><img src="doc-files/timeline-live-indefinite.svg" alt="Example timeline
* <p style="align:center"><img src="doc-files/timeline-live-indefinite.svg" alt="Example timeline
* for a live stream with indefinite availability"> A timeline for a live stream with indefinite
* for a live stream with indefinite availability">
* availability is similar to the <a href="#live-limited">Live stream with limited availability</a>
*
* case, except that the window starts at the beginning of the period to indicate that all of the
* <p>A timeline for a live stream with indefinite availability is similar to the <a
* previously broadcast content can still be played.
* href="#live-limited">Live stream with limited availability</a> case, except that the window
* starts at the beginning of the period to indicate that all of the previously broadcast content
* can still be played.
*
*
* <h3 id="live-multi-period">Live stream with multiple periods</h3>
* <h3 id="live-multi-period">Live stream with multiple periods</h3>
*
*
* <p style="align:center"><img src="doc-files/timeline-live-multi-period.svg" alt="Example timeline
* <p style="align:center"><img src="doc-files/timeline-live-multi-period.svg" alt="Example timeline
* for a live stream with multiple periods"> This case arises when a live stream is explicitly
* for a live stream with multiple periods">
* divided into separate periods, for example at content boundaries. This case is similar to the <a
*
* href="#live-limited">Live stream with limited availability</a> case, except that the window may
* <p>This case arises when a live stream is explicitly divided into separate periods, for example
* span more than one period. Multiple periods are also possible in the indefinite availability
* at content boundaries. This case is similar to the <a href="#live-limited">Live stream with
* case.
* limited availability</a> case, except that the window may span more than one period. Multiple
* periods are also possible in the indefinite availability case.
*
*
* <h3>On-demand stream followed by live stream</h3>
* <h3>On-demand stream followed by live stream</h3>
*
*
* <p style="align:center"><img src="doc-files/timeline-advanced.svg" alt="Example timeline for an
* <p style="align:center"><img src="doc-files/timeline-advanced.svg" alt="Example timeline for an
* on-demand stream followed by a live stream"> This case is the concatenation of the <a
* on-demand stream followed by a live stream">
* href="#single-file">Single media file or on-demand stream</a> and <a href="#multi-period">Live
*
* stream with multiple periods</a> cases. When playback of the on-demand stream ends, playback of
* <p>This case is the concatenation of the <a href="#single-file">Single media file or on-demand
* the live stream will start from its default position near the live edge.
* stream</a> and <a href="#multi-period">Live stream with multiple periods</a> cases. When playback
* of the on-demand stream ends, playback of the live stream will start from its default position
* near the live edge.
*
*
* <h3 id="single-file-midrolls">On-demand stream with mid-roll ads</h3>
* <h3 id="single-file-midrolls">On-demand stream with mid-roll ads</h3>
*
*
* <p style="align:center"><img src="doc-files/timeline-single-file-midrolls.svg" alt="Example
* <p style="align:center"><img src="doc-files/timeline-single-file-midrolls.svg" alt="Example
* timeline for an on-demand stream with mid-roll ad groups"> This case includes mid-roll ad groups,
* timeline for an on-demand stream with mid-roll ad groups">
* which are defined as part of the timeline's single period. The period can be queried for
*
* information about the ad groups and the ads they contain.
* <p>This case includes mid-roll ad groups, which are defined as part of the timeline's single
* period. The period can be queried for information about the ad groups and the ads they contain.
*/
*/
public
abstract
class
Timeline
{
public
abstract
class
Timeline
{
...
...
library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSessionEventListener.java
View file @
7e7a33a8
...
@@ -15,8 +15,9 @@
...
@@ -15,8 +15,9 @@
*/
*/
package
com
.
google
.
android
.
exoplayer2
.
drm
;
package
com
.
google
.
android
.
exoplayer2
.
drm
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Util
.
postOrRun
;
import
android.os.Handler
;
import
android.os.Handler
;
import
android.os.Looper
;
import
androidx.annotation.CheckResult
;
import
androidx.annotation.CheckResult
;
import
androidx.annotation.Nullable
;
import
androidx.annotation.Nullable
;
import
com.google.android.exoplayer2.Player
;
import
com.google.android.exoplayer2.Player
;
...
@@ -207,15 +208,6 @@ public interface DrmSessionEventListener {
...
@@ -207,15 +208,6 @@ public interface DrmSessionEventListener {
}
}
}
}
/** Dispatches {@link #onDrmSessionAcquired(int, MediaPeriodId)}. */
private
static
void
postOrRun
(
Handler
handler
,
Runnable
runnable
)
{
if
(
handler
.
getLooper
()
==
Looper
.
myLooper
())
{
runnable
.
run
();
}
else
{
handler
.
post
(
runnable
);
}
}
private
static
final
class
ListenerAndHandler
{
private
static
final
class
ListenerAndHandler
{
public
Handler
handler
;
public
Handler
handler
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java
View file @
7e7a33a8
...
@@ -1442,6 +1442,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
...
@@ -1442,6 +1442,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
}
}
if
(
codec
==
null
)
{
if
(
codec
==
null
)
{
if
(!
legacyKeepAvailableCodecInfosWithoutCodec
())
{
availableCodecInfos
=
null
;
}
maybeInitCodecOrBypass
();
maybeInitCodecOrBypass
();
return
;
return
;
}
}
...
@@ -1507,6 +1510,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
...
@@ -1507,6 +1510,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
}
}
/**
/**
* Returns whether to keep available codec infos when the codec hasn't been initialized, which is
* the behavior before a bug fix. See also [Internal: b/162837741].
*/
protected
boolean
legacyKeepAvailableCodecInfosWithoutCodec
()
{
return
false
;
}
/**
* Called when one of the output formats changes.
* Called when one of the output formats changes.
*
*
* <p>The default implementation is a no-op.
* <p>The default implementation is a no-op.
...
...
library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java
View file @
7e7a33a8
...
@@ -77,7 +77,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
...
@@ -77,7 +77,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
* <p>A typical usage of DownloadHelper follows these steps:
* <p>A typical usage of DownloadHelper follows these steps:
*
*
* <ol>
* <ol>
* <li>Build the helper using one of the {@code for
XXX
} methods.
* <li>Build the helper using one of the {@code for
MediaItem
} methods.
* <li>Prepare the helper using {@link #prepare(Callback)} and wait for the callback.
* <li>Prepare the helper using {@link #prepare(Callback)} and wait for the callback.
* <li>Optional: Inspect the selected tracks using {@link #getMappedTrackInfo(int)} and {@link
* <li>Optional: Inspect the selected tracks using {@link #getMappedTrackInfo(int)} and {@link
* #getTrackSelections(int, int)}, and make adjustments using {@link
* #getTrackSelections(int, int)}, and make adjustments using {@link
...
@@ -448,14 +448,7 @@ public final class DownloadHelper {
...
@@ -448,14 +448,7 @@ public final class DownloadHelper {
DataSource
.
Factory
dataSourceFactory
,
DataSource
.
Factory
dataSourceFactory
,
@Nullable
DrmSessionManager
drmSessionManager
)
{
@Nullable
DrmSessionManager
drmSessionManager
)
{
return
createMediaSourceInternal
(
return
createMediaSourceInternal
(
new
MediaItem
.
Builder
()
downloadRequest
.
toMediaItem
(),
dataSourceFactory
,
drmSessionManager
);
.
setUri
(
downloadRequest
.
uri
)
.
setCustomCacheKey
(
downloadRequest
.
customCacheKey
)
.
setMimeType
(
downloadRequest
.
mimeType
)
.
setStreamKeys
(
downloadRequest
.
streamKeys
)
.
build
(),
dataSourceFactory
,
drmSessionManager
);
}
}
private
final
MediaItem
.
PlaybackProperties
playbackProperties
;
private
final
MediaItem
.
PlaybackProperties
playbackProperties
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadRequest.java
View file @
7e7a33a8
...
@@ -22,6 +22,7 @@ import android.os.Parcel;
...
@@ -22,6 +22,7 @@ import android.os.Parcel;
import
android.os.Parcelable
;
import
android.os.Parcelable
;
import
androidx.annotation.Nullable
;
import
androidx.annotation.Nullable
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.MediaItem
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.Util
;
import
com.google.android.exoplayer2.util.Util
;
import
com.google.common.collect.ImmutableList
;
import
com.google.common.collect.ImmutableList
;
...
@@ -220,6 +221,18 @@ public final class DownloadRequest implements Parcelable {
...
@@ -220,6 +221,18 @@ public final class DownloadRequest implements Parcelable {
newRequest
.
data
);
newRequest
.
data
);
}
}
/** Returns a {@link MediaItem} for the content defined by the request. */
public
MediaItem
toMediaItem
()
{
return
new
MediaItem
.
Builder
()
.
setMediaId
(
id
)
.
setUri
(
uri
)
.
setCustomCacheKey
(
customCacheKey
)
.
setMimeType
(
mimeType
)
.
setStreamKeys
(
streamKeys
)
.
setDrmKeySetId
(
keySetId
)
.
build
();
}
@Override
@Override
public
String
toString
()
{
public
String
toString
()
{
return
mimeType
+
":"
+
id
;
return
mimeType
+
":"
+
id
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java
View file @
7e7a33a8
...
@@ -264,7 +264,8 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
...
@@ -264,7 +264,8 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
for
(
TrackSelection
trackSelection
:
selections
)
{
for
(
TrackSelection
trackSelection
:
selections
)
{
if
(
trackSelection
!=
null
)
{
if
(
trackSelection
!=
null
)
{
Format
selectedFormat
=
trackSelection
.
getSelectedFormat
();
Format
selectedFormat
=
trackSelection
.
getSelectedFormat
();
if
(!
MimeTypes
.
allSamplesAreSyncSamples
(
selectedFormat
.
sampleMimeType
))
{
if
(!
MimeTypes
.
allSamplesAreSyncSamples
(
selectedFormat
.
sampleMimeType
,
selectedFormat
.
codecs
))
{
return
true
;
return
true
;
}
}
}
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceEventListener.java
View file @
7e7a33a8
...
@@ -15,8 +15,9 @@
...
@@ -15,8 +15,9 @@
*/
*/
package
com
.
google
.
android
.
exoplayer2
.
source
;
package
com
.
google
.
android
.
exoplayer2
.
source
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Util
.
postOrRun
;
import
android.os.Handler
;
import
android.os.Handler
;
import
android.os.Looper
;
import
androidx.annotation.CheckResult
;
import
androidx.annotation.CheckResult
;
import
androidx.annotation.Nullable
;
import
androidx.annotation.Nullable
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.C
;
...
@@ -471,14 +472,6 @@ public interface MediaSourceEventListener {
...
@@ -471,14 +472,6 @@ public interface MediaSourceEventListener {
return
mediaTimeMs
==
C
.
TIME_UNSET
?
C
.
TIME_UNSET
:
mediaTimeOffsetMs
+
mediaTimeMs
;
return
mediaTimeMs
==
C
.
TIME_UNSET
?
C
.
TIME_UNSET
:
mediaTimeOffsetMs
+
mediaTimeMs
;
}
}
private
static
void
postOrRun
(
Handler
handler
,
Runnable
runnable
)
{
if
(
handler
.
getLooper
()
==
Looper
.
myLooper
())
{
runnable
.
run
();
}
else
{
handler
.
post
(
runnable
);
}
}
private
static
final
class
ListenerAndHandler
{
private
static
final
class
ListenerAndHandler
{
public
Handler
handler
;
public
Handler
handler
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java
View file @
7e7a33a8
...
@@ -215,6 +215,22 @@ public class SampleQueue implements TrackOutput {
...
@@ -215,6 +215,22 @@ public class SampleQueue implements TrackOutput {
sampleDataQueue
.
discardUpstreamSampleBytes
(
discardUpstreamSampleMetadata
(
discardFromIndex
));
sampleDataQueue
.
discardUpstreamSampleBytes
(
discardUpstreamSampleMetadata
(
discardFromIndex
));
}
}
/**
* Discards samples from the write side of the queue.
*
* @param timeUs Samples will be discarded from the write end of the queue until a sample with a
* timestamp smaller than timeUs is encountered (this sample is not discarded). Must be larger
* than {@link #getLargestReadTimestampUs()}.
*/
public
final
void
discardUpstreamFrom
(
long
timeUs
)
{
if
(
length
==
0
)
{
return
;
}
checkArgument
(
timeUs
>
getLargestReadTimestampUs
());
int
retainCount
=
countUnreadSamplesBefore
(
timeUs
);
discardUpstreamSamples
(
absoluteFirstIndex
+
retainCount
);
}
// Called by the consuming thread.
// Called by the consuming thread.
/** Calls {@link #discardToEnd()} and releases any resources owned by the queue. */
/** Calls {@link #discardToEnd()} and releases any resources owned by the queue. */
...
@@ -279,6 +295,16 @@ public class SampleQueue implements TrackOutput {
...
@@ -279,6 +295,16 @@ public class SampleQueue implements TrackOutput {
}
}
/**
/**
* Returns the largest sample timestamp that has been read since the last {@link #reset}.
*
* @return The largest sample timestamp that has been read, or {@link Long#MIN_VALUE} if no
* samples have been read.
*/
public
final
synchronized
long
getLargestReadTimestampUs
()
{
return
max
(
largestDiscardedTimestampUs
,
getLargestTimestamp
(
readPosition
));
}
/**
* Returns whether the last sample of the stream has knowingly been queued. A return value of
* Returns whether the last sample of the stream has knowingly been queued. A return value of
* {@code false} means that the last sample had not been queued or that it's unknown whether the
* {@code false} means that the last sample had not been queued or that it's unknown whether the
* last sample has been queued.
* last sample has been queued.
...
@@ -659,7 +685,7 @@ public class SampleQueue implements TrackOutput {
...
@@ -659,7 +685,7 @@ public class SampleQueue implements TrackOutput {
upstreamFormat
=
format
;
upstreamFormat
=
format
;
}
}
upstreamAllSamplesAreSyncSamples
=
upstreamAllSamplesAreSyncSamples
=
MimeTypes
.
allSamplesAreSyncSamples
(
upstreamFormat
.
sampleMimeType
);
MimeTypes
.
allSamplesAreSyncSamples
(
upstreamFormat
.
sampleMimeType
,
upstreamFormat
.
codecs
);
loggedUnexpectedNonSyncSample
=
false
;
loggedUnexpectedNonSyncSample
=
false
;
return
true
;
return
true
;
}
}
...
@@ -777,20 +803,10 @@ public class SampleQueue implements TrackOutput {
...
@@ -777,20 +803,10 @@ public class SampleQueue implements TrackOutput {
if
(
length
==
0
)
{
if
(
length
==
0
)
{
return
timeUs
>
largestDiscardedTimestampUs
;
return
timeUs
>
largestDiscardedTimestampUs
;
}
}
long
largestReadTimestampUs
=
if
(
getLargestReadTimestampUs
()
>=
timeUs
)
{
max
(
largestDiscardedTimestampUs
,
getLargestTimestamp
(
readPosition
));
if
(
largestReadTimestampUs
>=
timeUs
)
{
return
false
;
return
false
;
}
}
int
retainCount
=
length
;
int
retainCount
=
countUnreadSamplesBefore
(
timeUs
);
int
relativeSampleIndex
=
getRelativeIndex
(
length
-
1
);
while
(
retainCount
>
readPosition
&&
timesUs
[
relativeSampleIndex
]
>=
timeUs
)
{
retainCount
--;
relativeSampleIndex
--;
if
(
relativeSampleIndex
==
-
1
)
{
relativeSampleIndex
=
capacity
-
1
;
}
}
discardUpstreamSampleMetadata
(
absoluteFirstIndex
+
retainCount
);
discardUpstreamSampleMetadata
(
absoluteFirstIndex
+
retainCount
);
return
true
;
return
true
;
}
}
...
@@ -888,6 +904,26 @@ public class SampleQueue implements TrackOutput {
...
@@ -888,6 +904,26 @@ public class SampleQueue implements TrackOutput {
}
}
/**
/**
* Counts the number of samples that haven't been read that have a timestamp smaller than {@code
* timeUs}.
*
* @param timeUs The specified time.
* @return The number of unread samples with a timestamp smaller than {@code timeUs}.
*/
private
int
countUnreadSamplesBefore
(
long
timeUs
)
{
int
count
=
length
;
int
relativeSampleIndex
=
getRelativeIndex
(
length
-
1
);
while
(
count
>
readPosition
&&
timesUs
[
relativeSampleIndex
]
>=
timeUs
)
{
count
--;
relativeSampleIndex
--;
if
(
relativeSampleIndex
==
-
1
)
{
relativeSampleIndex
=
capacity
-
1
;
}
}
return
count
;
}
/**
* Discards the specified number of samples.
* Discards the specified number of samples.
*
*
* @param discardCount The number of samples to discard.
* @param discardCount The number of samples to discard.
...
...
library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java
View file @
7e7a33a8
...
@@ -319,12 +319,6 @@ public class EventLogger implements AnalyticsListener {
...
@@ -319,12 +319,6 @@ public class EventLogger implements AnalyticsListener {
}
}
@Override
@Override
public
void
onAudioPositionAdvancing
(
EventTime
eventTime
,
long
playoutStartSystemTimeMs
)
{
long
timeSincePlayoutStartMs
=
System
.
currentTimeMillis
()
-
playoutStartSystemTimeMs
;
logd
(
eventTime
,
"audioPositionAdvancing"
,
"timeSincePlayoutStartMs="
+
timeSincePlayoutStartMs
);
}
@Override
public
void
onAudioUnderrun
(
public
void
onAudioUnderrun
(
EventTime
eventTime
,
int
bufferSize
,
long
bufferSizeMs
,
long
elapsedSinceLastFeedMs
)
{
EventTime
eventTime
,
int
bufferSize
,
long
bufferSizeMs
,
long
elapsedSinceLastFeedMs
)
{
loge
(
loge
(
...
...
library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java
View file @
7e7a33a8
...
@@ -17,6 +17,7 @@ package com.google.android.exoplayer2;
...
@@ -17,6 +17,7 @@ package com.google.android.exoplayer2;
import
static
com
.
google
.
android
.
exoplayer2
.
testutil
.
FakeSampleStream
.
FakeSampleStreamItem
.
END_OF_STREAM_ITEM
;
import
static
com
.
google
.
android
.
exoplayer2
.
testutil
.
FakeSampleStream
.
FakeSampleStreamItem
.
END_OF_STREAM_ITEM
;
import
static
com
.
google
.
android
.
exoplayer2
.
testutil
.
FakeSampleStream
.
FakeSampleStreamItem
.
oneByteSample
;
import
static
com
.
google
.
android
.
exoplayer2
.
testutil
.
FakeSampleStream
.
FakeSampleStreamItem
.
oneByteSample
;
import
static
com
.
google
.
android
.
exoplayer2
.
testutil
.
TestExoPlayer
.
playUntilStartOfWindow
;
import
static
com
.
google
.
android
.
exoplayer2
.
testutil
.
TestExoPlayer
.
runUntilPlaybackState
;
import
static
com
.
google
.
android
.
exoplayer2
.
testutil
.
TestExoPlayer
.
runUntilPlaybackState
;
import
static
com
.
google
.
android
.
exoplayer2
.
testutil
.
TestExoPlayer
.
runUntilTimelineChanged
;
import
static
com
.
google
.
android
.
exoplayer2
.
testutil
.
TestExoPlayer
.
runUntilTimelineChanged
;
import
static
com
.
google
.
android
.
exoplayer2
.
testutil
.
TestUtil
.
runMainLooperUntil
;
import
static
com
.
google
.
android
.
exoplayer2
.
testutil
.
TestUtil
.
runMainLooperUntil
;
...
@@ -114,9 +115,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
...
@@ -114,9 +115,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
import
java.util.concurrent.atomic.AtomicInteger
;
import
java.util.concurrent.atomic.AtomicInteger
;
import
java.util.concurrent.atomic.AtomicLong
;
import
java.util.concurrent.atomic.AtomicLong
;
import
java.util.concurrent.atomic.AtomicReference
;
import
java.util.concurrent.atomic.AtomicReference
;
import
java.util.stream.Collectors
;
import
org.junit.Before
;
import
org.junit.Before
;
import
org.junit.Test
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
import
org.junit.runner.RunWith
;
import
org.mockito.ArgumentCaptor
;
import
org.mockito.ArgumentMatcher
;
import
org.mockito.ArgumentMatcher
;
import
org.mockito.InOrder
;
import
org.mockito.InOrder
;
import
org.mockito.Mockito
;
import
org.mockito.Mockito
;
...
@@ -439,49 +442,41 @@ public final class ExoPlayerTest {
...
@@ -439,49 +442,41 @@ public final class ExoPlayerTest {
public
void
repeatModeChanges
()
throws
Exception
{
public
void
repeatModeChanges
()
throws
Exception
{
Timeline
timeline
=
new
FakeTimeline
(
/* windowCount= */
3
);
Timeline
timeline
=
new
FakeTimeline
(
/* windowCount= */
3
);
FakeRenderer
renderer
=
new
FakeRenderer
(
C
.
TRACK_TYPE_VIDEO
);
FakeRenderer
renderer
=
new
FakeRenderer
(
C
.
TRACK_TYPE_VIDEO
);
ActionSchedule
actionSchedule
=
SimpleExoPlayer
player
=
new
TestExoPlayer
.
Builder
(
context
).
setRenderers
(
renderer
).
build
();
new
ActionSchedule
.
Builder
(
TAG
)
AnalyticsListener
mockAnalyticsListener
=
mock
(
AnalyticsListener
.
class
);
.
pause
()
player
.
addAnalyticsListener
(
mockAnalyticsListener
);
.
waitForTimelineChanged
(
timeline
,
/* expectedReason */
Player
.
TIMELINE_CHANGE_REASON_SOURCE_UPDATE
)
player
.
setMediaSource
(
new
FakeMediaSource
(
timeline
,
ExoPlayerTestRunner
.
VIDEO_FORMAT
));
.
playUntilStartOfWindow
(
/* windowIndex= */
1
)
player
.
prepare
();
.
setRepeatMode
(
Player
.
REPEAT_MODE_ONE
)
runUntilTimelineChanged
(
player
);
.
playUntilStartOfWindow
(
/* windowIndex= */
1
)
playUntilStartOfWindow
(
player
,
/* windowIndex= */
1
);
.
setRepeatMode
(
Player
.
REPEAT_MODE_OFF
)
player
.
setRepeatMode
(
Player
.
REPEAT_MODE_ONE
);
.
playUntilStartOfWindow
(
/* windowIndex= */
2
)
playUntilStartOfWindow
(
player
,
/* windowIndex= */
1
);
.
setRepeatMode
(
Player
.
REPEAT_MODE_ONE
)
player
.
setRepeatMode
(
Player
.
REPEAT_MODE_OFF
);
.
playUntilStartOfWindow
(
/* windowIndex= */
2
)
playUntilStartOfWindow
(
player
,
/* windowIndex= */
2
);
.
setRepeatMode
(
Player
.
REPEAT_MODE_ALL
)
player
.
setRepeatMode
(
Player
.
REPEAT_MODE_ONE
);
.
playUntilStartOfWindow
(
/* windowIndex= */
0
)
playUntilStartOfWindow
(
player
,
/* windowIndex= */
2
);
.
setRepeatMode
(
Player
.
REPEAT_MODE_ONE
)
player
.
setRepeatMode
(
Player
.
REPEAT_MODE_ALL
);
.
playUntilStartOfWindow
(
/* windowIndex= */
0
)
playUntilStartOfWindow
(
player
,
/* windowIndex= */
0
);
.
playUntilStartOfWindow
(
/* windowIndex= */
0
)
player
.
setRepeatMode
(
Player
.
REPEAT_MODE_ONE
);
.
setRepeatMode
(
Player
.
REPEAT_MODE_OFF
)
playUntilStartOfWindow
(
player
,
/* windowIndex= */
0
);
.
play
()
playUntilStartOfWindow
(
player
,
/* windowIndex= */
0
);
.
build
();
player
.
setRepeatMode
(
Player
.
REPEAT_MODE_OFF
);
ExoPlayerTestRunner
testRunner
=
playUntilStartOfWindow
(
player
,
/* windowIndex= */
1
);
new
ExoPlayerTestRunner
.
Builder
(
context
)
playUntilStartOfWindow
(
player
,
/* windowIndex= */
2
);
.
setTimeline
(
timeline
)
player
.
play
();
.
setRenderers
(
renderer
)
runUntilPlaybackState
(
player
,
Player
.
STATE_ENDED
);
.
setActionSchedule
(
actionSchedule
)
.
build
()
ArgumentCaptor
<
AnalyticsListener
.
EventTime
>
eventTimes
=
.
start
()
ArgumentCaptor
.
forClass
(
AnalyticsListener
.
EventTime
.
class
);
.
blockUntilEnded
(
TIMEOUT_MS
);
verify
(
mockAnalyticsListener
,
times
(
10
))
testRunner
.
assertPlayedPeriodIndices
(
0
,
1
,
1
,
2
,
2
,
0
,
0
,
0
,
1
,
2
);
.
onMediaItemTransition
(
eventTimes
.
capture
(),
any
(),
anyInt
());
testRunner
.
assertPositionDiscontinuityReasonsEqual
(
assertThat
(
Player
.
DISCONTINUITY_REASON_PERIOD_TRANSITION
,
eventTimes
.
getAllValues
().
stream
()
Player
.
DISCONTINUITY_REASON_PERIOD_TRANSITION
,
.
map
(
eventTime
->
eventTime
.
currentWindowIndex
)
Player
.
DISCONTINUITY_REASON_PERIOD_TRANSITION
,
.
collect
(
Collectors
.
toList
()))
Player
.
DISCONTINUITY_REASON_PERIOD_TRANSITION
,
.
containsExactly
(
0
,
1
,
1
,
2
,
2
,
0
,
0
,
0
,
1
,
2
)
Player
.
DISCONTINUITY_REASON_PERIOD_TRANSITION
,
.
inOrder
();
Player
.
DISCONTINUITY_REASON_PERIOD_TRANSITION
,
Player
.
DISCONTINUITY_REASON_PERIOD_TRANSITION
,
Player
.
DISCONTINUITY_REASON_PERIOD_TRANSITION
,
Player
.
DISCONTINUITY_REASON_PERIOD_TRANSITION
);
testRunner
.
assertTimelinesSame
(
new
FakeMediaSource
.
InitialTimeline
(
timeline
),
timeline
);
testRunner
.
assertTimelineChangeReasonsEqual
(
Player
.
TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED
,
Player
.
TIMELINE_CHANGE_REASON_SOURCE_UPDATE
);
assertThat
(
renderer
.
isEnded
).
isTrue
();
assertThat
(
renderer
.
isEnded
).
isTrue
();
}
}
...
...
library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuerTest.java
View file @
7e7a33a8
...
@@ -21,8 +21,8 @@ import static org.junit.Assert.assertThrows;
...
@@ -21,8 +21,8 @@ import static org.junit.Assert.assertThrows;
import
static
org
.
mockito
.
Mockito
.
doAnswer
;
import
static
org
.
mockito
.
Mockito
.
doAnswer
;
import
android.media.MediaCodec
;
import
android.media.MediaCodec
;
import
android.media.MediaFormat
;
import
android.os.HandlerThread
;
import
android.os.HandlerThread
;
import
android.os.Looper
;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.decoder.CryptoInfo
;
import
com.google.android.exoplayer2.decoder.CryptoInfo
;
...
@@ -31,15 +31,12 @@ import java.io.IOException;
...
@@ -31,15 +31,12 @@ import java.io.IOException;
import
java.util.concurrent.atomic.AtomicLong
;
import
java.util.concurrent.atomic.AtomicLong
;
import
org.junit.After
;
import
org.junit.After
;
import
org.junit.Before
;
import
org.junit.Before
;
import
org.junit.Ignore
;
import
org.junit.Rule
;
import
org.junit.Rule
;
import
org.junit.Test
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
import
org.junit.runner.RunWith
;
import
org.mockito.Mock
;
import
org.mockito.Mock
;
import
org.mockito.junit.MockitoJUnit
;
import
org.mockito.junit.MockitoJUnit
;
import
org.mockito.junit.MockitoRule
;
import
org.mockito.junit.MockitoRule
;
import
org.robolectric.Shadows
;
import
org.robolectric.shadows.ShadowLooper
;
/** Unit tests for {@link AsynchronousMediaCodecBufferEnqueuer}. */
/** Unit tests for {@link AsynchronousMediaCodecBufferEnqueuer}. */
@RunWith
(
AndroidJUnit4
.
class
)
@RunWith
(
AndroidJUnit4
.
class
)
...
@@ -54,6 +51,8 @@ public class AsynchronousMediaCodecBufferEnqueuerTest {
...
@@ -54,6 +51,8 @@ public class AsynchronousMediaCodecBufferEnqueuerTest {
@Before
@Before
public
void
setUp
()
throws
IOException
{
public
void
setUp
()
throws
IOException
{
codec
=
MediaCodec
.
createByCodecName
(
"h264"
);
codec
=
MediaCodec
.
createByCodecName
(
"h264"
);
codec
.
configure
(
new
MediaFormat
(),
/* surface= */
null
,
/* crypto= */
null
,
/* flags= */
0
);
codec
.
start
();
handlerThread
=
new
TestHandlerThread
(
"TestHandlerThread"
);
handlerThread
=
new
TestHandlerThread
(
"TestHandlerThread"
);
enqueuer
=
enqueuer
=
new
AsynchronousMediaCodecBufferEnqueuer
(
codec
,
handlerThread
,
mockConditionVariable
);
new
AsynchronousMediaCodecBufferEnqueuer
(
codec
,
handlerThread
,
mockConditionVariable
);
...
@@ -62,7 +61,8 @@ public class AsynchronousMediaCodecBufferEnqueuerTest {
...
@@ -62,7 +61,8 @@ public class AsynchronousMediaCodecBufferEnqueuerTest {
@After
@After
public
void
tearDown
()
{
public
void
tearDown
()
{
enqueuer
.
shutdown
();
enqueuer
.
shutdown
();
codec
.
stop
();
codec
.
release
();
assertThat
(
TestHandlerThread
.
INSTANCES_STARTED
.
get
()).
isEqualTo
(
0
);
assertThat
(
TestHandlerThread
.
INSTANCES_STARTED
.
get
()).
isEqualTo
(
0
);
}
}
...
@@ -98,32 +98,6 @@ public class AsynchronousMediaCodecBufferEnqueuerTest {
...
@@ -98,32 +98,6 @@ public class AsynchronousMediaCodecBufferEnqueuerTest {
/* flags= */
0
));
/* flags= */
0
));
}
}
@Ignore
@Test
public
void
queueInputBuffer_multipleTimes_limitsObjectsAllocation
()
{
enqueuer
.
start
();
Looper
looper
=
handlerThread
.
getLooper
();
ShadowLooper
shadowLooper
=
Shadows
.
shadowOf
(
looper
);
for
(
int
cycle
=
0
;
cycle
<
100
;
cycle
++)
{
// This test assumes that the shadow MediaCodec implementation can dequeue at least
// 10 input buffers before queueing them back.
for
(
int
i
=
0
;
i
<
10
;
i
++)
{
int
inputBufferIndex
=
codec
.
dequeueInputBuffer
(
0
);
enqueuer
.
queueInputBuffer
(
/* index= */
inputBufferIndex
,
/* offset= */
0
,
/* size= */
0
,
/* presentationTimeUs= */
i
,
/* flags= */
0
);
}
// Execute all messages, queues input buffers back to MediaCodec.
shadowLooper
.
idle
();
}
assertThat
(
AsynchronousMediaCodecBufferEnqueuer
.
getInstancePoolSize
()).
isEqualTo
(
10
);
}
@Test
@Test
public
void
queueSecureInputBuffer_withPendingCryptoException_throwsCryptoException
()
{
public
void
queueSecureInputBuffer_withPendingCryptoException_throwsCryptoException
()
{
enqueuer
.
setPendingRuntimeException
(
enqueuer
.
setPendingRuntimeException
(
...
@@ -159,33 +133,6 @@ public class AsynchronousMediaCodecBufferEnqueuerTest {
...
@@ -159,33 +133,6 @@ public class AsynchronousMediaCodecBufferEnqueuerTest {
/* flags= */
0
));
/* flags= */
0
));
}
}
@Ignore
@Test
public
void
queueSecureInputBuffer_multipleTimes_limitsObjectsAllocation
()
{
enqueuer
.
start
();
Looper
looper
=
handlerThread
.
getLooper
();
CryptoInfo
info
=
createCryptoInfo
();
ShadowLooper
shadowLooper
=
Shadows
.
shadowOf
(
looper
);
for
(
int
cycle
=
0
;
cycle
<
100
;
cycle
++)
{
// This test assumes that the shadow MediaCodec implementation can dequeue at least
// 10 input buffers before queueing them back.
int
inputBufferIndex
=
codec
.
dequeueInputBuffer
(
0
);
for
(
int
i
=
0
;
i
<
10
;
i
++)
{
enqueuer
.
queueSecureInputBuffer
(
/* index= */
inputBufferIndex
,
/* offset= */
0
,
/* info= */
info
,
/* presentationTimeUs= */
i
,
/* flags= */
0
);
}
// Execute all messages, queues input buffers back to MediaCodec.
shadowLooper
.
idle
();
}
assertThat
(
AsynchronousMediaCodecBufferEnqueuer
.
getInstancePoolSize
()).
isEqualTo
(
10
);
}
@Test
@Test
public
void
flush_withoutStart_works
()
{
public
void
flush_withoutStart_works
()
{
enqueuer
.
flush
();
enqueuer
.
flush
();
...
...
library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java
View file @
7e7a33a8
...
@@ -879,6 +879,118 @@ public final class SampleQueueTest {
...
@@ -879,6 +879,118 @@ public final class SampleQueueTest {
}
}
@Test
@Test
public
void
discardUpstreamFrom
()
{
writeTestData
();
sampleQueue
.
discardUpstreamFrom
(
8000
);
assertAllocationCount
(
10
);
sampleQueue
.
discardUpstreamFrom
(
7000
);
assertAllocationCount
(
9
);
sampleQueue
.
discardUpstreamFrom
(
6000
);
assertAllocationCount
(
7
);
sampleQueue
.
discardUpstreamFrom
(
5000
);
assertAllocationCount
(
5
);
sampleQueue
.
discardUpstreamFrom
(
4000
);
assertAllocationCount
(
4
);
sampleQueue
.
discardUpstreamFrom
(
3000
);
assertAllocationCount
(
3
);
sampleQueue
.
discardUpstreamFrom
(
2000
);
assertAllocationCount
(
2
);
sampleQueue
.
discardUpstreamFrom
(
1000
);
assertAllocationCount
(
1
);
sampleQueue
.
discardUpstreamFrom
(
0
);
assertAllocationCount
(
0
);
assertReadFormat
(
false
,
FORMAT_2
);
assertNoSamplesToRead
(
FORMAT_2
);
}
@Test
public
void
discardUpstreamFromMulti
()
{
writeTestData
();
sampleQueue
.
discardUpstreamFrom
(
4000
);
assertAllocationCount
(
4
);
sampleQueue
.
discardUpstreamFrom
(
0
);
assertAllocationCount
(
0
);
assertReadFormat
(
false
,
FORMAT_2
);
assertNoSamplesToRead
(
FORMAT_2
);
}
@Test
public
void
discardUpstreamFromNonSampleTimestamps
()
{
writeTestData
();
sampleQueue
.
discardUpstreamFrom
(
3500
);
assertAllocationCount
(
4
);
sampleQueue
.
discardUpstreamFrom
(
500
);
assertAllocationCount
(
1
);
sampleQueue
.
discardUpstreamFrom
(
0
);
assertAllocationCount
(
0
);
assertReadFormat
(
false
,
FORMAT_2
);
assertNoSamplesToRead
(
FORMAT_2
);
}
@Test
public
void
discardUpstreamFromBeforeRead
()
{
writeTestData
();
sampleQueue
.
discardUpstreamFrom
(
4000
);
assertAllocationCount
(
4
);
assertReadTestData
(
null
,
0
,
4
);
assertReadFormat
(
false
,
FORMAT_2
);
assertNoSamplesToRead
(
FORMAT_2
);
}
@Test
public
void
discardUpstreamFromAfterRead
()
{
writeTestData
();
assertReadTestData
(
null
,
0
,
3
);
sampleQueue
.
discardUpstreamFrom
(
8000
);
assertAllocationCount
(
10
);
sampleQueue
.
discardToRead
();
assertAllocationCount
(
7
);
sampleQueue
.
discardUpstreamFrom
(
7000
);
assertAllocationCount
(
6
);
sampleQueue
.
discardUpstreamFrom
(
6000
);
assertAllocationCount
(
4
);
sampleQueue
.
discardUpstreamFrom
(
5000
);
assertAllocationCount
(
2
);
sampleQueue
.
discardUpstreamFrom
(
4000
);
assertAllocationCount
(
1
);
sampleQueue
.
discardUpstreamFrom
(
3000
);
assertAllocationCount
(
0
);
assertReadFormat
(
false
,
FORMAT_2
);
assertNoSamplesToRead
(
FORMAT_2
);
}
@Test
public
void
largestQueuedTimestampWithDiscardUpstreamFrom
()
{
writeTestData
();
assertThat
(
sampleQueue
.
getLargestQueuedTimestampUs
()).
isEqualTo
(
LAST_SAMPLE_TIMESTAMP
);
sampleQueue
.
discardUpstreamFrom
(
SAMPLE_TIMESTAMPS
[
SAMPLE_TIMESTAMPS
.
length
-
1
]);
// Discarding from upstream should reduce the largest timestamp.
assertThat
(
sampleQueue
.
getLargestQueuedTimestampUs
())
.
isEqualTo
(
SAMPLE_TIMESTAMPS
[
SAMPLE_TIMESTAMPS
.
length
-
2
]);
sampleQueue
.
discardUpstreamFrom
(
0
);
// Discarding everything from upstream without reading should unset the largest timestamp.
assertThat
(
sampleQueue
.
getLargestQueuedTimestampUs
()).
isEqualTo
(
MIN_VALUE
);
}
@Test
public
void
largestQueuedTimestampWithDiscardUpstreamFromDecodeOrder
()
{
long
[]
decodeOrderTimestamps
=
new
long
[]
{
0
,
3000
,
2000
,
1000
,
4000
,
7000
,
6000
,
5000
};
writeTestData
(
DATA
,
SAMPLE_SIZES
,
SAMPLE_OFFSETS
,
decodeOrderTimestamps
,
SAMPLE_FORMATS
,
SAMPLE_FLAGS
);
assertThat
(
sampleQueue
.
getLargestQueuedTimestampUs
()).
isEqualTo
(
7000
);
sampleQueue
.
discardUpstreamFrom
(
SAMPLE_TIMESTAMPS
[
SAMPLE_TIMESTAMPS
.
length
-
2
]);
// Discarding the last two samples should not change the largest timestamp, due to the decode
// ordering of the timestamps.
assertThat
(
sampleQueue
.
getLargestQueuedTimestampUs
()).
isEqualTo
(
7000
);
sampleQueue
.
discardUpstreamFrom
(
SAMPLE_TIMESTAMPS
[
SAMPLE_TIMESTAMPS
.
length
-
3
]);
// Once a third sample is discarded, the largest timestamp should have changed.
assertThat
(
sampleQueue
.
getLargestQueuedTimestampUs
()).
isEqualTo
(
4000
);
sampleQueue
.
discardUpstreamFrom
(
0
);
// Discarding everything from upstream without reading should unset the largest timestamp.
assertThat
(
sampleQueue
.
getLargestQueuedTimestampUs
()).
isEqualTo
(
MIN_VALUE
);
}
@Test
public
void
discardUpstream
()
{
public
void
discardUpstream
()
{
writeTestData
();
writeTestData
();
sampleQueue
.
discardUpstreamSamples
(
8
);
sampleQueue
.
discardUpstreamSamples
(
8
);
...
@@ -987,6 +1099,43 @@ public final class SampleQueueTest {
...
@@ -987,6 +1099,43 @@ public final class SampleQueueTest {
}
}
@Test
@Test
public
void
largestReadTimestampWithReadAll
()
{
writeTestData
();
assertThat
(
sampleQueue
.
getLargestReadTimestampUs
()).
isEqualTo
(
MIN_VALUE
);
assertReadTestData
();
assertThat
(
sampleQueue
.
getLargestReadTimestampUs
()).
isEqualTo
(
LAST_SAMPLE_TIMESTAMP
);
}
@Test
public
void
largestReadTimestampWithReads
()
{
writeTestData
();
assertThat
(
sampleQueue
.
getLargestReadTimestampUs
()).
isEqualTo
(
MIN_VALUE
);
assertReadTestData
(
/* startFormat= */
null
,
0
,
2
);
assertThat
(
sampleQueue
.
getLargestReadTimestampUs
()).
isEqualTo
(
SAMPLE_TIMESTAMPS
[
1
]);
assertReadTestData
(
SAMPLE_FORMATS
[
1
],
2
,
3
);
assertThat
(
sampleQueue
.
getLargestReadTimestampUs
()).
isEqualTo
(
SAMPLE_TIMESTAMPS
[
4
]);
}
@Test
public
void
largestReadTimestampWithDiscard
()
{
// Discarding shouldn't change the read timestamp.
writeTestData
();
assertThat
(
sampleQueue
.
getLargestReadTimestampUs
()).
isEqualTo
(
MIN_VALUE
);
sampleQueue
.
discardUpstreamSamples
(
5
);
assertThat
(
sampleQueue
.
getLargestReadTimestampUs
()).
isEqualTo
(
MIN_VALUE
);
assertReadTestData
(
/* startFormat= */
null
,
0
,
3
);
assertThat
(
sampleQueue
.
getLargestReadTimestampUs
()).
isEqualTo
(
SAMPLE_TIMESTAMPS
[
2
]);
sampleQueue
.
discardUpstreamSamples
(
3
);
assertThat
(
sampleQueue
.
getLargestReadTimestampUs
()).
isEqualTo
(
SAMPLE_TIMESTAMPS
[
2
]);
sampleQueue
.
discardToRead
();
assertThat
(
sampleQueue
.
getLargestReadTimestampUs
()).
isEqualTo
(
SAMPLE_TIMESTAMPS
[
2
]);
}
@Test
public
void
setSampleOffsetBeforeData
()
{
public
void
setSampleOffsetBeforeData
()
{
long
sampleOffsetUs
=
1000
;
long
sampleOffsetUs
=
1000
;
sampleQueue
.
setSampleOffsetUs
(
sampleOffsetUs
);
sampleQueue
.
setSampleOffsetUs
(
sampleOffsetUs
);
...
...
library/dash/README.md
View file @
7e7a33a8
# ExoPlayer DASH library module #
# ExoPlayer DASH library module #
Provides support for Dynamic Adaptive Streaming over HTTP (DASH) content. To
Provides support for Dynamic Adaptive Streaming over HTTP (DASH) content.
play DASH content, instantiate a
`DashMediaSource`
and pass it to
`ExoPlayer.prepare`
.
Adding a dependency to this module is all that's required to enable playback of
DASH
`MediaItem`
s added to an
`ExoPlayer`
or
`SimpleExoPlayer`
in their default
configurations. Internally,
`DefaultMediaSourceFactory`
will automatically
detect the presence of the module and convert DASH
`MediaItem`
s into
`DashMediaSource`
instances for playback.
Similarly, a
`DownloadManager`
in its default configuration will use
`DefaultDownloaderFactory`
, which will automatically detect the presence of
the module and build
`DashDownloader`
instances to download DASH content.
For advanced playback use cases, applications can build
`DashMediaSource`
instances and pass them directly to the player. For advanced download use cases,
`DashDownloader`
can be used directly.
## Links ##
## Links ##
...
...
library/hls/README.md
View file @
7e7a33a8
# ExoPlayer HLS library module #
# ExoPlayer HLS library module #
Provides support for HTTP Live Streaming (HLS) content. To play HLS content,
Provides support for HTTP Live Streaming (HLS) content.
instantiate a
`HlsMediaSource`
and pass it to
`ExoPlayer.prepare`
.
Adding a dependency to this module is all that's required to enable playback of
HLS
`MediaItem`
s added to an
`ExoPlayer`
or
`SimpleExoPlayer`
in their default
configurations. Internally,
`DefaultMediaSourceFactory`
will automatically
detect the presence of the module and convert HLS
`MediaItem`
s into
`HlsMediaSource`
instances for playback.
Similarly, a
`DownloadManager`
in its default configuration will use
`DefaultDownloaderFactory`
, which will automatically detect the presence of
the module and build
`HlsDownloader`
instances to download HLS content.
For advanced playback use cases, applications can build
`HlsMediaSource`
instances and pass them directly to the player. For advanced download use cases,
`HlsDownloader`
can be used directly.
## Links ##
## Links ##
...
...
library/smoothstreaming/README.md
View file @
7e7a33a8
# ExoPlayer SmoothStreaming library module #
# ExoPlayer SmoothStreaming library module #
Provides support for Smooth Streaming content. To play Smooth Streaming content,
Provides support for SmoothStreaming content.
instantiate a
`SsMediaSource`
and pass it to
`ExoPlayer.prepare`
.
Adding a dependency to this module is all that's required to enable playback of
SmoothStreaming
`MediaItem`
s added to an
`ExoPlayer`
or
`SimpleExoPlayer`
in
their default configurations. Internally,
`DefaultMediaSourceFactory`
will
automatically detect the presence of the module and convert SmoothStreaming
`MediaItem`
s into
`SsMediaSource`
instances for playback.
Similarly, a
`DownloadManager`
in its default configuration will use
`DefaultDownloaderFactory`
, which will automatically detect the presence of
the module and build
`SsDownloader`
instances to download SmoothStreaming
content.
For advanced playback use cases, applications can build
`SsMediaSource`
instances and pass them directly to the player. For advanced download use cases,
`SsDownloader`
can be used directly.
## Links ##
## Links ##
...
...
library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java
View file @
7e7a33a8
...
@@ -38,6 +38,7 @@ import com.google.android.exoplayer2.DefaultControlDispatcher;
...
@@ -38,6 +38,7 @@ import com.google.android.exoplayer2.DefaultControlDispatcher;
import
com.google.android.exoplayer2.ExoPlayerLibraryInfo
;
import
com.google.android.exoplayer2.ExoPlayerLibraryInfo
;
import
com.google.android.exoplayer2.PlaybackPreparer
;
import
com.google.android.exoplayer2.PlaybackPreparer
;
import
com.google.android.exoplayer2.Player
;
import
com.google.android.exoplayer2.Player
;
import
com.google.android.exoplayer2.Player.State
;
import
com.google.android.exoplayer2.Timeline
;
import
com.google.android.exoplayer2.Timeline
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.RepeatModeUtil
;
import
com.google.android.exoplayer2.util.RepeatModeUtil
;
...
@@ -480,6 +481,7 @@ public class PlayerControlView extends FrameLayout {
...
@@ -480,6 +481,7 @@ public class PlayerControlView extends FrameLayout {
}
}
vrButton
=
findViewById
(
R
.
id
.
exo_vr
);
vrButton
=
findViewById
(
R
.
id
.
exo_vr
);
setShowVrButton
(
false
);
setShowVrButton
(
false
);
updateButton
(
false
,
false
,
vrButton
);
Resources
resources
=
context
.
getResources
();
Resources
resources
=
context
.
getResources
();
...
@@ -793,6 +795,7 @@ public class PlayerControlView extends FrameLayout {
...
@@ -793,6 +795,7 @@ public class PlayerControlView extends FrameLayout {
public
void
setVrButtonListener
(
@Nullable
OnClickListener
onClickListener
)
{
public
void
setVrButtonListener
(
@Nullable
OnClickListener
onClickListener
)
{
if
(
vrButton
!=
null
)
{
if
(
vrButton
!=
null
)
{
vrButton
.
setOnClickListener
(
onClickListener
);
vrButton
.
setOnClickListener
(
onClickListener
);
updateButton
(
getShowVrButton
(),
onClickListener
!=
null
,
vrButton
);
}
}
}
}
...
@@ -1204,19 +1207,22 @@ public class PlayerControlView extends FrameLayout {
...
@@ -1204,19 +1207,22 @@ public class PlayerControlView extends FrameLayout {
}
}
if
(
event
.
getAction
()
==
KeyEvent
.
ACTION_DOWN
)
{
if
(
event
.
getAction
()
==
KeyEvent
.
ACTION_DOWN
)
{
if
(
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_FAST_FORWARD
)
{
if
(
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_FAST_FORWARD
)
{
controlDispatcher
.
dispatchFastForward
(
player
);
if
(
player
.
getPlaybackState
()
!=
Player
.
STATE_ENDED
)
{
controlDispatcher
.
dispatchFastForward
(
player
);
}
}
else
if
(
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_REWIND
)
{
}
else
if
(
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_REWIND
)
{
controlDispatcher
.
dispatchRewind
(
player
);
controlDispatcher
.
dispatchRewind
(
player
);
}
else
if
(
event
.
getRepeatCount
()
==
0
)
{
}
else
if
(
event
.
getRepeatCount
()
==
0
)
{
switch
(
keyCode
)
{
switch
(
keyCode
)
{
case
KeyEvent
.
KEYCODE_MEDIA_PLAY_PAUSE
:
case
KeyEvent
.
KEYCODE_MEDIA_PLAY_PAUSE
:
controlDispatcher
.
dispatchSetPlayWhenReady
(
player
,
!
player
.
getPlayWhenReady
());
case
KeyEvent
.
KEYCODE_HEADSETHOOK
:
dispatchPlayPause
(
player
);
break
;
break
;
case
KeyEvent
.
KEYCODE_MEDIA_PLAY
:
case
KeyEvent
.
KEYCODE_MEDIA_PLAY
:
controlDispatcher
.
dispatchSetPlayWhenReady
(
player
,
true
);
dispatchPlay
(
player
);
break
;
break
;
case
KeyEvent
.
KEYCODE_MEDIA_PAUSE
:
case
KeyEvent
.
KEYCODE_MEDIA_PAUSE
:
controlDispatcher
.
dispatchSetPlayWhenReady
(
player
,
false
);
dispatchPause
(
player
);
break
;
break
;
case
KeyEvent
.
KEYCODE_MEDIA_NEXT
:
case
KeyEvent
.
KEYCODE_MEDIA_NEXT
:
controlDispatcher
.
dispatchNext
(
player
);
controlDispatcher
.
dispatchNext
(
player
);
...
@@ -1239,11 +1245,37 @@ public class PlayerControlView extends FrameLayout {
...
@@ -1239,11 +1245,37 @@ public class PlayerControlView extends FrameLayout {
&&
player
.
getPlayWhenReady
();
&&
player
.
getPlayWhenReady
();
}
}
private
void
dispatchPlayPause
(
Player
player
)
{
@State
int
state
=
player
.
getPlaybackState
();
if
(
state
==
Player
.
STATE_IDLE
||
state
==
Player
.
STATE_ENDED
||
!
player
.
getPlayWhenReady
())
{
dispatchPlay
(
player
);
}
else
{
dispatchPause
(
player
);
}
}
private
void
dispatchPlay
(
Player
player
)
{
@State
int
state
=
player
.
getPlaybackState
();
if
(
state
==
Player
.
STATE_IDLE
)
{
if
(
playbackPreparer
!=
null
)
{
playbackPreparer
.
preparePlayback
();
}
}
else
if
(
state
==
Player
.
STATE_ENDED
)
{
seekTo
(
player
,
player
.
getCurrentWindowIndex
(),
C
.
TIME_UNSET
);
}
controlDispatcher
.
dispatchSetPlayWhenReady
(
player
,
/* playWhenReady= */
true
);
}
private
void
dispatchPause
(
Player
player
)
{
controlDispatcher
.
dispatchSetPlayWhenReady
(
player
,
/* playWhenReady= */
false
);
}
@SuppressLint
(
"InlinedApi"
)
@SuppressLint
(
"InlinedApi"
)
private
static
boolean
isHandledMediaKey
(
int
keyCode
)
{
private
static
boolean
isHandledMediaKey
(
int
keyCode
)
{
return
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_FAST_FORWARD
return
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_FAST_FORWARD
||
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_REWIND
||
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_REWIND
||
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_PLAY_PAUSE
||
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_PLAY_PAUSE
||
keyCode
==
KeyEvent
.
KEYCODE_HEADSETHOOK
||
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_PLAY
||
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_PLAY
||
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_PAUSE
||
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_PAUSE
||
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_NEXT
||
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_NEXT
...
@@ -1349,20 +1381,15 @@ public class PlayerControlView extends FrameLayout {
...
@@ -1349,20 +1381,15 @@ public class PlayerControlView extends FrameLayout {
}
else
if
(
previousButton
==
view
)
{
}
else
if
(
previousButton
==
view
)
{
controlDispatcher
.
dispatchPrevious
(
player
);
controlDispatcher
.
dispatchPrevious
(
player
);
}
else
if
(
fastForwardButton
==
view
)
{
}
else
if
(
fastForwardButton
==
view
)
{
controlDispatcher
.
dispatchFastForward
(
player
);
if
(
player
.
getPlaybackState
()
!=
Player
.
STATE_ENDED
)
{
controlDispatcher
.
dispatchFastForward
(
player
);
}
}
else
if
(
rewindButton
==
view
)
{
}
else
if
(
rewindButton
==
view
)
{
controlDispatcher
.
dispatchRewind
(
player
);
controlDispatcher
.
dispatchRewind
(
player
);
}
else
if
(
playButton
==
view
)
{
}
else
if
(
playButton
==
view
)
{
if
(
player
.
getPlaybackState
()
==
Player
.
STATE_IDLE
)
{
dispatchPlay
(
player
);
if
(
playbackPreparer
!=
null
)
{
playbackPreparer
.
preparePlayback
();
}
}
else
if
(
player
.
getPlaybackState
()
==
Player
.
STATE_ENDED
)
{
seekTo
(
player
,
player
.
getCurrentWindowIndex
(),
C
.
TIME_UNSET
);
}
controlDispatcher
.
dispatchSetPlayWhenReady
(
player
,
true
);
}
else
if
(
pauseButton
==
view
)
{
}
else
if
(
pauseButton
==
view
)
{
controlDispatcher
.
dispatchSetPlayWhenReady
(
player
,
false
);
dispatchPause
(
player
);
}
else
if
(
repeatToggleButton
==
view
)
{
}
else
if
(
repeatToggleButton
==
view
)
{
controlDispatcher
.
dispatchSetRepeatMode
(
controlDispatcher
.
dispatchSetRepeatMode
(
player
,
RepeatModeUtil
.
getNextRepeatMode
(
player
.
getRepeatMode
(),
repeatToggleModes
));
player
,
RepeatModeUtil
.
getNextRepeatMode
(
player
.
getRepeatMode
(),
repeatToggleModes
));
...
...
library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java
View file @
7e7a33a8
...
@@ -16,6 +16,7 @@
...
@@ -16,6 +16,7 @@
package
com
.
google
.
android
.
exoplayer2
.
ui
;
package
com
.
google
.
android
.
exoplayer2
.
ui
;
import
android.app.Notification
;
import
android.app.Notification
;
import
android.app.NotificationChannel
;
import
android.app.PendingIntent
;
import
android.app.PendingIntent
;
import
android.content.BroadcastReceiver
;
import
android.content.BroadcastReceiver
;
import
android.content.Context
;
import
android.content.Context
;
...
@@ -877,6 +878,10 @@ public class PlayerNotificationManager {
...
@@ -877,6 +878,10 @@ public class PlayerNotificationManager {
*
*
* <p>See {@link NotificationCompat.Builder#setPriority(int)}.
* <p>See {@link NotificationCompat.Builder#setPriority(int)}.
*
*
* <p>To set the priority for API levels above 25, you can create your own {@link
* NotificationChannel} with a given importance level and pass the id of the channel to the {@link
* #PlayerNotificationManager(Context, String, int, MediaDescriptionAdapter) constructor}.
*
* @param priority The priority which can be one of {@link NotificationCompat#PRIORITY_DEFAULT},
* @param priority The priority which can be one of {@link NotificationCompat#PRIORITY_DEFAULT},
* {@link NotificationCompat#PRIORITY_MAX}, {@link NotificationCompat#PRIORITY_HIGH}, {@link
* {@link NotificationCompat#PRIORITY_MAX}, {@link NotificationCompat#PRIORITY_HIGH}, {@link
* NotificationCompat#PRIORITY_LOW} or {@link NotificationCompat#PRIORITY_MIN}. If not set
* NotificationCompat#PRIORITY_LOW} or {@link NotificationCompat#PRIORITY_MIN}. If not set
...
...
library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java
View file @
7e7a33a8
...
@@ -45,6 +45,7 @@ import com.google.android.exoplayer2.Format;
...
@@ -45,6 +45,7 @@ import com.google.android.exoplayer2.Format;
import
com.google.android.exoplayer2.PlaybackParameters
;
import
com.google.android.exoplayer2.PlaybackParameters
;
import
com.google.android.exoplayer2.PlaybackPreparer
;
import
com.google.android.exoplayer2.PlaybackPreparer
;
import
com.google.android.exoplayer2.Player
;
import
com.google.android.exoplayer2.Player
;
import
com.google.android.exoplayer2.Player.State
;
import
com.google.android.exoplayer2.RendererCapabilities
;
import
com.google.android.exoplayer2.RendererCapabilities
;
import
com.google.android.exoplayer2.Timeline
;
import
com.google.android.exoplayer2.Timeline
;
import
com.google.android.exoplayer2.source.TrackGroup
;
import
com.google.android.exoplayer2.source.TrackGroup
;
...
@@ -396,7 +397,7 @@ public class StyledPlayerControlView extends FrameLayout {
...
@@ -396,7 +397,7 @@ public class StyledPlayerControlView extends FrameLayout {
private
final
String
fullScreenEnterContentDescription
;
private
final
String
fullScreenEnterContentDescription
;
@Nullable
private
Player
player
;
@Nullable
private
Player
player
;
private
com
.
google
.
android
.
exoplayer2
.
ControlDispatcher
controlDispatcher
;
private
ControlDispatcher
controlDispatcher
;
@Nullable
private
ProgressUpdateListener
progressUpdateListener
;
@Nullable
private
ProgressUpdateListener
progressUpdateListener
;
@Nullable
private
PlaybackPreparer
playbackPreparer
;
@Nullable
private
PlaybackPreparer
playbackPreparer
;
...
@@ -537,8 +538,7 @@ public class StyledPlayerControlView extends FrameLayout {
...
@@ -537,8 +538,7 @@ public class StyledPlayerControlView extends FrameLayout {
extraAdGroupTimesMs
=
new
long
[
0
];
extraAdGroupTimesMs
=
new
long
[
0
];
extraPlayedAdGroups
=
new
boolean
[
0
];
extraPlayedAdGroups
=
new
boolean
[
0
];
componentListener
=
new
ComponentListener
();
componentListener
=
new
ComponentListener
();
controlDispatcher
=
controlDispatcher
=
new
DefaultControlDispatcher
(
fastForwardMs
,
rewindMs
);
new
com
.
google
.
android
.
exoplayer2
.
DefaultControlDispatcher
(
fastForwardMs
,
rewindMs
);
updateProgressAction
=
this
::
updateProgress
;
updateProgressAction
=
this
::
updateProgress
;
LayoutInflater
.
from
(
context
).
inflate
(
controllerLayoutId
,
/* root= */
this
);
LayoutInflater
.
from
(
context
).
inflate
(
controllerLayoutId
,
/* root= */
this
);
...
@@ -635,6 +635,7 @@ public class StyledPlayerControlView extends FrameLayout {
...
@@ -635,6 +635,7 @@ public class StyledPlayerControlView extends FrameLayout {
vrButton
=
findViewById
(
R
.
id
.
exo_vr
);
vrButton
=
findViewById
(
R
.
id
.
exo_vr
);
if
(
vrButton
!=
null
)
{
if
(
vrButton
!=
null
)
{
setShowVrButton
(
showVrButton
);
setShowVrButton
(
showVrButton
);
updateButton
(
/* enabled= */
false
,
vrButton
);
}
}
// Related to Settings List View
// Related to Settings List View
...
@@ -839,9 +840,9 @@ public class StyledPlayerControlView extends FrameLayout {
...
@@ -839,9 +840,9 @@ public class StyledPlayerControlView extends FrameLayout {
}
}
/**
/**
* Sets the {@link
com.google.android.exoplayer2.
ControlDispatcher}.
* Sets the {@link ControlDispatcher}.
*
*
* @param controlDispatcher The {@link
com.google.android.exoplayer2.
ControlDispatcher}.
* @param controlDispatcher The {@link ControlDispatcher}.
*/
*/
public
void
setControlDispatcher
(
ControlDispatcher
controlDispatcher
)
{
public
void
setControlDispatcher
(
ControlDispatcher
controlDispatcher
)
{
if
(
this
.
controlDispatcher
!=
controlDispatcher
)
{
if
(
this
.
controlDispatcher
!=
controlDispatcher
)
{
...
@@ -1638,19 +1639,22 @@ public class StyledPlayerControlView extends FrameLayout {
...
@@ -1638,19 +1639,22 @@ public class StyledPlayerControlView extends FrameLayout {
}
}
if
(
event
.
getAction
()
==
KeyEvent
.
ACTION_DOWN
)
{
if
(
event
.
getAction
()
==
KeyEvent
.
ACTION_DOWN
)
{
if
(
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_FAST_FORWARD
)
{
if
(
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_FAST_FORWARD
)
{
controlDispatcher
.
dispatchFastForward
(
player
);
if
(
player
.
getPlaybackState
()
!=
Player
.
STATE_ENDED
)
{
controlDispatcher
.
dispatchFastForward
(
player
);
}
}
else
if
(
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_REWIND
)
{
}
else
if
(
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_REWIND
)
{
controlDispatcher
.
dispatchRewind
(
player
);
controlDispatcher
.
dispatchRewind
(
player
);
}
else
if
(
event
.
getRepeatCount
()
==
0
)
{
}
else
if
(
event
.
getRepeatCount
()
==
0
)
{
switch
(
keyCode
)
{
switch
(
keyCode
)
{
case
KeyEvent
.
KEYCODE_MEDIA_PLAY_PAUSE
:
case
KeyEvent
.
KEYCODE_MEDIA_PLAY_PAUSE
:
controlDispatcher
.
dispatchSetPlayWhenReady
(
player
,
!
player
.
getPlayWhenReady
());
case
KeyEvent
.
KEYCODE_HEADSETHOOK
:
dispatchPlayPause
(
player
);
break
;
break
;
case
KeyEvent
.
KEYCODE_MEDIA_PLAY
:
case
KeyEvent
.
KEYCODE_MEDIA_PLAY
:
controlDispatcher
.
dispatchSetPlayWhenReady
(
player
,
true
);
dispatchPlay
(
player
);
break
;
break
;
case
KeyEvent
.
KEYCODE_MEDIA_PAUSE
:
case
KeyEvent
.
KEYCODE_MEDIA_PAUSE
:
controlDispatcher
.
dispatchSetPlayWhenReady
(
player
,
false
);
dispatchPause
(
player
);
break
;
break
;
case
KeyEvent
.
KEYCODE_MEDIA_NEXT
:
case
KeyEvent
.
KEYCODE_MEDIA_NEXT
:
controlDispatcher
.
dispatchNext
(
player
);
controlDispatcher
.
dispatchNext
(
player
);
...
@@ -1673,11 +1677,37 @@ public class StyledPlayerControlView extends FrameLayout {
...
@@ -1673,11 +1677,37 @@ public class StyledPlayerControlView extends FrameLayout {
&&
player
.
getPlayWhenReady
();
&&
player
.
getPlayWhenReady
();
}
}
private
void
dispatchPlayPause
(
Player
player
)
{
@State
int
state
=
player
.
getPlaybackState
();
if
(
state
==
Player
.
STATE_IDLE
||
state
==
Player
.
STATE_ENDED
||
!
player
.
getPlayWhenReady
())
{
dispatchPlay
(
player
);
}
else
{
dispatchPause
(
player
);
}
}
private
void
dispatchPlay
(
Player
player
)
{
@State
int
state
=
player
.
getPlaybackState
();
if
(
state
==
Player
.
STATE_IDLE
)
{
if
(
playbackPreparer
!=
null
)
{
playbackPreparer
.
preparePlayback
();
}
}
else
if
(
state
==
Player
.
STATE_ENDED
)
{
seekTo
(
player
,
player
.
getCurrentWindowIndex
(),
C
.
TIME_UNSET
);
}
controlDispatcher
.
dispatchSetPlayWhenReady
(
player
,
/* playWhenReady= */
true
);
}
private
void
dispatchPause
(
Player
player
)
{
controlDispatcher
.
dispatchSetPlayWhenReady
(
player
,
/* playWhenReady= */
false
);
}
@SuppressLint
(
"InlinedApi"
)
@SuppressLint
(
"InlinedApi"
)
private
static
boolean
isHandledMediaKey
(
int
keyCode
)
{
private
static
boolean
isHandledMediaKey
(
int
keyCode
)
{
return
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_FAST_FORWARD
return
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_FAST_FORWARD
||
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_REWIND
||
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_REWIND
||
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_PLAY_PAUSE
||
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_PLAY_PAUSE
||
keyCode
==
KeyEvent
.
KEYCODE_HEADSETHOOK
||
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_PLAY
||
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_PLAY
||
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_PAUSE
||
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_PAUSE
||
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_NEXT
||
keyCode
==
KeyEvent
.
KEYCODE_MEDIA_NEXT
...
@@ -1806,18 +1836,13 @@ public class StyledPlayerControlView extends FrameLayout {
...
@@ -1806,18 +1836,13 @@ public class StyledPlayerControlView extends FrameLayout {
}
else
if
(
previousButton
==
view
)
{
}
else
if
(
previousButton
==
view
)
{
controlDispatcher
.
dispatchPrevious
(
player
);
controlDispatcher
.
dispatchPrevious
(
player
);
}
else
if
(
fastForwardButton
==
view
)
{
}
else
if
(
fastForwardButton
==
view
)
{
controlDispatcher
.
dispatchFastForward
(
player
);
if
(
player
.
getPlaybackState
()
!=
Player
.
STATE_ENDED
)
{
controlDispatcher
.
dispatchFastForward
(
player
);
}
}
else
if
(
rewindButton
==
view
)
{
}
else
if
(
rewindButton
==
view
)
{
controlDispatcher
.
dispatchRewind
(
player
);
controlDispatcher
.
dispatchRewind
(
player
);
}
else
if
(
playPauseButton
==
view
)
{
}
else
if
(
playPauseButton
==
view
)
{
if
(
player
.
getPlaybackState
()
==
Player
.
STATE_IDLE
)
{
dispatchPlayPause
(
player
);
if
(
playbackPreparer
!=
null
)
{
playbackPreparer
.
preparePlayback
();
}
}
else
if
(
player
.
getPlaybackState
()
==
Player
.
STATE_ENDED
)
{
seekTo
(
player
,
player
.
getCurrentWindowIndex
(),
C
.
TIME_UNSET
);
}
controlDispatcher
.
dispatchSetPlayWhenReady
(
player
,
!
player
.
getPlayWhenReady
());
}
else
if
(
repeatToggleButton
==
view
)
{
}
else
if
(
repeatToggleButton
==
view
)
{
controlDispatcher
.
dispatchSetRepeatMode
(
controlDispatcher
.
dispatchSetRepeatMode
(
player
,
RepeatModeUtil
.
getNextRepeatMode
(
player
.
getRepeatMode
(),
repeatToggleModes
));
player
,
RepeatModeUtil
.
getNextRepeatMode
(
player
.
getRepeatMode
(),
repeatToggleModes
));
...
...
library/ui/src/main/res/layout/exo_styled_settings_list_item.xml
View file @
7e7a33a8
...
@@ -35,6 +35,8 @@
...
@@ -35,6 +35,8 @@
android:minHeight=
"@dimen/exo_settings_height"
android:minHeight=
"@dimen/exo_settings_height"
android:layout_marginLeft=
"2dp"
android:layout_marginLeft=
"2dp"
android:layout_marginRight=
"2dp"
android:layout_marginRight=
"2dp"
android:paddingEnd=
"4dp"
android:paddingRight=
"4dp"
android:gravity=
"center|start"
android:gravity=
"center|start"
android:orientation=
"vertical"
>
android:orientation=
"vertical"
>
...
...
library/ui/src/main/res/layout/exo_styled_sub_settings_list_item.xml
View file @
7e7a33a8
...
@@ -36,8 +36,8 @@
...
@@ -36,8 +36,8 @@
android:layout_width=
"wrap_content"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_height=
"wrap_content"
android:minHeight=
"@dimen/exo_settings_height"
android:minHeight=
"@dimen/exo_settings_height"
android:
paddingStart=
"2
dp"
android:
layout_marginEnd=
"4
dp"
android:
paddingLeft=
"2
dp"
android:
layout_marginRight=
"4
dp"
android:gravity=
"center|start"
android:gravity=
"center|start"
android:textColor=
"@color/exo_white"
android:textColor=
"@color/exo_white"
android:textSize=
"@dimen/exo_settings_main_text_size"
/>
android:textSize=
"@dimen/exo_settings_main_text_size"
/>
...
...
playbacktests/build.gradle
View file @
7e7a33a8
...
@@ -13,6 +13,12 @@
...
@@ -13,6 +13,12 @@
// limitations under the License.
// limitations under the License.
apply
from:
"$gradle.ext.exoplayerSettingsDir/common_library_config.gradle"
apply
from:
"$gradle.ext.exoplayerSettingsDir/common_library_config.gradle"
android
{
defaultConfig
{
multiDexEnabled
true
}
}
dependencies
{
dependencies
{
androidTestImplementation
'androidx.test:rules:'
+
androidxTestRulesVersion
androidTestImplementation
'androidx.test:rules:'
+
androidxTestRulesVersion
androidTestImplementation
'androidx.test:runner:'
+
androidxTestRunnerVersion
androidTestImplementation
'androidx.test:runner:'
+
androidxTestRunnerVersion
...
...
playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashWidevineOfflineTest.java
View file @
7e7a33a8
...
@@ -17,11 +17,12 @@ package com.google.android.exoplayer2.playbacktests.gts;
...
@@ -17,11 +17,12 @@ package com.google.android.exoplayer2.playbacktests.gts;
import
static
com
.
google
.
common
.
truth
.
Truth
.
assertThat
;
import
static
com
.
google
.
common
.
truth
.
Truth
.
assertThat
;
import
static
com
.
google
.
common
.
truth
.
Truth
.
assertWithMessage
;
import
static
com
.
google
.
common
.
truth
.
Truth
.
assertWithMessage
;
import
static
org
.
junit
.
Assert
.
assertThrows
;
import
static
org
.
junit
.
Assert
.
fail
;
import
android.media.MediaDrm.MediaDrmStateException
;
import
android.media.MediaDrm.MediaDrmStateException
;
import
android.net.Uri
;
import
android.net.Uri
;
import
android.util.Pair
;
import
android.util.Pair
;
import
androidx.annotation.Nullable
;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
androidx.test.rule.ActivityTestRule
;
import
androidx.test.rule.ActivityTestRule
;
import
com.google.android.exoplayer2.Format
;
import
com.google.android.exoplayer2.Format
;
...
@@ -121,19 +122,19 @@ public final class DashWidevineOfflineTest {
...
@@ -121,19 +122,19 @@ public final class DashWidevineOfflineTest {
downloadLicense
();
downloadLicense
();
releaseLicense
();
// keySetId no longer valid.
releaseLicense
();
// keySetId no longer valid.
Throwable
error
=
try
{
assertThrows
(
testRunner
.
run
();
"Playback should fail because the license has been released."
,
fail
(
"Playback should fail because the license has been released."
);
Throwable
.
class
,
}
catch
(
RuntimeException
expected
)
{
()
->
testRunner
.
run
());
// Get the root cause
Throwable
error
=
expected
;
// Get the root cause
@Nullable
Throwable
cause
=
error
.
getCause
();
Throwable
cause
=
error
.
getCause
();
while
(
cause
!=
null
&&
cause
!=
error
)
{
while
(
cause
!=
null
&&
cause
!=
error
)
{
error
=
cause
;
error
=
cause
;
cause
=
error
.
getCause
();
cause
=
error
.
getCause
();
}
assertThat
(
error
).
isInstanceOf
(
MediaDrmStateException
.
class
);
}
}
assertThat
(
error
).
isInstanceOf
(
MediaDrmStateException
.
class
);
}
}
@Test
@Test
...
@@ -144,18 +145,19 @@ public final class DashWidevineOfflineTest {
...
@@ -144,18 +145,19 @@ public final class DashWidevineOfflineTest {
downloadLicense
();
downloadLicense
();
releaseLicense
();
// keySetId no longer valid.
releaseLicense
();
// keySetId no longer valid.
Throwable
error
=
try
{
assertThrows
(
testRunner
.
run
();
"Playback should fail because the license has been released."
,
fail
(
"Playback should fail because the license has been released."
);
Throwable
.
class
,
}
catch
(
RuntimeException
expected
)
{
()
->
testRunner
.
run
());
// Get the root cause
// Get the root cause
Throwable
error
=
expected
;
Throwable
cause
=
error
.
getCause
();
@Nullable
Throwable
cause
=
error
.
getCause
();
while
(
cause
!=
null
&&
cause
!=
error
)
{
while
(
cause
!=
null
&&
cause
!=
error
)
{
error
=
cause
;
error
=
cause
;
cause
=
error
.
getCause
();
cause
=
error
.
getCause
();
}
assertThat
(
error
).
isInstanceOf
(
IllegalArgumentException
.
class
);
}
}
assertThat
(
error
).
isInstanceOf
(
IllegalArgumentException
.
class
);
}
}
@Test
@Test
...
...
playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DebugRenderersFactory.java
View file @
7e7a33a8
...
@@ -34,7 +34,6 @@ import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
...
@@ -34,7 +34,6 @@ import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import
com.google.android.exoplayer2.mediacodec.MediaCodecAdapter
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecAdapter
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecInfo
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecInfo
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecSelector
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecSelector
;
import
com.google.android.exoplayer2.util.Util
;
import
com.google.android.exoplayer2.video.MediaCodecVideoRenderer
;
import
com.google.android.exoplayer2.video.MediaCodecVideoRenderer
;
import
com.google.android.exoplayer2.video.VideoRendererEventListener
;
import
com.google.android.exoplayer2.video.VideoRendererEventListener
;
import
java.nio.ByteBuffer
;
import
java.nio.ByteBuffer
;
...
@@ -85,9 +84,9 @@ import java.util.ArrayList;
...
@@ -85,9 +84,9 @@ import java.util.ArrayList;
private
final
long
[]
timestampsList
;
private
final
long
[]
timestampsList
;
private
final
ArrayDeque
<
Long
>
inputFormatChangeTimesUs
;
private
final
ArrayDeque
<
Long
>
inputFormatChangeTimesUs
;
private
final
boolean
shouldMediaFormatChangeTimesBeChecked
;
private
boolean
skipToPositionBeforeRenderingFirstFrame
;
private
boolean
skipToPositionBeforeRenderingFirstFrame
;
private
boolean
shouldMediaFormatChangeTimesBeChecked
;
private
int
startIndex
;
private
int
startIndex
;
private
int
queueSize
;
private
int
queueSize
;
...
@@ -114,6 +113,16 @@ import java.util.ArrayList;
...
@@ -114,6 +113,16 @@ import java.util.ArrayList;
maxDroppedFrameCountToNotify
);
maxDroppedFrameCountToNotify
);
timestampsList
=
new
long
[
ARRAY_SIZE
];
timestampsList
=
new
long
[
ARRAY_SIZE
];
inputFormatChangeTimesUs
=
new
ArrayDeque
<>();
inputFormatChangeTimesUs
=
new
ArrayDeque
<>();
/*
// Output MediaFormat changes are known to occur too early until API 30 (see [internal:
// b/149818050, b/149751672]).
shouldMediaFormatChangeTimesBeChecked = Util.SDK_INT > 30;
*/
// [Internal ref: b/149751672] Seeking currently causes an unexpected MediaFormat change, so
// this check is disabled until that is deemed fixed.
shouldMediaFormatChangeTimesBeChecked
=
false
;
}
}
@Override
@Override
...
@@ -135,10 +144,6 @@ import java.util.ArrayList;
...
@@ -135,10 +144,6 @@ import java.util.ArrayList;
// frames up to the current playback position [Internal: b/66494991].
// frames up to the current playback position [Internal: b/66494991].
skipToPositionBeforeRenderingFirstFrame
=
getState
()
==
Renderer
.
STATE_STARTED
;
skipToPositionBeforeRenderingFirstFrame
=
getState
()
==
Renderer
.
STATE_STARTED
;
super
.
configureCodec
(
codecInfo
,
codecAdapter
,
format
,
crypto
,
operatingRate
);
super
.
configureCodec
(
codecInfo
,
codecAdapter
,
format
,
crypto
,
operatingRate
);
// Output MediaFormat changes are known to occur too early until API 30 (see [internal:
// b/149818050, b/149751672]).
shouldMediaFormatChangeTimesBeChecked
=
Util
.
SDK_INT
>
30
;
}
}
@Override
@Override
...
@@ -186,6 +191,8 @@ import java.util.ArrayList;
...
@@ -186,6 +191,8 @@ import java.util.ArrayList;
if
(
mediaFormat
!=
null
&&
!
mediaFormat
.
equals
(
currentMediaFormat
))
{
if
(
mediaFormat
!=
null
&&
!
mediaFormat
.
equals
(
currentMediaFormat
))
{
outputMediaFormatChanged
=
true
;
outputMediaFormatChanged
=
true
;
currentMediaFormat
=
mediaFormat
;
currentMediaFormat
=
mediaFormat
;
}
else
{
inputFormatChangeTimesUs
.
remove
();
}
}
}
}
...
...
testutils/src/main/java/com/google/android/exoplayer2/testutil/TestExoPlayer.java
View file @
7e7a33a8
...
@@ -20,6 +20,7 @@ import static com.google.android.exoplayer2.testutil.TestUtil.runMainLooperUntil
...
@@ -20,6 +20,7 @@ import static com.google.android.exoplayer2.testutil.TestUtil.runMainLooperUntil
import
static
com
.
google
.
common
.
truth
.
Truth
.
assertThat
;
import
static
com
.
google
.
common
.
truth
.
Truth
.
assertThat
;
import
android.content.Context
;
import
android.content.Context
;
import
android.os.Handler
;
import
android.os.Looper
;
import
android.os.Looper
;
import
androidx.annotation.Nullable
;
import
androidx.annotation.Nullable
;
import
com.google.android.exoplayer2.DefaultLoadControl
;
import
com.google.android.exoplayer2.DefaultLoadControl
;
...
@@ -37,6 +38,7 @@ import com.google.android.exoplayer2.upstream.BandwidthMeter;
...
@@ -37,6 +38,7 @@ import com.google.android.exoplayer2.upstream.BandwidthMeter;
import
com.google.android.exoplayer2.upstream.DefaultBandwidthMeter
;
import
com.google.android.exoplayer2.upstream.DefaultBandwidthMeter
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.Clock
;
import
com.google.android.exoplayer2.util.Clock
;
import
com.google.android.exoplayer2.util.ConditionVariable
;
import
com.google.android.exoplayer2.util.Util
;
import
com.google.android.exoplayer2.util.Util
;
import
com.google.android.exoplayer2.video.VideoListener
;
import
com.google.android.exoplayer2.video.VideoListener
;
import
java.util.concurrent.TimeoutException
;
import
java.util.concurrent.TimeoutException
;
...
@@ -396,6 +398,7 @@ public class TestExoPlayer {
...
@@ -396,6 +398,7 @@ public class TestExoPlayer {
*/
*/
public
static
void
runUntilPositionDiscontinuity
(
public
static
void
runUntilPositionDiscontinuity
(
Player
player
,
@Player
.
DiscontinuityReason
int
expectedReason
)
throws
TimeoutException
{
Player
player
,
@Player
.
DiscontinuityReason
int
expectedReason
)
throws
TimeoutException
{
verifyMainTestThread
(
player
);
AtomicBoolean
receivedCallback
=
new
AtomicBoolean
(
false
);
AtomicBoolean
receivedCallback
=
new
AtomicBoolean
(
false
);
Player
.
EventListener
listener
=
Player
.
EventListener
listener
=
new
Player
.
EventListener
()
{
new
Player
.
EventListener
()
{
...
@@ -459,6 +462,59 @@ public class TestExoPlayer {
...
@@ -459,6 +462,59 @@ public class TestExoPlayer {
}
}
/**
/**
* Calls {@link Player#play()}, runs tasks of the main {@link Looper} until the {@code player}
* reaches the specified position and then pauses the {@code player}.
*
* @param player The {@link Player}.
* @param windowIndex The window.
* @param positionMs The position within the window, in milliseconds.
* @throws TimeoutException If the {@link TestUtil#DEFAULT_TIMEOUT_MS default timeout} is
* exceeded.
*/
public
static
void
playUntilPosition
(
ExoPlayer
player
,
int
windowIndex
,
long
positionMs
)
throws
TimeoutException
{
verifyMainTestThread
(
player
);
Handler
testHandler
=
Util
.
createHandlerForCurrentOrMainLooper
();
AtomicBoolean
messageHandled
=
new
AtomicBoolean
();
player
.
createMessage
(
(
messageType
,
payload
)
->
{
// Block playback thread until pause command has been sent from test thread.
ConditionVariable
blockPlaybackThreadCondition
=
new
ConditionVariable
();
testHandler
.
post
(
()
->
{
player
.
pause
();
messageHandled
.
set
(
true
);
blockPlaybackThreadCondition
.
open
();
});
try
{
blockPlaybackThreadCondition
.
block
();
}
catch
(
InterruptedException
e
)
{
// Ignore.
}
})
.
setPosition
(
windowIndex
,
positionMs
)
.
send
();
player
.
play
();
runMainLooperUntil
(
messageHandled:
:
get
);
}
/**
* Calls {@link Player#play()}, runs tasks of the main {@link Looper} until the {@code player}
* reaches the specified window and then pauses the {@code player}.
*
* @param player The {@link Player}.
* @param windowIndex The window.
* @throws TimeoutException If the {@link TestUtil#DEFAULT_TIMEOUT_MS default timeout} is
* exceeded.
*/
public
static
void
playUntilStartOfWindow
(
ExoPlayer
player
,
int
windowIndex
)
throws
TimeoutException
{
playUntilPosition
(
player
,
windowIndex
,
/* positionMs= */
0
);
}
/**
* Runs tasks of the main {@link Looper} until the player completely handled all previously issued
* Runs tasks of the main {@link Looper} until the player completely handled all previously issued
* commands on the internal playback thread.
* commands on the internal playback thread.
*
*
...
...
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