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
b853978a
authored
Aug 18, 2020
by
insun
Committed by
kim-vde
Aug 18, 2020
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Fix bug to show 'play' button at the end of stream
PiperOrigin-RevId: 327158791
parent
103bb98d
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
316 additions
and
1 deletions
library/core/src/main/java/com/google/android/exoplayer2/MetadataRetriever.java
library/core/src/test/java/com/google/android/exoplayer2/MetadataRetrieverTest.java
library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java
library/core/src/main/java/com/google/android/exoplayer2/MetadataRetriever.java
0 → 100644
View file @
b853978a
/*
* Copyright 2020 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
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkNotNull
;
import
android.content.Context
;
import
android.os.Handler
;
import
android.os.HandlerThread
;
import
android.os.Message
;
import
com.google.android.exoplayer2.source.DefaultMediaSourceFactory
;
import
com.google.android.exoplayer2.source.MediaPeriod
;
import
com.google.android.exoplayer2.source.MediaSource
;
import
com.google.android.exoplayer2.source.MediaSourceFactory
;
import
com.google.android.exoplayer2.source.TrackGroupArray
;
import
com.google.android.exoplayer2.upstream.Allocator
;
import
com.google.android.exoplayer2.upstream.DefaultAllocator
;
import
com.google.android.exoplayer2.util.Util
;
import
com.google.common.util.concurrent.ListenableFuture
;
import
com.google.common.util.concurrent.SettableFuture
;
import
org.checkerframework.checker.nullness.qual.MonotonicNonNull
;
// TODO(internal b/161127201): discard samples written to the sample queue.
/** Retrieves the static metadata of {@link MediaItem MediaItems}. */
public
final
class
MetadataRetriever
{
private
MetadataRetriever
()
{}
/**
* Retrieves the {@link TrackGroupArray} corresponding to a {@link MediaItem}.
*
* <p>This is equivalent to using {@code
* retrieveMetadata(DefaultMediaSourceFactory.newInstance(context), mediaItem)}.
*
* @param context The {@link Context}.
* @param mediaItem The {@link MediaItem} whose metadata should be retrieved.
* @return A {@link ListenableFuture} of the result.
*/
public
static
ListenableFuture
<
TrackGroupArray
>
retrieveMetadata
(
Context
context
,
MediaItem
mediaItem
)
{
return
retrieveMetadata
(
DefaultMediaSourceFactory
.
newInstance
(
context
),
mediaItem
);
}
/**
* Retrieves the {@link TrackGroupArray} corresponding to a {@link MediaItem}.
*
* <p>This method is thread-safe.
*
* @param mediaSourceFactory mediaSourceFactory The {@link MediaSourceFactory} to use to read the
* data.
* @param mediaItem The {@link MediaItem} whose metadata should be retrieved.
* @return A {@link ListenableFuture} of the result.
*/
public
static
ListenableFuture
<
TrackGroupArray
>
retrieveMetadata
(
MediaSourceFactory
mediaSourceFactory
,
MediaItem
mediaItem
)
{
// Recreate thread and handler every time this method is called so that it can be used
// concurrently.
return
new
MetadataRetrieverInternal
(
mediaSourceFactory
).
retrieveMetadata
(
mediaItem
);
}
private
static
final
class
MetadataRetrieverInternal
{
private
static
final
int
MESSAGE_PREPARE_SOURCE
=
0
;
private
static
final
int
MESSAGE_CHECK_FOR_FAILURE
=
1
;
private
static
final
int
MESSAGE_CONTINUE_LOADING
=
2
;
private
static
final
int
MESSAGE_RELEASE
=
3
;
private
final
MediaSourceFactory
mediaSourceFactory
;
private
final
HandlerThread
mediaSourceThread
;
private
final
Handler
mediaSourceHandler
;
private
final
SettableFuture
<
TrackGroupArray
>
trackGroupsFuture
;
public
MetadataRetrieverInternal
(
MediaSourceFactory
mediaSourceFactory
)
{
this
.
mediaSourceFactory
=
mediaSourceFactory
;
mediaSourceThread
=
new
HandlerThread
(
"ExoPlayer:MetadataRetriever"
);
mediaSourceThread
.
start
();
mediaSourceHandler
=
Util
.
createHandler
(
mediaSourceThread
.
getLooper
(),
new
MediaSourceHandlerCallback
());
trackGroupsFuture
=
SettableFuture
.
create
();
}
public
ListenableFuture
<
TrackGroupArray
>
retrieveMetadata
(
MediaItem
mediaItem
)
{
mediaSourceHandler
.
obtainMessage
(
MESSAGE_PREPARE_SOURCE
,
mediaItem
).
sendToTarget
();
return
trackGroupsFuture
;
}
private
final
class
MediaSourceHandlerCallback
implements
Handler
.
Callback
{
private
static
final
int
ERROR_POLL_INTERVAL_MS
=
100
;
private
final
MediaSourceCaller
mediaSourceCaller
;
private
@MonotonicNonNull
MediaSource
mediaSource
;
private
@MonotonicNonNull
MediaPeriod
mediaPeriod
;
public
MediaSourceHandlerCallback
()
{
mediaSourceCaller
=
new
MediaSourceCaller
();
}
@Override
public
boolean
handleMessage
(
Message
msg
)
{
switch
(
msg
.
what
)
{
case
MESSAGE_PREPARE_SOURCE:
MediaItem
mediaItem
=
(
MediaItem
)
msg
.
obj
;
mediaSource
=
mediaSourceFactory
.
createMediaSource
(
mediaItem
);
mediaSource
.
prepareSource
(
mediaSourceCaller
,
/* mediaTransferListener= */
null
);
mediaSourceHandler
.
sendEmptyMessage
(
MESSAGE_CHECK_FOR_FAILURE
);
return
true
;
case
MESSAGE_CHECK_FOR_FAILURE:
try
{
if
(
mediaPeriod
==
null
)
{
checkNotNull
(
mediaSource
).
maybeThrowSourceInfoRefreshError
();
}
else
{
mediaPeriod
.
maybeThrowPrepareError
();
}
mediaSourceHandler
.
sendEmptyMessageDelayed
(
MESSAGE_CHECK_FOR_FAILURE
,
/* delayMillis= */
ERROR_POLL_INTERVAL_MS
);
}
catch
(
Exception
e
)
{
trackGroupsFuture
.
setException
(
e
);
mediaSourceHandler
.
obtainMessage
(
MESSAGE_RELEASE
).
sendToTarget
();
}
return
true
;
case
MESSAGE_CONTINUE_LOADING:
checkNotNull
(
mediaPeriod
).
continueLoading
(
/* positionUs= */
0
);
return
true
;
case
MESSAGE_RELEASE:
if
(
mediaPeriod
!=
null
)
{
checkNotNull
(
mediaSource
).
releasePeriod
(
mediaPeriod
);
}
checkNotNull
(
mediaSource
).
releaseSource
(
mediaSourceCaller
);
mediaSourceHandler
.
removeCallbacksAndMessages
(
/* token= */
null
);
mediaSourceThread
.
quit
();
return
true
;
default
:
return
false
;
}
}
private
final
class
MediaSourceCaller
implements
MediaSource
.
MediaSourceCaller
{
private
final
MediaPeriodCallback
mediaPeriodCallback
;
private
final
Allocator
allocator
;
private
boolean
mediaPeriodCreated
;
public
MediaSourceCaller
()
{
mediaPeriodCallback
=
new
MediaPeriodCallback
();
allocator
=
new
DefaultAllocator
(
/* trimOnReset= */
true
,
/* individualAllocationSize= */
C
.
DEFAULT_BUFFER_SEGMENT_SIZE
);
}
@Override
public
void
onSourceInfoRefreshed
(
MediaSource
source
,
Timeline
timeline
)
{
if
(
mediaPeriodCreated
)
{
// Ignore dynamic updates.
return
;
}
mediaPeriodCreated
=
true
;
mediaPeriod
=
source
.
createPeriod
(
new
MediaSource
.
MediaPeriodId
(
timeline
.
getUidOfPeriod
(
/* periodIndex= */
0
)),
allocator
,
/* startPositionUs= */
0
);
mediaPeriod
.
prepare
(
mediaPeriodCallback
,
/* positionUs= */
0
);
}
private
final
class
MediaPeriodCallback
implements
MediaPeriod
.
Callback
{
@Override
public
void
onPrepared
(
MediaPeriod
mediaPeriod
)
{
trackGroupsFuture
.
set
(
mediaPeriod
.
getTrackGroups
());
mediaSourceHandler
.
obtainMessage
(
MESSAGE_RELEASE
).
sendToTarget
();
}
@Override
public
void
onContinueLoadingRequested
(
MediaPeriod
mediaPeriod
)
{
mediaSourceHandler
.
obtainMessage
(
MESSAGE_CONTINUE_LOADING
).
sendToTarget
();
}
}
}
}
}
}
library/core/src/test/java/com/google/android/exoplayer2/MetadataRetrieverTest.java
0 → 100644
View file @
b853978a
/*
* Copyright 2020 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
;
import
static
com
.
google
.
android
.
exoplayer2
.
MetadataRetriever
.
retrieveMetadata
;
import
static
com
.
google
.
common
.
truth
.
Truth
.
assertThat
;
import
static
org
.
junit
.
Assert
.
assertThrows
;
import
android.content.Context
;
import
android.net.Uri
;
import
android.os.SystemClock
;
import
androidx.test.core.app.ApplicationProvider
;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
com.google.android.exoplayer2.source.TrackGroupArray
;
import
com.google.android.exoplayer2.util.MimeTypes
;
import
com.google.common.util.concurrent.ListenableFuture
;
import
java.util.concurrent.ExecutionException
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
import
org.robolectric.annotation.LooperMode
;
/** Tests for {@link MetadataRetriever}. */
@RunWith
(
AndroidJUnit4
.
class
)
@LooperMode
(
LooperMode
.
Mode
.
PAUSED
)
public
class
MetadataRetrieverTest
{
@Test
public
void
retrieveMetadata_singleMediaItem
()
throws
Exception
{
Context
context
=
ApplicationProvider
.
getApplicationContext
();
MediaItem
mediaItem
=
MediaItem
.
fromUri
(
Uri
.
parse
(
"asset://android_asset/media/mp4/sample.mp4"
));
ListenableFuture
<
TrackGroupArray
>
trackGroupsFuture
=
retrieveMetadata
(
context
,
mediaItem
);
TrackGroupArray
trackGroups
=
waitAndGetTrackGroups
(
trackGroupsFuture
);
assertThat
(
trackGroups
.
length
).
isEqualTo
(
2
);
// Video group.
assertThat
(
trackGroups
.
get
(
0
).
length
).
isEqualTo
(
1
);
assertThat
(
trackGroups
.
get
(
0
).
getFormat
(
0
).
sampleMimeType
).
isEqualTo
(
MimeTypes
.
VIDEO_H264
);
// Audio group.
assertThat
(
trackGroups
.
get
(
1
).
length
).
isEqualTo
(
1
);
assertThat
(
trackGroups
.
get
(
1
).
getFormat
(
0
).
sampleMimeType
).
isEqualTo
(
MimeTypes
.
AUDIO_AAC
);
}
@Test
public
void
retrieveMetadata_multipleMediaItems
()
throws
Exception
{
Context
context
=
ApplicationProvider
.
getApplicationContext
();
MediaItem
mediaItem1
=
MediaItem
.
fromUri
(
Uri
.
parse
(
"asset://android_asset/media/mp4/sample.mp4"
));
MediaItem
mediaItem2
=
MediaItem
.
fromUri
(
Uri
.
parse
(
"asset://android_asset/media/mp3/bear-id3.mp3"
));
ListenableFuture
<
TrackGroupArray
>
trackGroupsFuture1
=
retrieveMetadata
(
context
,
mediaItem1
);
ListenableFuture
<
TrackGroupArray
>
trackGroupsFuture2
=
retrieveMetadata
(
context
,
mediaItem2
);
TrackGroupArray
trackGroups1
=
waitAndGetTrackGroups
(
trackGroupsFuture1
);
TrackGroupArray
trackGroups2
=
waitAndGetTrackGroups
(
trackGroupsFuture2
);
// First track group.
assertThat
(
trackGroups1
.
length
).
isEqualTo
(
2
);
// First track group - Video group.
assertThat
(
trackGroups1
.
get
(
0
).
length
).
isEqualTo
(
1
);
assertThat
(
trackGroups1
.
get
(
0
).
getFormat
(
0
).
sampleMimeType
).
isEqualTo
(
MimeTypes
.
VIDEO_H264
);
// First track group - Audio group.
assertThat
(
trackGroups1
.
get
(
1
).
length
).
isEqualTo
(
1
);
assertThat
(
trackGroups1
.
get
(
1
).
getFormat
(
0
).
sampleMimeType
).
isEqualTo
(
MimeTypes
.
AUDIO_AAC
);
// Second track group.
assertThat
(
trackGroups2
.
length
).
isEqualTo
(
1
);
// Second track group - Audio group.
assertThat
(
trackGroups2
.
get
(
0
).
length
).
isEqualTo
(
1
);
assertThat
(
trackGroups2
.
get
(
0
).
getFormat
(
0
).
sampleMimeType
).
isEqualTo
(
MimeTypes
.
AUDIO_MPEG
);
}
@Test
public
void
retrieveMetadata_throwsErrorIfCannotLoad
()
{
Context
context
=
ApplicationProvider
.
getApplicationContext
();
MediaItem
mediaItem
=
MediaItem
.
fromUri
(
Uri
.
parse
(
"asset://android_asset/media/does_not_exist"
));
ListenableFuture
<
TrackGroupArray
>
trackGroupsFuture
=
retrieveMetadata
(
context
,
mediaItem
);
assertThrows
(
ExecutionException
.
class
,
()
->
waitAndGetTrackGroups
(
trackGroupsFuture
));
}
private
static
TrackGroupArray
waitAndGetTrackGroups
(
ListenableFuture
<
TrackGroupArray
>
trackGroupsFuture
)
throws
InterruptedException
,
ExecutionException
{
while
(!
trackGroupsFuture
.
isDone
())
{
// Simulate advancing SystemClock so that delayed messages sent to handlers are received.
SystemClock
.
setCurrentTimeMillis
(
SystemClock
.
uptimeMillis
()
+
100
);
Thread
.
sleep
(
/* millis= */
100
);
}
return
trackGroupsFuture
.
get
();
}
}
library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java
View file @
b853978a
...
@@ -1097,7 +1097,7 @@ public class StyledPlayerControlView extends FrameLayout {
...
@@ -1097,7 +1097,7 @@ public class StyledPlayerControlView extends FrameLayout {
return
;
return
;
}
}
if
(
playPauseButton
!=
null
)
{
if
(
playPauseButton
!=
null
)
{
if
(
player
!=
null
&&
player
.
getPlayWhenReady
())
{
if
(
shouldShowPauseButton
())
{
((
ImageView
)
playPauseButton
)
((
ImageView
)
playPauseButton
)
.
setImageDrawable
(
resources
.
getDrawable
(
R
.
drawable
.
exo_styled_controls_pause
));
.
setImageDrawable
(
resources
.
getDrawable
(
R
.
drawable
.
exo_styled_controls_pause
));
playPauseButton
.
setContentDescription
(
playPauseButton
.
setContentDescription
(
...
@@ -1665,6 +1665,13 @@ public class StyledPlayerControlView extends FrameLayout {
...
@@ -1665,6 +1665,13 @@ public class StyledPlayerControlView extends FrameLayout {
return
true
;
return
true
;
}
}
private
boolean
shouldShowPauseButton
()
{
return
player
!=
null
&&
player
.
getPlaybackState
()
!=
Player
.
STATE_ENDED
&&
player
.
getPlaybackState
()
!=
Player
.
STATE_IDLE
&&
player
.
getPlayWhenReady
();
}
@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
...
...
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