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
14f75c16
authored
Jun 29, 2022
by
rohks
Committed by
Rohit Singh
Jul 07, 2022
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Make `MetadataRenderer` configurable to output metadata early.
PiperOrigin-RevId: 457974611
parent
c74cf1f1
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
212 additions
and
8 deletions
library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java
library/core/src/test/java/com/google/android/exoplayer2/metadata/MetadataRendererTest.java
library/dash/src/test/java/com/google/android/exoplayer2/source/dash/e2etest/DashPlaybackTest.java
testdata/src/test/assets/playbackdumps/dash/metadata_from_early_output.dump
testdata/src/test/assets/playbackdumps/dash/metadata_from_timely_output.dump
library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java
View file @
14f75c16
...
...
@@ -35,7 +35,12 @@ import java.util.ArrayList;
import
java.util.List
;
import
org.checkerframework.dataflow.qual.SideEffectFree
;
/** A renderer for metadata. */
/**
* A renderer for metadata.
*
* <p>The renderer can be configured to render metadata as soon as they are available using {@link
* #MetadataRenderer(MetadataOutput, Looper, MetadataDecoderFactory, boolean)}.
*/
public
final
class
MetadataRenderer
extends
BaseRenderer
implements
Callback
{
private
static
final
String
TAG
=
"MetadataRenderer"
;
...
...
@@ -45,6 +50,7 @@ public final class MetadataRenderer extends BaseRenderer implements Callback {
private
final
MetadataOutput
output
;
@Nullable
private
final
Handler
outputHandler
;
private
final
MetadataInputBuffer
buffer
;
private
final
boolean
outputMetadataEarly
;
@Nullable
private
MetadataDecoder
decoder
;
private
boolean
inputStreamEnded
;
...
...
@@ -54,6 +60,9 @@ public final class MetadataRenderer extends BaseRenderer implements Callback {
private
long
outputStreamOffsetUs
;
/**
* Creates an instance that uses {@link MetadataDecoderFactory#DEFAULT} to create {@link
* MetadataDecoder} instances.
*
* @param output The output.
* @param outputLooper The looper associated with the thread on which the output should be called.
* If the output makes use of standard Android UI components, then this should normally be the
...
...
@@ -66,6 +75,8 @@ public final class MetadataRenderer extends BaseRenderer implements Callback {
}
/**
* Creates an instance.
*
* @param output The output.
* @param outputLooper The looper associated with the thread on which the output should be called.
* If the output makes use of standard Android UI components, then this should normally be the
...
...
@@ -76,11 +87,34 @@ public final class MetadataRenderer extends BaseRenderer implements Callback {
*/
public
MetadataRenderer
(
MetadataOutput
output
,
@Nullable
Looper
outputLooper
,
MetadataDecoderFactory
decoderFactory
)
{
this
(
output
,
outputLooper
,
decoderFactory
,
/* outputMetadataEarly= */
false
);
}
/**
* Creates an instance.
*
* @param output The output.
* @param outputLooper The looper associated with the thread on which the output should be called.
* If the output makes use of standard Android UI components, then this should normally be the
* looper associated with the application's main thread, which can be obtained using {@link
* android.app.Activity#getMainLooper()}. Null may be passed if the output should be called
* directly on the player's internal rendering thread.
* @param decoderFactory A factory from which to obtain {@link MetadataDecoder} instances.
* @param outputMetadataEarly Whether the renderer outputs metadata early. When {@code true},
* {@link #render} will output metadata as soon as they are available to the renderer,
* otherwise {@link #render} will output metadata in sync with the rendering position.
*/
public
MetadataRenderer
(
MetadataOutput
output
,
@Nullable
Looper
outputLooper
,
MetadataDecoderFactory
decoderFactory
,
boolean
outputMetadataEarly
)
{
super
(
C
.
TRACK_TYPE_METADATA
);
this
.
output
=
Assertions
.
checkNotNull
(
output
);
this
.
outputHandler
=
outputLooper
==
null
?
null
:
Util
.
createHandler
(
outputLooper
,
/* callback= */
this
);
this
.
decoderFactory
=
Assertions
.
checkNotNull
(
decoderFactory
);
this
.
outputMetadataEarly
=
outputMetadataEarly
;
buffer
=
new
MetadataInputBuffer
();
outputStreamOffsetUs
=
C
.
TIME_UNSET
;
}
...
...
@@ -212,7 +246,8 @@ public final class MetadataRenderer extends BaseRenderer implements Callback {
private
boolean
outputMetadata
(
long
positionUs
)
{
boolean
didOutput
=
false
;
if
(
pendingMetadata
!=
null
&&
pendingMetadata
.
presentationTimeUs
<=
getPresentationTimeUs
(
positionUs
))
{
&&
(
outputMetadataEarly
||
pendingMetadata
.
presentationTimeUs
<=
getPresentationTimeUs
(
positionUs
)))
{
invokeRenderer
(
pendingMetadata
);
pendingMetadata
=
null
;
didOutput
=
true
;
...
...
library/core/src/test/java/com/google/android/exoplayer2/metadata/MetadataRendererTest.java
View file @
14f75c16
...
...
@@ -144,16 +144,87 @@ public class MetadataRendererTest {
assertThat
(
metadata
).
isEmpty
();
}
@Test
public
void
renderMetadata_withTimelyOutput
()
throws
Exception
{
EventMessage
emsg
=
new
EventMessage
(
"urn:test-scheme-id"
,
/* value= */
""
,
/* durationMs= */
1
,
/* id= */
0
,
"Test data"
.
getBytes
(
UTF_8
));
byte
[]
encodedEmsg
=
eventMessageEncoder
.
encode
(
emsg
);
List
<
Metadata
>
metadata
=
new
ArrayList
<>();
MetadataRenderer
renderer
=
new
MetadataRenderer
(
/* output= */
metadata:
:
add
,
/* outputLooper= */
null
);
FakeSampleStream
fakeSampleStream
=
createFakeSampleStream
(
ImmutableList
.
of
(
sample
(
/* timeUs= */
100_000
,
C
.
BUFFER_FLAG_KEY_FRAME
,
encodedEmsg
),
sample
(
/* timeUs= */
1_000_000
,
C
.
BUFFER_FLAG_KEY_FRAME
,
encodedEmsg
),
END_OF_STREAM_ITEM
));
fakeSampleStream
.
writeData
(
/* startPositionUs= */
0
);
renderer
.
replaceStream
(
new
Format
[]
{
EMSG_FORMAT
},
fakeSampleStream
,
/* startPositionUs= */
0L
,
/* offsetUs= */
0L
);
// Call render() twice, the first call is to read the format and the second call will read the
// metadata.
renderer
.
render
(
/* positionUs= */
0
,
/* elapsedRealtimeUs= */
0
);
renderer
.
render
(
/* positionUs= */
500_000
,
/* elapsedRealtimeUs= */
0
);
assertThat
(
metadata
).
hasSize
(
1
);
assertThat
(
metadata
.
get
(
0
).
presentationTimeUs
).
isEqualTo
(
100_000
);
}
@Test
public
void
renderMetadata_withEarlyOutput
()
throws
Exception
{
EventMessage
emsg
=
new
EventMessage
(
"urn:test-scheme-id"
,
/* value= */
""
,
/* durationMs= */
1
,
/* id= */
0
,
"Test data"
.
getBytes
(
UTF_8
));
byte
[]
encodedEmsg
=
eventMessageEncoder
.
encode
(
emsg
);
List
<
Metadata
>
metadata
=
new
ArrayList
<>();
MetadataRenderer
renderer
=
new
MetadataRenderer
(
/* output= */
metadata:
:
add
,
/* outputLooper= */
null
,
MetadataDecoderFactory
.
DEFAULT
,
/* outputMetadataEarly= */
true
);
FakeSampleStream
fakeSampleStream
=
createFakeSampleStream
(
ImmutableList
.
of
(
sample
(
/* timeUs= */
100_000
,
C
.
BUFFER_FLAG_KEY_FRAME
,
encodedEmsg
),
sample
(
/* timeUs= */
1_000_000
,
C
.
BUFFER_FLAG_KEY_FRAME
,
encodedEmsg
),
END_OF_STREAM_ITEM
));
fakeSampleStream
.
writeData
(
/* startPositionUs= */
0
);
renderer
.
replaceStream
(
new
Format
[]
{
EMSG_FORMAT
},
fakeSampleStream
,
/* startPositionUs= */
0L
,
/* offsetUs= */
0L
);
// Call render() twice, the first call is to read the format and the second call will read the
// metadata.
renderer
.
render
(
/* positionUs= */
0
,
/* elapsedRealtimeUs= */
0
);
renderer
.
render
(
/* positionUs= */
500_000
,
/* elapsedRealtimeUs= */
0
);
// The renderer outputs metadata early.
assertThat
(
metadata
).
hasSize
(
2
);
assertThat
(
metadata
.
get
(
0
).
presentationTimeUs
).
isEqualTo
(
100_000
);
assertThat
(
metadata
.
get
(
1
).
presentationTimeUs
).
isEqualTo
(
1_000_000
);
}
private
static
List
<
Metadata
>
runRenderer
(
byte
[]
input
)
throws
ExoPlaybackException
{
List
<
Metadata
>
metadata
=
new
ArrayList
<>();
MetadataRenderer
renderer
=
new
MetadataRenderer
(
metadata:
:
add
,
/* outputLooper= */
null
);
FakeSampleStream
fakeSampleStream
=
new
FakeSampleStream
(
new
DefaultAllocator
(
/* trimOnReset= */
true
,
/* individualAllocationSize= */
1024
),
/* mediaSourceEventDispatcher= */
null
,
DrmSessionManager
.
DRM_UNSUPPORTED
,
new
DrmSessionEventListener
.
EventDispatcher
(),
EMSG_FORMAT
,
createFakeSampleStream
(
ImmutableList
.
of
(
sample
(
/* timeUs= */
0
,
C
.
BUFFER_FLAG_KEY_FRAME
,
input
),
END_OF_STREAM_ITEM
));
fakeSampleStream
.
writeData
(
/* startPositionUs= */
0
);
...
...
@@ -168,6 +239,17 @@ public class MetadataRendererTest {
return
Collections
.
unmodifiableList
(
metadata
);
}
private
static
FakeSampleStream
createFakeSampleStream
(
ImmutableList
<
FakeSampleStream
.
FakeSampleStreamItem
>
samples
)
{
return
new
FakeSampleStream
(
new
DefaultAllocator
(
/* trimOnReset= */
true
,
/* individualAllocationSize= */
1024
),
/* mediaSourceEventDispatcher= */
null
,
DrmSessionManager
.
DRM_UNSUPPORTED
,
new
DrmSessionEventListener
.
EventDispatcher
(),
EMSG_FORMAT
,
samples
);
}
/**
* Builds an ID3v2 tag containing a single 'user defined text information frame' (id='TXXX') with
* {@code description} and {@code value}.
...
...
library/dash/src/test/java/com/google/android/exoplayer2/source/dash/e2etest/DashPlaybackTest.java
View file @
14f75c16
...
...
@@ -25,6 +25,10 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import
com.google.android.exoplayer2.ExoPlayer
;
import
com.google.android.exoplayer2.MediaItem
;
import
com.google.android.exoplayer2.Player
;
import
com.google.android.exoplayer2.Renderer
;
import
com.google.android.exoplayer2.RenderersFactory
;
import
com.google.android.exoplayer2.metadata.MetadataDecoderFactory
;
import
com.google.android.exoplayer2.metadata.MetadataRenderer
;
import
com.google.android.exoplayer2.robolectric.PlaybackOutput
;
import
com.google.android.exoplayer2.robolectric.ShadowMediaCodecConfig
;
import
com.google.android.exoplayer2.robolectric.TestPlayerRunHelper
;
...
...
@@ -96,4 +100,70 @@ public final class DashPlaybackTest {
DumpFileAsserts
.
assertOutput
(
applicationContext
,
playbackOutput
,
"playbackdumps/dash/emsg.dump"
);
}
@Test
public
void
renderMetadata_withTimelyOutput
()
throws
Exception
{
Context
applicationContext
=
ApplicationProvider
.
getApplicationContext
();
RenderersFactory
renderersFactory
=
(
eventHandler
,
videoRendererEventListener
,
audioRendererEventListener
,
textRendererOutput
,
metadataRendererOutput
)
->
new
Renderer
[]
{
new
MetadataRenderer
(
metadataRendererOutput
,
eventHandler
.
getLooper
())};
ExoPlayer
player
=
new
ExoPlayer
.
Builder
(
applicationContext
,
renderersFactory
)
.
setClock
(
new
FakeClock
(
/* isAutoAdvancing= */
true
))
.
build
();
player
.
setVideoSurface
(
new
Surface
(
new
SurfaceTexture
(
/* texName= */
1
)));
CapturingRenderersFactory
capturingRenderersFactory
=
new
CapturingRenderersFactory
(
applicationContext
);
PlaybackOutput
playbackOutput
=
PlaybackOutput
.
register
(
player
,
capturingRenderersFactory
);
player
.
setMediaItem
(
MediaItem
.
fromUri
(
"asset:///media/dash/emsg/sample.mpd"
));
player
.
prepare
();
player
.
play
();
TestPlayerRunHelper
.
playUntilPosition
(
player
,
/* mediaItemIndex= */
0
,
/* positionMs= */
500
);
player
.
release
();
// Ensure output contains metadata up to the playback position.
DumpFileAsserts
.
assertOutput
(
applicationContext
,
playbackOutput
,
"playbackdumps/dash/metadata_from_timely_output.dump"
);
}
@Test
public
void
renderMetadata_withEarlyOutput
()
throws
Exception
{
Context
applicationContext
=
ApplicationProvider
.
getApplicationContext
();
RenderersFactory
renderersFactory
=
(
eventHandler
,
videoRendererEventListener
,
audioRendererEventListener
,
textRendererOutput
,
metadataRendererOutput
)
->
new
Renderer
[]
{
new
MetadataRenderer
(
metadataRendererOutput
,
eventHandler
.
getLooper
(),
MetadataDecoderFactory
.
DEFAULT
,
/* outputMetadataEarly= */
true
)
};
ExoPlayer
player
=
new
ExoPlayer
.
Builder
(
applicationContext
,
renderersFactory
)
.
setClock
(
new
FakeClock
(
/* isAutoAdvancing= */
true
))
.
build
();
player
.
setVideoSurface
(
new
Surface
(
new
SurfaceTexture
(
/* texName= */
1
)));
CapturingRenderersFactory
capturingRenderersFactory
=
new
CapturingRenderersFactory
(
applicationContext
);
PlaybackOutput
playbackOutput
=
PlaybackOutput
.
register
(
player
,
capturingRenderersFactory
);
player
.
setMediaItem
(
MediaItem
.
fromUri
(
"asset:///media/dash/emsg/sample.mpd"
));
player
.
prepare
();
player
.
play
();
TestPlayerRunHelper
.
playUntilPosition
(
player
,
/* mediaItemIndex= */
0
,
/* positionMs= */
500
);
player
.
release
();
// Ensure output contains all metadata irrespective of the playback position.
DumpFileAsserts
.
assertOutput
(
applicationContext
,
playbackOutput
,
"playbackdumps/dash/metadata_from_early_output.dump"
);
}
}
testdata/src/test/assets/playbackdumps/dash/metadata_from_early_output.dump
0 → 100644
View file @
14f75c16
MetadataOutput:
Metadata[0]:
presentationTimeUs = 100000
entry[0] = EMSG: scheme=urn:mpeg:dash:event:callback:2015, id=0, durationMs=1000, value=1
Metadata[1]:
presentationTimeUs = 100000
entry[0] = EMSG: scheme=urn:mpeg:dash:event:callback:2015, id=1, durationMs=1000, value=1
Metadata[2]:
presentationTimeUs = 1000000
entry[0] = EMSG: scheme=urn:mpeg:dash:event:callback:2015, id=2, durationMs=1000, value=1
testdata/src/test/assets/playbackdumps/dash/metadata_from_timely_output.dump
0 → 100644
View file @
14f75c16
MetadataOutput:
Metadata[0]:
presentationTimeUs = 100000
entry[0] = EMSG: scheme=urn:mpeg:dash:event:callback:2015, id=0, durationMs=1000, value=1
Metadata[1]:
presentationTimeUs = 100000
entry[0] = EMSG: scheme=urn:mpeg:dash:event:callback:2015, id=1, durationMs=1000, value=1
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