Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix animated webp playback #8120

Merged
merged 9 commits into from
May 26, 2023

Conversation

alexmaras
Copy link
Contributor

@alexmaras alexmaras commented Feb 12, 2023

Type of change

  • Feature
  • Bugfix
  • Technical
  • Other :

Content

Allowed Animated WebP to play correctly.

This involves making Glide transformations optional in order to prevent mimetypes that Glide doesn't support from throwing exceptions. This has a very minor impact - making rounded corners not work for animated WebP files in the timeline. However, I see this as an improvement over it not working at all.

Motivation and context

Animated WebP files are delivered by default from the Facebook Bridge (https://github.com/mautrix/facebook) so this has been a pain point for me.

Element Android issues:
#2695
#4199

Animated WebP is supported on android versions natively. The only issue is that the Glide library doesn't support it outside of rendering:
bumptech/glide#571

This could alternatively potentially be fixed by using https://github.com/zjupure/GlideWebpDecoder
However, this feels like a change that could potentially introduce more issues, as that library only supports a couple of recent versions of Glide, which would necessitate keeping those in-line. I'm not sure how well maintained the library is, and this could be worse than just accepting the minor differences in missing rounded corners for animated WebP files.

Screenshots / GIFs

Before After
animated-webp-before animated-webp-after

There is a minor annoyance introduced by this - non-animated webp files will have a "play" button on them. This is unavoidable without some major changes to the image viewer. We'd need to pull the image and read the VP8X header for the ANIM bit to see if it's an animated webp. The same issue is already present with non-animated Gifs.

Tests

  • Run in Android Studio
  • Open a room with animated WebP files
  • Tap into the animated WebP, ensure it still plays
  • Tap into an animated gif, ensure it still plays
  • Tap into a still WebP, ensure there are no changes (due to now being marked as playable)

Tested devices

  • Physical
  • Emulator
  • OS version(s): Pixel 6 API 21 on Emulator w/ Lollipop, Pixel 4A API 31 on Emulator, Nothing Phone One (physical) w/ Android 12

The changes don't do anything particularly useful on older versions of Android - but it's not more broken than it was. Android requires API v28 to play Animated WebP by the looks of it. At the moment, it would just be still images, whereas with this change they can at least see that they're supposed to be animated.

Checklist

@@ -509,7 +509,7 @@ class MessageItemFactory @Inject constructor(
allowNonMxcUrls = informationData.sendState.isSending()
)

val playable = messageContent.mimeType == MimeTypes.Gif
val playable = messageContent.mimeType == MimeTypes.Gif || messageContent.mimeType == MimeTypes.Webp
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a relatively poor check, but it's the best we can do without being able to read the file headers.

In theory, we could also use

Suggested change
val playable = messageContent.mimeType == MimeTypes.Gif || messageContent.mimeType == MimeTypes.Webp
val playable = messageContent.mimeType == MimeTypes.Gif || (messageContent.mimeType == MimeTypes.Webp && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)

to not show the "play" button on older versions of Android, but it doesn't really make much of an appreciable difference - they're broken either way, and were before.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have any statistics, but I don't think animated webps are commonly used compared to static webp images, so making all webp images 'playable' might not be the best approach.

The playable attribute is only used to display the 'play' button above the image, but if you have the autoplay animated images option enabled the image will already be playing in the timeline, so given these 2 possible cases:

  1. Users not seeing the play button in an animated webp image and not having autoplay images enabled, thus not knowing that it's an animated image.
  2. Users seeing play buttons over every single webp, most of them being static.

I think it would be better to go with the first option, that is: remove the || messageContent.mimeType == MimeTypes.Webp.

Copy link
Contributor Author

@alexmaras alexmaras Feb 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, fair call. I suspect you're right that the majority are static images.

In theory, I could work through to try to read the header of the WebP and actually know whether it's playable, but it's just a URL at this point, so I'd need to stream some of the file down until I have sufficient data to know. The check isn't particularly complex.

This would give a sensible place to add an equivalent check for .gif files too. I recognise that the vast majority of gifs are going to be animated, I'd be intrigued to know the stats on webp files.

I'll test removing the playable marker for webp and see whether it plays when autoplay is enabled. If it does, I reckon that's a really good starting point, and I could submit a secondary PR down the road to check properly whether it's animated if that makes the most sense to you.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly, autoplay in thumbnail doesn't work unless playable is enabled.

https://github.com/vector-im/element-android/blob/de50577ac38ebd9f2b69715142de1520bf9299eb/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt#L552

It does work when tapping into it, which is still a better result than it not working at all.
If we want to actually have it autoplay in preview mode, we'd need to know beforehand that it's actually an animated webp.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, for some reason the test image I was using was always being displayed as a thumbnail (size maybe?) so I didn't notice this issue.

Copy link
Member

@jmartinesp jmartinesp Feb 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm actually seeing the opposite issue. playable for WebP is disabled, autoplay is disabled too, but thumbnails for animated webp images are autoplayed anyway. I don't think it's directly related to your changes, but I also have no idea why in your case it seems like the opposite is happening.

See recording Top and bottom items are WebP, middle is a gif. Tested on an Android 13 emulator. One of the images is this one: https://mathiasbynens.be/demo/animated-webp-supported.webp.
Screen_recording_20230222_181549.webm

Copy link
Contributor Author

@alexmaras alexmaras Feb 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been able to replicate both behaviors now. I'm going to do some testing to figure out what causes it to autoplay sometimes when autoplay is turned off. In your recording, you can see that the nyancat gif has squared corners, and I'm currently looking at my emulator on API 33 and it has rounded corners and doesn't play until I click into it, even if I tap in and then go back out, and if I turn autoplay on, go back, then go turn it off again.

However, my laptop is now showing the same behavior as yours is.

I'll let you know when I have an explanation for this. Thanks for spotting!

EDIT: I've actually managed to replicate this within a single running emulator with 2 rooms. My old Facebook-bridged test room is showing me the intended behavior with autoplay off, while my empty room that I'm sending myself webp files from is animating while autoplay is off. Perhaps it's because the Facebook room is not E2E encrypted.

EDIT2: Yep, I created a new non-E2E room and it's paused with rounded corners prior to tapping into it, while the other (otherwise identical) room is autoplaying. This is because it's requesting a thumbnail during createGlideRequest, but it can obviously only do this is if the server is capable of providing a thumbnail, which it can't with E2E.
The use of playable and dontAnimate doesn't work for the Webp because Glide doesn't fully support Webp. dontAnimate is used for Gifs. I'll try to see if there's a way around this.

@alexmaras alexmaras marked this pull request as ready for review February 12, 2023 16:10
@bmarty bmarty added the Z-Community-PR Issue is solved by a community member's PR label Feb 13, 2023
Copy link
Member

@jmartinesp jmartinesp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think most of the code makes sense except for the playable attribute being true for all webp images.

Also, to have the same behaviour in the attachment screen as you do, I had to add the || it.mimeType == MimeTypes.Webp to both DataAttachmentRoomProvider.getAttachmentInfoAt and RoomEventsAttachmentProvider.getAttachmentInfoAt, otherwise the webp images didn't animate in the attachment viewer screen.

@alexmaras
Copy link
Contributor Author

alexmaras commented Feb 22, 2023

Thanks @jmartinesp - apologies, I had those previously. I must have checked them out during my pre-commit clean-up. I'll add them back in and revert WebP being marked as playable and re-test.

Edit: Ah - I realised why I took them out. I thought they weren't needed, I had forgotten that the attachments exist in the room settings too. Fixing that now.

@@ -528,6 +528,8 @@ class MessageItemFactory @Inject constructor(
)

val playable = messageContent.mimeType == MimeTypes.Gif
// don't show play button because detecting animated webp isn't possible via mimetype
val playableIfAutoplay = playable || messageContent.mimeType == MimeTypes.Webp
Copy link
Contributor Author

@alexmaras alexmaras Feb 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works to autoplay animated WebP in the timeline if autoplay is enabled. If it isn't enabled, no play button shows (because playable is false) which will prevent confusion with static WebP. Static WebP functions fine in autoplay mode still.

This workaround is fairly effective. I've added a comment to try to explain what's going on here.

Ideally I'd like to look at doing another PR to determine conclusively whether a Gif or a WebP is actually animated via headers, therefore allowing the play button to appear on animated WebP too, and take away the play button on static Gifs, but that would be a longer exercise.

@alexmaras alexmaras requested a review from jmartinesp February 22, 2023 13:35
Copy link
Member

@jmartinesp jmartinesp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, sorry, I just realised you made some changes to address the issues, I should have reviewed this months ago...

LGTM. Thanks for the PR and sorry for the long delay.

@jmartinesp jmartinesp merged commit 24b1884 into element-hq:develop May 26, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Z-Community-PR Issue is solved by a community member's PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants