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
43cfc4a6
authored
Oct 05, 2021
by
olly
Committed by
Dustin
Feb 01, 2022
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Revert Demo and remove BitmapFactoryVideoRenderer classes
parent
e9fcc967
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
3 additions
and
697 deletions
demos/main/src/main/assets/media.exolist.json
demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java
library/core/build.gradle
library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java
library/core/src/main/java/com/google/android/exoplayer2/video/BitmapFactoryVideoRenderer.java
library/core/src/test/java/com/google/android/exoplayer2/video/BitmapFactoryVideoRendererTest.java
library/core/src/test/java/com/google/android/exoplayer2/video/FakeEventListener.java
library/core/src/test/java/com/google/android/exoplayer2/video/ShadowSurfaceExtended.java
testdata/src/test/assets/media/jpeg/image-320-240.jpg
demos/main/src/main/assets/media.exolist.json
View file @
43cfc4a6
...
@@ -543,10 +543,6 @@
...
@@ -543,10 +543,6 @@
"name"
:
"Misc"
,
"name"
:
"Misc"
,
"samples"
:
[
"samples"
:
[
{
{
"name"
:
"User File"
,
"uri"
:
"content://user"
},
{
"name"
:
"Dizzy (MP4)"
,
"name"
:
"Dizzy (MP4)"
,
"uri"
:
"https://html5demos.com/assets/dizzy.mp4"
"uri"
:
"https://html5demos.com/assets/dizzy.mp4"
},
},
...
...
demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java
View file @
43cfc4a6
...
@@ -19,7 +19,6 @@ import static com.google.android.exoplayer2.util.Assertions.checkArgument;
...
@@ -19,7 +19,6 @@ import static com.google.android.exoplayer2.util.Assertions.checkArgument;
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
.
checkState
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkState
;
import
android.content.ContentResolver
;
import
android.content.Context
;
import
android.content.Context
;
import
android.content.Intent
;
import
android.content.Intent
;
import
android.content.SharedPreferences
;
import
android.content.SharedPreferences
;
...
@@ -41,8 +40,6 @@ import android.widget.ExpandableListView.OnChildClickListener;
...
@@ -41,8 +40,6 @@ import android.widget.ExpandableListView.OnChildClickListener;
import
android.widget.ImageButton
;
import
android.widget.ImageButton
;
import
android.widget.TextView
;
import
android.widget.TextView
;
import
android.widget.Toast
;
import
android.widget.Toast
;
import
androidx.activity.result.ActivityResultLauncher
;
import
androidx.activity.result.contract.ActivityResultContracts
;
import
androidx.annotation.Nullable
;
import
androidx.annotation.Nullable
;
import
androidx.appcompat.app.AppCompatActivity
;
import
androidx.appcompat.app.AppCompatActivity
;
import
com.google.android.exoplayer2.MediaItem
;
import
com.google.android.exoplayer2.MediaItem
;
...
@@ -76,7 +73,6 @@ public class SampleChooserActivity extends AppCompatActivity
...
@@ -76,7 +73,6 @@ public class SampleChooserActivity extends AppCompatActivity
private
static
final
String
TAG
=
"SampleChooserActivity"
;
private
static
final
String
TAG
=
"SampleChooserActivity"
;
private
static
final
String
GROUP_POSITION_PREFERENCE_KEY
=
"sample_chooser_group_position"
;
private
static
final
String
GROUP_POSITION_PREFERENCE_KEY
=
"sample_chooser_group_position"
;
private
static
final
String
CHILD_POSITION_PREFERENCE_KEY
=
"sample_chooser_child_position"
;
private
static
final
String
CHILD_POSITION_PREFERENCE_KEY
=
"sample_chooser_child_position"
;
private
static
final
Uri
USER_CONTENT
=
new
Uri
.
Builder
().
scheme
(
ContentResolver
.
SCHEME_CONTENT
).
authority
(
"user"
).
build
();
private
String
[]
uris
;
private
String
[]
uris
;
private
boolean
useExtensionRenderers
;
private
boolean
useExtensionRenderers
;
...
@@ -84,13 +80,6 @@ public class SampleChooserActivity extends AppCompatActivity
...
@@ -84,13 +80,6 @@ public class SampleChooserActivity extends AppCompatActivity
private
SampleAdapter
sampleAdapter
;
private
SampleAdapter
sampleAdapter
;
private
MenuItem
preferExtensionDecodersMenuItem
;
private
MenuItem
preferExtensionDecodersMenuItem
;
private
ExpandableListView
sampleListView
;
private
ExpandableListView
sampleListView
;
private
final
ActivityResultLauncher
<
String
[]>
openDocumentLauncher
=
registerForActivityResult
(
new
ActivityResultContracts
.
OpenDocument
(),
uri
->
{
if
(
uri
!=
null
)
{
final
MediaItem
mediaItem
=
new
MediaItem
.
Builder
().
setUri
(
uri
).
build
();
startPlayer
(
Collections
.
singletonList
(
mediaItem
));
}
});
@Override
@Override
public
void
onCreate
(
Bundle
savedInstanceState
)
{
public
void
onCreate
(
Bundle
savedInstanceState
)
{
...
@@ -234,25 +223,13 @@ public class SampleChooserActivity extends AppCompatActivity
...
@@ -234,25 +223,13 @@ public class SampleChooserActivity extends AppCompatActivity
prefEditor
.
apply
();
prefEditor
.
apply
();
PlaylistHolder
playlistHolder
=
(
PlaylistHolder
)
view
.
getTag
();
PlaylistHolder
playlistHolder
=
(
PlaylistHolder
)
view
.
getTag
();
final
List
<
MediaItem
>
mediaItems
=
playlistHolder
.
mediaItems
;
if
(!
mediaItems
.
isEmpty
())
{
final
MediaItem
mediaItem
=
mediaItems
.
get
(
0
);
if
(
mediaItem
.
localConfiguration
!=
null
&&
USER_CONTENT
.
equals
(
mediaItem
.
localConfiguration
.
uri
))
{
openDocumentLauncher
.
launch
(
new
String
[]{
"video/*"
,
"audio/*"
});
return
true
;
}
}
startPlayer
(
playlistHolder
.
mediaItems
);
return
true
;
}
private
void
startPlayer
(
final
List
<
MediaItem
>
mediaItems
)
{
Intent
intent
=
new
Intent
(
this
,
PlayerActivity
.
class
);
Intent
intent
=
new
Intent
(
this
,
PlayerActivity
.
class
);
intent
.
putExtra
(
intent
.
putExtra
(
IntentUtil
.
PREFER_EXTENSION_DECODERS_EXTRA
,
IntentUtil
.
PREFER_EXTENSION_DECODERS_EXTRA
,
isNonNullAndChecked
(
preferExtensionDecodersMenuItem
));
isNonNullAndChecked
(
preferExtensionDecodersMenuItem
));
IntentUtil
.
addToIntent
(
mediaItems
,
intent
);
IntentUtil
.
addToIntent
(
playlistHolder
.
mediaItems
,
intent
);
startActivity
(
intent
);
startActivity
(
intent
);
return
true
;
}
}
private
void
onSampleDownloadButtonClicked
(
PlaylistHolder
playlistHolder
)
{
private
void
onSampleDownloadButtonClicked
(
PlaylistHolder
playlistHolder
)
{
...
...
library/core/build.gradle
View file @
43cfc4a6
...
@@ -21,11 +21,7 @@ android {
...
@@ -21,11 +21,7 @@ android {
testInstrumentationRunnerArguments
clearPackageData:
'true'
testInstrumentationRunnerArguments
clearPackageData:
'true'
multiDexEnabled
true
multiDexEnabled
true
}
}
testOptions
{
unitTests
.
all
{
jvmArgs
'-noverify'
}
}
buildTypes
{
buildTypes
{
debug
{
debug
{
testCoverageEnabled
=
true
testCoverageEnabled
=
true
...
...
library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java
View file @
43cfc4a6
...
@@ -27,7 +27,6 @@ import com.google.android.exoplayer2.audio.AudioRendererEventListener;
...
@@ -27,7 +27,6 @@ import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import
com.google.android.exoplayer2.audio.AudioSink
;
import
com.google.android.exoplayer2.audio.AudioSink
;
import
com.google.android.exoplayer2.audio.DefaultAudioSink
;
import
com.google.android.exoplayer2.audio.DefaultAudioSink
;
import
com.google.android.exoplayer2.audio.MediaCodecAudioRenderer
;
import
com.google.android.exoplayer2.audio.MediaCodecAudioRenderer
;
import
com.google.android.exoplayer2.video.BitmapFactoryVideoRenderer
;
import
com.google.android.exoplayer2.mediacodec.DefaultMediaCodecAdapterFactory
;
import
com.google.android.exoplayer2.mediacodec.DefaultMediaCodecAdapterFactory
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecAdapter
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecAdapter
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecSelector
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecSelector
;
...
@@ -396,7 +395,6 @@ public class DefaultRenderersFactory implements RenderersFactory {
...
@@ -396,7 +395,6 @@ public class DefaultRenderersFactory implements RenderersFactory {
eventListener
,
eventListener
,
MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY
);
MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY
);
out
.
add
(
videoRenderer
);
out
.
add
(
videoRenderer
);
out
.
add
(
new
BitmapFactoryVideoRenderer
(
eventHandler
,
eventListener
));
if
(
extensionRendererMode
==
EXTENSION_RENDERER_MODE_OFF
)
{
if
(
extensionRendererMode
==
EXTENSION_RENDERER_MODE_OFF
)
{
return
;
return
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/video/BitmapFactoryVideoRenderer.java
deleted
100644 → 0
View file @
e9fcc967
package
com
.
google
.
android
.
exoplayer2
.
video
;
import
android.graphics.Bitmap
;
import
android.graphics.BitmapFactory
;
import
android.graphics.Canvas
;
import
android.graphics.Rect
;
import
android.os.Handler
;
import
android.os.SystemClock
;
import
android.view.Surface
;
import
androidx.annotation.NonNull
;
import
androidx.annotation.Nullable
;
import
androidx.annotation.VisibleForTesting
;
import
androidx.annotation.WorkerThread
;
import
androidx.arch.core.util.Function
;
import
com.google.android.exoplayer2.BaseRenderer
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.ExoPlaybackException
;
import
com.google.android.exoplayer2.Format
;
import
com.google.android.exoplayer2.FormatHolder
;
import
com.google.android.exoplayer2.RendererCapabilities
;
import
com.google.android.exoplayer2.decoder.DecoderCounters
;
import
com.google.android.exoplayer2.decoder.DecoderInputBuffer
;
import
com.google.android.exoplayer2.source.SampleStream
;
import
com.google.android.exoplayer2.util.MimeTypes
;
import
java.nio.ByteBuffer
;
public
class
BitmapFactoryVideoRenderer
extends
BaseRenderer
{
static
final
String
TAG
=
"BitmapFactoryRenderer"
;
//Sleep Reasons
static
final
String
STREAM_END
=
"Stream End"
;
static
final
String
STREAM_EMPTY
=
"Stream Empty"
;
static
final
String
RENDER_WAIT
=
"Render Wait"
;
private
static
int
threadId
;
private
final
Rect
rect
=
new
Rect
();
private
final
RenderRunnable
renderRunnable
=
new
RenderRunnable
();
final
VideoRendererEventListener
.
EventDispatcher
eventDispatcher
;
final
Thread
thread
=
new
Thread
(
renderRunnable
,
getClass
().
getSimpleName
()
+
threadId
++);
@Nullable
volatile
Surface
surface
;
private
VideoSize
lastVideoSize
=
VideoSize
.
UNKNOWN
;
private
long
currentTimeUs
;
private
long
frameUs
;
private
boolean
firstFrameRendered
;
@Nullable
private
DecoderCounters
decoderCounters
;
public
BitmapFactoryVideoRenderer
(
@Nullable
Handler
eventHandler
,
@Nullable
VideoRendererEventListener
eventListener
)
{
super
(
C
.
TRACK_TYPE_VIDEO
);
eventDispatcher
=
new
VideoRendererEventListener
.
EventDispatcher
(
eventHandler
,
eventListener
);
}
@NonNull
@Override
public
String
getName
()
{
return
TAG
;
}
@Override
protected
void
onEnabled
(
boolean
joining
,
boolean
mayRenderStartOfStream
)
throws
ExoPlaybackException
{
decoderCounters
=
new
DecoderCounters
();
eventDispatcher
.
enabled
(
decoderCounters
);
if
(
mayRenderStartOfStream
)
{
thread
.
start
();
}
}
@Override
protected
void
onStarted
()
throws
ExoPlaybackException
{
if
(
thread
.
getState
()
==
Thread
.
State
.
NEW
)
{
thread
.
start
();
}
}
@Override
protected
void
onDisabled
()
{
renderRunnable
.
stop
();
@Nullable
final
DecoderCounters
decoderCounters
=
this
.
decoderCounters
;
if
(
decoderCounters
!=
null
)
{
eventDispatcher
.
disabled
(
decoderCounters
);
}
}
@Override
public
void
render
(
long
positionUs
,
long
elapsedRealtimeUs
)
throws
ExoPlaybackException
{
//Log.d(TAG, "Render: us=" + positionUs);
synchronized
(
renderRunnable
)
{
currentTimeUs
=
positionUs
;
renderRunnable
.
notify
();
}
}
@Override
protected
void
onPositionReset
(
long
positionUs
,
boolean
joining
)
throws
ExoPlaybackException
{
thread
.
interrupt
();
}
@Override
public
void
handleMessage
(
int
messageType
,
@Nullable
Object
message
)
throws
ExoPlaybackException
{
if
(
messageType
==
MSG_SET_VIDEO_OUTPUT
)
{
if
(
message
instanceof
Surface
)
{
surface
=
(
Surface
)
message
;
}
else
{
surface
=
null
;
}
}
super
.
handleMessage
(
messageType
,
message
);
}
@Override
public
boolean
isReady
()
{
return
surface
!=
null
;
}
@Override
public
boolean
isEnded
()
{
return
renderRunnable
.
isEnded
();
}
@Override
public
int
supportsFormat
(
Format
format
)
throws
ExoPlaybackException
{
//Technically could support any format BitmapFactory supports
if
(
MimeTypes
.
VIDEO_MJPEG
.
equals
(
format
.
sampleMimeType
))
{
return
RendererCapabilities
.
create
(
C
.
FORMAT_HANDLED
);
}
return
RendererCapabilities
.
create
(
C
.
FORMAT_UNSUPPORTED_TYPE
);
}
@WorkerThread
private
void
onFormatChanged
(
@NonNull
FormatHolder
formatHolder
)
{
@Nullable
final
Format
format
=
formatHolder
.
format
;
if
(
format
!=
null
)
{
frameUs
=
(
long
)(
1_000_000L
/
format
.
frameRate
);
eventDispatcher
.
inputFormatChanged
(
format
,
null
);
}
}
@WorkerThread
void
renderBitmap
(
@NonNull
final
Bitmap
bitmap
)
{
@Nullable
final
Surface
surface
=
this
.
surface
;
if
(
surface
==
null
)
{
return
;
}
//Log.d(TAG, "Drawing: " + bitmap.getWidth() + "x" + bitmap.getHeight());
final
Canvas
canvas
=
surface
.
lockCanvas
(
null
);
renderBitmap
(
bitmap
,
canvas
);
surface
.
unlockCanvasAndPost
(
canvas
);
@Nullable
final
DecoderCounters
decoderCounters
=
BitmapFactoryVideoRenderer
.
this
.
decoderCounters
;
if
(
decoderCounters
!=
null
)
{
decoderCounters
.
renderedOutputBufferCount
++;
}
if
(!
firstFrameRendered
)
{
firstFrameRendered
=
true
;
eventDispatcher
.
renderedFirstFrame
(
surface
);
}
}
@WorkerThread
@VisibleForTesting
void
renderBitmap
(
Bitmap
bitmap
,
Canvas
canvas
)
{
final
VideoSize
videoSize
=
new
VideoSize
(
bitmap
.
getWidth
(),
bitmap
.
getHeight
());
if
(!
videoSize
.
equals
(
lastVideoSize
))
{
lastVideoSize
=
videoSize
;
eventDispatcher
.
videoSizeChanged
(
videoSize
);
}
rect
.
set
(
0
,
0
,
canvas
.
getWidth
(),
canvas
.
getHeight
());
canvas
.
drawBitmap
(
bitmap
,
null
,
rect
,
null
);
}
class
RenderRunnable
implements
Runnable
,
Function
<
String
,
Boolean
>
{
final
DecoderInputBuffer
decoderInputBuffer
=
new
DecoderInputBuffer
(
DecoderInputBuffer
.
BUFFER_REPLACEMENT_MODE_NORMAL
);
private
volatile
boolean
running
=
true
;
@VisibleForTesting
Function
<
String
,
Boolean
>
sleepFunction
=
this
;
void
stop
()
{
running
=
false
;
thread
.
interrupt
();
}
boolean
isEnded
()
{
return
!
running
||
decoderInputBuffer
.
isEndOfStream
();
}
@Nullable
private
Bitmap
decodeInputBuffer
(
final
DecoderInputBuffer
decoderInputBuffer
)
{
@Nullable
final
ByteBuffer
byteBuffer
=
decoderInputBuffer
.
data
;
if
(
byteBuffer
!=
null
)
{
final
Bitmap
bitmap
;
try
{
bitmap
=
BitmapFactory
.
decodeByteArray
(
byteBuffer
.
array
(),
byteBuffer
.
arrayOffset
(),
byteBuffer
.
arrayOffset
()
+
byteBuffer
.
position
());
if
(
bitmap
==
null
)
{
throw
new
NullPointerException
(
"Decode bytes failed"
);
}
else
{
return
bitmap
;
}
}
catch
(
Exception
e
)
{
eventDispatcher
.
videoCodecError
(
e
);
}
}
return
null
;
}
/**
*
* @return true if interrupted
*/
public
synchronized
Boolean
apply
(
String
why
)
{
try
{
wait
();
return
false
;
}
catch
(
InterruptedException
e
)
{
//If we are interrupted, treat as a cancel
return
true
;
}
}
private
boolean
sleep
(
String
why
)
{
return
sleepFunction
.
apply
(
why
);
}
@WorkerThread
public
void
run
()
{
final
FormatHolder
formatHolder
=
getFormatHolder
();
long
start
=
SystemClock
.
uptimeMillis
();
main:
while
(
running
)
{
decoderInputBuffer
.
clear
();
final
int
result
=
readSource
(
formatHolder
,
decoderInputBuffer
,
formatHolder
.
format
==
null
?
SampleStream
.
FLAG_REQUIRE_FORMAT
:
0
);
switch
(
result
)
{
case
C
.
RESULT_BUFFER_READ
:
{
if
(
decoderInputBuffer
.
isEndOfStream
())
{
//Wait for shutdown or stream to be changed
sleep
(
STREAM_END
);
continue
;
}
final
long
leadUs
=
decoderInputBuffer
.
timeUs
-
currentTimeUs
;
//If we are more than 1/2 a frame behind, skip the next frame
if
(
leadUs
<
-
frameUs
/
2
)
{
eventDispatcher
.
droppedFrames
(
1
,
SystemClock
.
uptimeMillis
()
-
start
);
start
=
SystemClock
.
uptimeMillis
();
continue
;
}
start
=
SystemClock
.
uptimeMillis
();
@Nullable
final
Bitmap
bitmap
=
decodeInputBuffer
(
decoderInputBuffer
);
if
(
bitmap
==
null
)
{
continue
;
}
while
(
currentTimeUs
<
decoderInputBuffer
.
timeUs
)
{
//Log.d(TAG, "Sleep: us=" + currentTimeUs);
if
(
sleep
(
RENDER_WAIT
))
{
//Sleep was interrupted, discard Bitmap
continue
main
;
}
}
if
(
running
)
{
renderBitmap
(
bitmap
);
}
}
break
;
case
C
.
RESULT_FORMAT_READ
:
onFormatChanged
(
formatHolder
);
break
;
case
C
.
RESULT_NOTHING_READ
:
sleep
(
STREAM_EMPTY
);
break
;
}
}
}
}
@VisibleForTesting
(
otherwise
=
VisibleForTesting
.
NONE
)
Rect
getRect
()
{
return
rect
;
}
@Nullable
@VisibleForTesting
DecoderCounters
getDecoderCounters
()
{
return
decoderCounters
;
}
@VisibleForTesting
(
otherwise
=
VisibleForTesting
.
NONE
)
Thread
getThread
()
{
return
thread
;
}
@Nullable
@VisibleForTesting
(
otherwise
=
VisibleForTesting
.
NONE
)
Surface
getSurface
()
{
return
surface
;
}
RenderRunnable
getRenderRunnable
()
{
return
renderRunnable
;
}
}
library/core/src/test/java/com/google/android/exoplayer2/video/BitmapFactoryVideoRendererTest.java
deleted
100644 → 0
View file @
e9fcc967
package
com
.
google
.
android
.
exoplayer2
.
video
;
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
.
sample
;
import
android.content.Context
;
import
android.graphics.Bitmap
;
import
android.graphics.Canvas
;
import
android.graphics.Rect
;
import
android.os.Handler
;
import
android.os.Looper
;
import
android.view.Surface
;
import
androidx.arch.core.util.Function
;
import
androidx.test.core.app.ApplicationProvider
;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.ExoPlaybackException
;
import
com.google.android.exoplayer2.Format
;
import
com.google.android.exoplayer2.PlaybackException
;
import
com.google.android.exoplayer2.Renderer
;
import
com.google.android.exoplayer2.RendererConfiguration
;
import
com.google.android.exoplayer2.drm.DrmSessionEventListener
;
import
com.google.android.exoplayer2.drm.DrmSessionManager
;
import
com.google.android.exoplayer2.testutil.FakeSampleStream
;
import
com.google.android.exoplayer2.testutil.TestUtil
;
import
com.google.android.exoplayer2.upstream.DefaultAllocator
;
import
com.google.android.exoplayer2.util.MimeTypes
;
import
com.google.common.collect.ImmutableList
;
import
java.io.IOException
;
import
org.junit.After
;
import
org.junit.Assert
;
import
org.junit.Before
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
import
org.robolectric.annotation.Config
;
import
org.robolectric.shadow.api.Shadow
;
import
org.robolectric.shadows.ShadowBitmapFactory
;
import
org.robolectric.shadows.ShadowLooper
;
@RunWith
(
AndroidJUnit4
.
class
)
@Config
(
shadows
=
{
ShadowSurfaceExtended
.
class
})
public
class
BitmapFactoryVideoRendererTest
{
private
final
static
Format
FORMAT_MJPEG
=
new
Format
.
Builder
().
setSampleMimeType
(
MimeTypes
.
VIDEO_MJPEG
).
setWidth
(
320
).
setHeight
(
240
).
setFrameRate
(
15
f
).
build
();
FakeEventListener
fakeEventListener
=
new
FakeEventListener
();
BitmapFactoryVideoRenderer
bitmapFactoryVideoRenderer
;
@Before
public
void
before
()
{
fakeEventListener
=
new
FakeEventListener
();
final
Handler
handler
=
new
Handler
(
Looper
.
getMainLooper
());
bitmapFactoryVideoRenderer
=
new
BitmapFactoryVideoRenderer
(
handler
,
fakeEventListener
);
}
@After
public
void
after
()
{
//Kill the Thread
bitmapFactoryVideoRenderer
.
onDisabled
();
}
@Test
public
void
getName
()
{
Assert
.
assertEquals
(
BitmapFactoryVideoRenderer
.
TAG
,
bitmapFactoryVideoRenderer
.
getName
());
}
@Test
public
void
onEnabled_givenMayRenderStartOfStream
()
throws
PlaybackException
{
bitmapFactoryVideoRenderer
.
onEnabled
(
false
,
true
);
ShadowLooper
.
idleMainLooper
();
Assert
.
assertNotNull
(
bitmapFactoryVideoRenderer
.
getDecoderCounters
());
Assert
.
assertEquals
(
Thread
.
State
.
RUNNABLE
,
bitmapFactoryVideoRenderer
.
getThread
().
getState
());
Assert
.
assertTrue
(
fakeEventListener
.
isVideoEnabled
());
}
@Test
public
void
onStarted_givenThreadNotStarted
()
throws
PlaybackException
{
bitmapFactoryVideoRenderer
.
onStarted
();
ShadowLooper
.
idleMainLooper
();
Assert
.
assertEquals
(
Thread
.
State
.
RUNNABLE
,
bitmapFactoryVideoRenderer
.
getThread
().
getState
());
}
@Test
public
void
onDisabled_givenOnEnabled
()
throws
PlaybackException
,
InterruptedException
{
onEnabled_givenMayRenderStartOfStream
();
bitmapFactoryVideoRenderer
.
onDisabled
();
ShadowLooper
.
idleMainLooper
();
Assert
.
assertFalse
(
fakeEventListener
.
isVideoEnabled
());
//Ensure Thread is shutdown
bitmapFactoryVideoRenderer
.
getThread
().
join
(
500L
);
Assert
.
assertTrue
(
bitmapFactoryVideoRenderer
.
isEnded
());
}
private
FakeSampleStream
getSampleStream
()
throws
IOException
{
final
Context
context
=
ApplicationProvider
.
getApplicationContext
();
final
byte
[]
bytes
=
TestUtil
.
getByteArray
(
context
,
"media/jpeg/image-320-240.jpg"
);
FakeSampleStream
fakeSampleStream
=
new
FakeSampleStream
(
new
DefaultAllocator
(
/* trimOnReset= */
true
,
/* individualAllocationSize= */
1024
),
/* mediaSourceEventDispatcher= */
null
,
DrmSessionManager
.
DRM_UNSUPPORTED
,
new
DrmSessionEventListener
.
EventDispatcher
(),
/* initialFormat= */
FORMAT_MJPEG
,
ImmutableList
.
of
(
sample
(
0L
,
C
.
BUFFER_FLAG_KEY_FRAME
,
bytes
),
END_OF_STREAM_ITEM
));
return
fakeSampleStream
;
}
private
Surface
setSurface
()
throws
ExoPlaybackException
{
final
Surface
surface
=
ShadowSurfaceExtended
.
newInstance
();
final
ShadowSurfaceExtended
shadowSurfaceExtended
=
Shadow
.
extract
(
surface
);
shadowSurfaceExtended
.
setSize
(
1080
,
1920
);
bitmapFactoryVideoRenderer
.
handleMessage
(
Renderer
.
MSG_SET_VIDEO_OUTPUT
,
surface
);
return
surface
;
}
@Test
public
void
handleMessage_givenSurface
()
throws
ExoPlaybackException
{
final
Surface
surface
=
setSurface
();
Assert
.
assertSame
(
surface
,
bitmapFactoryVideoRenderer
.
getSurface
());
bitmapFactoryVideoRenderer
.
handleMessage
(
Renderer
.
MSG_SET_VIDEO_OUTPUT
,
null
);
Assert
.
assertNull
(
bitmapFactoryVideoRenderer
.
getSurface
());
}
@Test
public
void
isReady_givenSurface
()
throws
ExoPlaybackException
{
Assert
.
assertFalse
(
bitmapFactoryVideoRenderer
.
isReady
());
setSurface
();
Assert
.
assertTrue
(
bitmapFactoryVideoRenderer
.
isReady
());
}
@Test
public
void
render_givenJpegAndSurface
()
throws
IOException
,
ExoPlaybackException
{
final
Surface
surface
=
setSurface
();
final
ShadowSurfaceExtended
shadowSurfaceExtended
=
Shadow
.
extract
(
surface
);
FakeSampleStream
fakeSampleStream
=
getSampleStream
();
fakeSampleStream
.
writeData
(
0L
);
bitmapFactoryVideoRenderer
.
enable
(
RendererConfiguration
.
DEFAULT
,
new
Format
[]{
FORMAT_MJPEG
},
fakeSampleStream
,
0L
,
false
,
true
,
0L
,
0L
);
bitmapFactoryVideoRenderer
.
render
(
0L
,
0L
);
// This test actually decodes the JPEG (very cool!),
// May need to bump up timers for slow machines
Assert
.
assertTrue
(
shadowSurfaceExtended
.
waitForPost
(
500L
));
}
@Test
public
void
supportsFormat_givenMjpegFormat
()
throws
ExoPlaybackException
{
Assert
.
assertEquals
(
C
.
FORMAT_HANDLED
,
bitmapFactoryVideoRenderer
.
supportsFormat
(
FORMAT_MJPEG
)
&
C
.
FORMAT_HANDLED
);
}
@Test
public
void
supportsFormat_givenMp4vFormat
()
throws
ExoPlaybackException
{
final
Format
format
=
new
Format
.
Builder
().
setSampleMimeType
(
MimeTypes
.
VIDEO_MP4V
).
build
();
Assert
.
assertEquals
(
0
,
bitmapFactoryVideoRenderer
.
supportsFormat
(
format
)
&
C
.
FORMAT_HANDLED
);
}
@Test
public
void
renderBitmap_given4by3BitmapAnd16by9Canvas
()
{
final
Bitmap
bitmap
=
Bitmap
.
createBitmap
(
FORMAT_MJPEG
.
width
,
FORMAT_MJPEG
.
height
,
Bitmap
.
Config
.
ARGB_8888
);
final
Bitmap
canvasBitmap
=
Bitmap
.
createBitmap
(
1080
,
1920
,
Bitmap
.
Config
.
ARGB_8888
);
final
Canvas
canvas
=
new
Canvas
(
canvasBitmap
);
bitmapFactoryVideoRenderer
.
renderBitmap
(
bitmap
,
canvas
);
ShadowLooper
.
idleMainLooper
();
final
Rect
rect
=
bitmapFactoryVideoRenderer
.
getRect
();
Assert
.
assertEquals
(
canvas
.
getWidth
(),
rect
.
width
());
Assert
.
assertEquals
(
canvas
.
getHeight
(),
rect
.
height
());
final
VideoSize
videoSize
=
fakeEventListener
.
videoSize
;
Assert
.
assertEquals
(
bitmap
.
getWidth
(),
videoSize
.
width
);
bitmapFactoryVideoRenderer
.
renderBitmap
(
bitmap
,
canvas
);
ShadowLooper
.
idleMainLooper
();
Assert
.
assertSame
(
videoSize
,
fakeEventListener
.
videoSize
);
}
@Test
public
void
RenderRunnable_run_givenLateFrame
()
throws
IOException
,
ExoPlaybackException
{
final
Function
<
String
,
Boolean
>
sleep
=
why
->
{
throw
new
RuntimeException
(
why
);};
FakeSampleStream
fakeSampleStream
=
getSampleStream
();
fakeSampleStream
.
writeData
(
0L
);
//Don't enable so the Thread is not running
bitmapFactoryVideoRenderer
.
replaceStream
(
new
Format
[]{
FORMAT_MJPEG
},
fakeSampleStream
,
0L
,
0L
);
BitmapFactoryVideoRenderer
.
RenderRunnable
renderRunnable
=
bitmapFactoryVideoRenderer
.
getRenderRunnable
();
renderRunnable
.
sleepFunction
=
sleep
;
bitmapFactoryVideoRenderer
.
render
(
1_000_000L
,
0L
);
try
{
renderRunnable
.
run
();
}
catch
(
RuntimeException
e
)
{
Assert
.
assertEquals
(
BitmapFactoryVideoRenderer
.
STREAM_EMPTY
,
e
.
getMessage
());
}
ShadowLooper
.
idleMainLooper
();
Assert
.
assertEquals
(
1
,
fakeEventListener
.
getDroppedFrames
());
}
@Test
public
void
RenderRunnable_run_givenBadJpeg
()
throws
IOException
,
ExoPlaybackException
{
final
Function
<
String
,
Boolean
>
sleep
=
why
->
{
throw
new
RuntimeException
(
why
);};
FakeSampleStream
fakeSampleStream
=
new
FakeSampleStream
(
new
DefaultAllocator
(
/* trimOnReset= */
true
,
/* individualAllocationSize= */
1024
),
/* mediaSourceEventDispatcher= */
null
,
DrmSessionManager
.
DRM_UNSUPPORTED
,
new
DrmSessionEventListener
.
EventDispatcher
(),
/* initialFormat= */
FORMAT_MJPEG
,
ImmutableList
.
of
(
oneByteSample
(
0L
,
C
.
BUFFER_FLAG_KEY_FRAME
),
END_OF_STREAM_ITEM
));
fakeSampleStream
.
writeData
(
0L
);
//Don't enable so the Thread is not running
bitmapFactoryVideoRenderer
.
replaceStream
(
new
Format
[]{
FORMAT_MJPEG
},
fakeSampleStream
,
0L
,
0L
);
BitmapFactoryVideoRenderer
.
RenderRunnable
renderRunnable
=
bitmapFactoryVideoRenderer
.
getRenderRunnable
();
renderRunnable
.
sleepFunction
=
sleep
;
bitmapFactoryVideoRenderer
.
render
(
0L
,
0L
);
// There is a bug in Robolectric where it doesn't handle null images,
// so we won't get our Exception
ShadowBitmapFactory
.
setAllowInvalidImageData
(
false
);
try
{
renderRunnable
.
run
();
}
catch
(
RuntimeException
e
)
{
Assert
.
assertEquals
(
BitmapFactoryVideoRenderer
.
STREAM_EMPTY
,
e
.
getMessage
());
}
ShadowLooper
.
idleMainLooper
();
Assert
.
assertTrue
(
fakeEventListener
.
getVideoCodecError
()
instanceof
NullPointerException
);
}
}
library/core/src/test/java/com/google/android/exoplayer2/video/FakeEventListener.java
deleted
100644 → 0
View file @
e9fcc967
package
com
.
google
.
android
.
exoplayer2
.
video
;
import
androidx.annotation.Nullable
;
import
com.google.android.exoplayer2.decoder.DecoderCounters
;
public
class
FakeEventListener
implements
VideoRendererEventListener
{
@Nullable
VideoSize
videoSize
;
@Nullable
DecoderCounters
decoderCounters
;
private
long
firstFrameRenderMs
=
Long
.
MIN_VALUE
;
private
int
droppedFrames
;
private
Exception
videoCodecError
;
@Override
public
void
onVideoSizeChanged
(
VideoSize
videoSize
)
{
this
.
videoSize
=
videoSize
;
}
public
boolean
isVideoEnabled
()
{
return
decoderCounters
!=
null
;
}
@Override
public
void
onVideoEnabled
(
DecoderCounters
counters
)
{
decoderCounters
=
counters
;
}
@Override
public
void
onVideoDisabled
(
DecoderCounters
counters
)
{
decoderCounters
=
null
;
}
public
long
getFirstFrameRenderMs
()
{
return
firstFrameRenderMs
;
}
@Override
public
void
onRenderedFirstFrame
(
Object
output
,
long
renderTimeMs
)
{
firstFrameRenderMs
=
renderTimeMs
;
}
public
int
getDroppedFrames
()
{
return
droppedFrames
;
}
@Override
public
void
onDroppedFrames
(
int
count
,
long
elapsedMs
)
{
droppedFrames
+=
count
;
}
public
Exception
getVideoCodecError
()
{
return
videoCodecError
;
}
@Override
public
void
onVideoCodecError
(
Exception
videoCodecError
)
{
this
.
videoCodecError
=
videoCodecError
;
}
}
library/core/src/test/java/com/google/android/exoplayer2/video/ShadowSurfaceExtended.java
deleted
100644 → 0
View file @
e9fcc967
package
com
.
google
.
android
.
exoplayer2
.
video
;
import
android.graphics.Bitmap
;
import
android.graphics.Canvas
;
import
android.graphics.Rect
;
import
android.view.Surface
;
import
java.util.concurrent.Semaphore
;
import
java.util.concurrent.TimeUnit
;
import
org.robolectric.annotation.Implements
;
import
org.robolectric.shadow.api.Shadow
;
import
org.robolectric.shadows.ShadowSurface
;
@Implements
(
Surface
.
class
)
public
class
ShadowSurfaceExtended
extends
ShadowSurface
{
private
final
Semaphore
postSemaphore
=
new
Semaphore
(
0
);
private
int
width
;
private
int
height
;
public
static
Surface
newInstance
()
{
return
Shadow
.
newInstanceOf
(
Surface
.
class
);
}
public
void
setSize
(
final
int
width
,
final
int
height
)
{
this
.
width
=
width
;
this
.
height
=
height
;
}
public
Canvas
lockCanvas
(
Rect
canvas
)
{
return
new
Canvas
(
Bitmap
.
createBitmap
(
width
,
height
,
Bitmap
.
Config
.
ARGB_8888
));
}
public
void
unlockCanvasAndPost
(
Canvas
canvas
)
{
postSemaphore
.
release
();
}
public
boolean
waitForPost
(
long
millis
)
{
try
{
return
postSemaphore
.
tryAcquire
(
millis
,
TimeUnit
.
MILLISECONDS
);
}
catch
(
InterruptedException
e
)
{
return
false
;
}
}
}
testdata/src/test/assets/media/jpeg/image-320-240.jpg
deleted
100644 → 0
View file @
e9fcc967
35.2 KB
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