mirror of
https://github.com/home-assistant/android
synced 2024-10-15 20:43:06 +00:00
Improve Notification GIFs (#2657)
* Modify Notification GIF Framerate Modoify VIDEO_INCREMENT_MICROSECONDS to have notification GIFs generate a frame every 0.5 seconds for a video instead of the current every 2 seconds. This should result in smoother notification animations thus improving the UX * Increase processingFrames.size Increase processingFrames.size as increasing the framerate results in the animation ending too soon * Scale video to a max width of 480 while maintaining video ratio Notification video width scaled to 480. (1920 / 4) Height will scale accordingly while maintaining video aspect ratio. e.g. 1920x2560 will be scaled to 480x640 Frames are set to every 1 seconds with 5 frames resulting in a 5 second GIF * Update MessagingManager.kt Based on research and rapid prototyping under another fork, I've decided that scaling the image to a fixed width and a ratioed height is not going to work. There are issues that need to be resolved which result in the image being cut off and stretched in the notification. End result is the only overall change is changing the frame generation from once every 2 seconds to once every 1 second. This has improved the fluidity of notifications but I understand is my own subjective opinion. This means there is still a potential case where 4K cameras will exceed the size limits of notifications. * Typo fix * Re-Implement Scaling to a Constant Width * Update MessagingManager.kt * Add logic to not scale videos smaller than specified width and Increase GIF speed (ViewFlipper) * Tune processing times Tune processing time to have one frame generated every 0.75s, then frames are played back at 3x speed. This results in 9 seconds of activity being played in 3 seconds. * Implement Dynamic Frame Count Dynamic number of frames up to a specified size limit (currently 2.5MB) * Review Before Merge
This commit is contained in:
parent
22df320fd9
commit
0c9e8ac65f
|
@ -259,7 +259,7 @@ class MessagingManager @Inject constructor(
|
||||||
|
|
||||||
// Video Values
|
// Video Values
|
||||||
const val VIDEO_START_MICROSECONDS = 100000L
|
const val VIDEO_START_MICROSECONDS = 100000L
|
||||||
const val VIDEO_INCREMENT_MICROSECONDS = 2000000L
|
const val VIDEO_INCREMENT_MICROSECONDS = 750000L
|
||||||
const val VIDEO_GUESS_MILLISECONDS = 7000L
|
const val VIDEO_GUESS_MILLISECONDS = 7000L
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1395,6 +1395,8 @@ class MessagingManager @Inject constructor(
|
||||||
) {
|
) {
|
||||||
url ?: return@withContext null
|
url ?: return@withContext null
|
||||||
val processingFrames = mutableListOf<Deferred<Bitmap?>>()
|
val processingFrames = mutableListOf<Deferred<Bitmap?>>()
|
||||||
|
var processingFramesSize = 0
|
||||||
|
var singleFrame = 0
|
||||||
|
|
||||||
try {
|
try {
|
||||||
MediaMetadataRetriever().let { mediaRetriever ->
|
MediaMetadataRetriever().let { mediaRetriever ->
|
||||||
|
@ -1407,15 +1409,21 @@ class MessagingManager @Inject constructor(
|
||||||
|
|
||||||
val durationInMicroSeconds = ((mediaRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLongOrNull() ?: VIDEO_GUESS_MILLISECONDS)) * 1000
|
val durationInMicroSeconds = ((mediaRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLongOrNull() ?: VIDEO_GUESS_MILLISECONDS)) * 1000
|
||||||
|
|
||||||
// Start at 100 milliseconds and get frames every 2 seconds until reaching the end
|
// Start at 100 milliseconds and get frames every 0.75 seconds until reaching the end
|
||||||
run frameLoop@{
|
run frameLoop@{
|
||||||
for (timeInMicroSeconds in VIDEO_START_MICROSECONDS until durationInMicroSeconds step VIDEO_INCREMENT_MICROSECONDS) {
|
for (timeInMicroSeconds in VIDEO_START_MICROSECONDS until durationInMicroSeconds step VIDEO_INCREMENT_MICROSECONDS) {
|
||||||
if (processingFrames.size >= 5) {
|
// Max size in bytes for notification GIF
|
||||||
|
val maxSize = (2500000 - singleFrame)
|
||||||
|
if (processingFramesSize >= maxSize) {
|
||||||
return@frameLoop
|
return@frameLoop
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaRetriever.getFrameAtTime(timeInMicroSeconds, MediaMetadataRetriever.OPTION_CLOSEST)
|
mediaRetriever.getFrameAtTime(timeInMicroSeconds, MediaMetadataRetriever.OPTION_CLOSEST)
|
||||||
?.let { smallFrame -> processingFrames.add(async { smallFrame.getCompressedFrame() }) }
|
?.let { smallFrame ->
|
||||||
|
processingFrames.add(async { smallFrame.getCompressedFrame() })
|
||||||
|
processingFramesSize += (smallFrame.getCompressedFrame())!!.allocationByteCount
|
||||||
|
singleFrame = (smallFrame.getCompressedFrame())!!.allocationByteCount
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1430,8 +1438,16 @@ class MessagingManager @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Bitmap.getCompressedFrame(): Bitmap? {
|
private fun Bitmap.getCompressedFrame(): Bitmap? {
|
||||||
val newHeight = height / 4
|
var newWidth = 480
|
||||||
val newWidth = width / 4
|
var newHeight = 0
|
||||||
|
// If already smaller than 480p do not scale else scale
|
||||||
|
if (width < newWidth) {
|
||||||
|
newWidth = width
|
||||||
|
newHeight = height
|
||||||
|
} else {
|
||||||
|
val ratio: Float = (width.toFloat() / height.toFloat())
|
||||||
|
newHeight = (newWidth / ratio).toInt()
|
||||||
|
}
|
||||||
return Bitmap.createScaledBitmap(this, newWidth, newHeight, false)
|
return Bitmap.createScaledBitmap(this, newWidth, newHeight, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginTop="4dp"
|
android:layout_marginTop="4dp"
|
||||||
android:autoStart="true"
|
android:autoStart="true"
|
||||||
android:flipInterval="400"
|
android:flipInterval="250"
|
||||||
android:inAnimation="@null"
|
android:inAnimation="@null"
|
||||||
android:outAnimation="@null" />
|
android:outAnimation="@null" />
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue