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

Additional embed sources and external-media consent controls #2424

Merged
merged 54 commits into from
Jan 5, 2024

Conversation

pfrazee
Copy link
Collaborator

@pfrazee pfrazee commented Jan 4, 2024

Supersedes #2353 by:

  • Updating routing
  • Tuning UIs in the consent settings and modal
  • Simplifying copy for the consent settings
  • Adding an "allow all media embeds" button in the consent modal

Inherited from the original PR:

implements #2314
fixes #2338
fixes #2386
fixes #2357

Fixing:

  • Twitch clips embeds
  • Twitch video embeds
  • YouTube mobile embeds
  • International spotify embeds
  • Adjust aspect ratio for YT shorts

Adding:

  • Vimeo
  • Apple Music
  • Tenor
  • Giphy
  • Consent modal
  • Per-service consent setting

iOS: https://www.youtube.com/watch?v=ediW57z0Yt0
Android: https://www.youtube.com/watch?v=nLG8jTO3bhg
Web Mobile: https://www.youtube.com/watch?v=dVDMGMg3lTU
Web: https://www.youtube.com/watch?v=c2J3voPma5k

Consent:

CleanShot.2024-01-04.at.10.51.39.mp4
CleanShot.2024-01-04.at.10.49.37.mp4

Remove react-native-youtube-iframe

This isn't necessary. We can reliably use the WebView on native, and it's actually better performing. The library was interacting with the YT embed JS api, which we don't need.

Gifs

Displaying

Tenor and Giphy use a different player. Since we can rely on these images to not have audio and not be resource intensive, we do not need to remove them from the view once loaded. They will also maintain aspect ratio so we don't need to worry about jumping as we scroll away from them.

We don't want to load external assets before the user opts to do so. Therefore:

  1. Thumbnail that was uploaded to bsky is shown first.
  2. User presses play on a gif
  3. The gif is prefetched. While prefetching, ActivityIndicator is overlayed on the thumbnail
  4. After prefetch, the gif is displayed
  5. User can press the gif to control playback. We don't actually remove the gif to stop playback, but rather let expo-image control the playback of the gif

In an effort to keep these as non-intrusive as possible to the view, gifs are capped at a height of 250px

Posting

Usually, tenor or giphy will give a share link that is a direct link to a gif, like so: https://media.giphy.com/media/tXL4FHPSnVJ0A/giphy.gif. This is the link that we want to use to render the image in the Image component. However, this link does not let us fetch the metadata and thumbnail.

However, we know that the ID for the gif is tXL4FHPSnVJ0A. Therefore, we can rewrite the url to be https://giphy.com/gifs/tXL4FHPSnVJ0A and giphy will resolve that to the correct link, in this case https://giphy.com/gifs/kim-novak-tXL4FHPSnVJ0A.

Therefore, we add a little workaround to link-meta.ts when we request the metadata.

    const giphyMetaUri = getGiphyMetaUri(urlp)
    if (giphyMetaUri) {
      url = giphyMetaUri
      urlp = new URL(url)
    }

Unfortunately with Tenor, we cannot (as far as I have been able to tell, and @mozzius supposedly had the same issues) reliably do this with Tenor. Since we must obtain metadata, we only support embedding links to the actual tenor website such as https://tenor.com/view/thankful-thursday-gif-17785223204185977462. We can then append .gif to that url to get the gif for displaying in the Image component. Note though here that the ID has changed, so we cannot reverse this process to get metadata.

On one hand, updating (what I'm assuming you know as CardyB? lmao) to support some sort of gif images thing would be....a possibility. Actually, a very simple one from the looks of it. However, in an effort to still maintain some level of alt text for gifs, I don't think this is the best idea. The meta data serves as a fair form of alt text.

URLs supported (Giphy links can be shared as any of the different file names such as giphy-downsized.gif, giphy.webp, giphy.mp4, etc and we will always use the webp):

Downsizing

Regardless of the type of link provided, we always will use webp when possible. Any giphy type (gif, webp, or mp4) will use the .webp version of the image. These appear to be sub 2MB in almost all cases.

Player

expo-image supports pausing/playing a gif natively. We can use this functionality easily, and don't need to bother replacing images with thumbnails or anything else.

No longer needed canvas hack

On web, things are a bit trickier. There are a few options that we have:

  1. Don't support pause/play. This isn't very nice since it doesn't keep parity with the native app
  2. Replace the animated image with the thumbnail when paused. This isn't a great option because some thumbnails are not the same dimensions as their respective gifs. Therefore, on every pause/play the layout would have to change. There is the option of using contentFit="contain", but we still run into issues with some placeholders and trying to maintain the same dimensions as the gif, but this is still subpar since there are differences sometimes such as background color (not transparent in the thumbnail, instead shows up as white), widths being different, etc.
  3. Use the still image provided by Giphy. This is an excellent choice! But naturally, Tenor doesn't support this 🙃

The solution then is to use a canvas. I've created a small component, WebGifStill

  const canvasRef = React.useRef<HTMLCanvasElement>(null)
  React.useEffect(() => {
    // Create a new image and draw it to the canvas. This draws only the first frame of the gif or webp
    const img = new Image()
    img.onload = () => {
      canvasRef.current
        ?.getContext('2d')
        ?.drawImage(img, 0, 0, imageDims.width, imageDims.height)
    }
    img.src = source
  }, [source, imageDims])

  return (
    <canvas ref={canvasRef} height={imageDims.height} width={imageDims.width} />
  )

We leave this still behind the playing image to 1. to act as the "stopped" gif and 2. prevent flickers when rendering/removing the gif from the view (to simulate stop/play). Drawing the image to the canvas renders only the first frame of the image which is just what we want. Additionally, this does not create an additional request for the image.

I've tested this against both WEBP and GIFs on Edge, Chrome, Firefox, and Safari on desktop, Safari and Chrome on iOS, and Chrome and Firefox on Android. All of these browsers support this. See Canvas Browser Compatibility

#2379 blocks this PR right now. I had implemented a canvas hack where we could draw an image, but this will be unnecessary once web thumb resizing is fixed 🎉 Now, all we have to do to "pause" the gif on web is to just replace the gif with the thumb.

Consent

  1. Added external embed options to the local storage schema
  2. Updated the legacy schema with these options
  3. Added a useState and useSetState context in external-embeds-prefs.tsx
  4. Added the screen and updated navigation types
  5. Added button to the settings main screen to take you to a list of external embeds
  6. Handled updating state logic inside of the screen
  7. Created a modal component and updated the modal types so we can use useModalControls()

Cleanup 🧹

Tidied up embed-player.ts and the types there. It was getting a bit messy and had a bunch of details we didn't actually need.

@haileyok
Copy link
Member

haileyok commented Jan 4, 2024

As a side note, zod experience is fleeting. The way I did the schema (with all the .optional()) was the only way I could get it to not wipe previous settings store, but there might be an easier way to do it.

@pfrazee
Copy link
Collaborator Author

pfrazee commented Jan 4, 2024

As a side note, zod experience is fleeting. The way I did the schema (with all the .optional()) was the only way I could get it to not wipe previous settings store, but there might be an easier way to do it.

No yeah that's the move there. Old objects wont have that key

@pfrazee pfrazee merged commit 0dae24e into main Jan 5, 2024
4 checks passed
@pfrazee pfrazee deleted the haileyok-additional-embeds branch January 5, 2024 01:37
pull bot pushed a commit to imax9000/bsky-app-ua that referenced this pull request Jan 6, 2024
…-social#2424)

* add apple music embed

* add vimeo embed

* add logic for tenor and giphy embeds

* keep it simple, use playerUri for images too

* add gif embed player

* lint, fix tests

* remove links that can't produce a thumb

* Revert "remove links that can't produce a thumb"

This reverts commit 985b92b.

* Revert "Revert "remove links that can't produce a thumb""

This reverts commit 4895ded.

* Revert "Revert "Revert "remove links that can't produce a thumb"""

This reverts commit 36d04b5.

* properly obtain giphy metadata regardless of used url

* test fixes

* adjust gif player

* add all twitch embed types

* support m.youtube links

* few logic adjustments

* adjust spotify player height

* prefetch gif before showing

* use memory-disk cache policy on gifs

* use `disk` cachePolicy on ios - can't start/stop animation

* support pause/play on web

* onLoad fix

* remove extra pressable, add accessibility, fix scale issues

* improve size of embed

* add settings

* fix(?) settings

* add source to embed player params

* update tests

* better naming and settings options

* consent modal

* fix test id

* why is webstorm adding .tsx

* web modal

* simplify types

* adjust snap points

* remove unnecessary yt embed library. just use the webview always

* remove now useless WebGifStill 😭

* more type cleanup

* more type cleanup

* combine parse and prefs check in one memo

* improve dimensions of youtube shorts

* oops didn't commit the test 🫥

* add shorts as separate embed type

* fix up schema

* shorts modal

* hide gif details

* support localized spotify embeds

* more cleanup

* improve look and accessibility of gif embeds

* Update routing for the external embeds settings page

* Update and simplify the external embed preferences screen

* Update copy in embedconsent modal and add 'allow all' button

---------

Co-authored-by: Hailey <[email protected]>
estrattonbailey added a commit that referenced this pull request Jan 8, 2024
* origin: (45 commits)
  Fix splash config (#2452)
  convert prefix to lowercase in actor autocomplete query (#2431)
  support intl tenor links (#2438)
  Splash: reduce motion + dark mode (#2448)
  Bump android app version code
  Don't use mask for android at all (#2445)
  Update Korean localization (#2432)
  Use android mode, fix fallback (#2437)
  Update for Version 1.63 Português (BR) (#2435)
  New translations messages.po (Ukrainian) (#2422)
  Update Japanese localization (addition + language code correction) (#2423)
  1.63
  Additional embed sources and external-media consent controls (#2424)
  Reduce web requests (#2420)
  New user home feed fixes (#2421)
  E2E runner fixes (#2428)
  Recompile all locales (#2411) (#2416)
  Add Ukrainian localization (#2336)
  Update Portuguese for Português (#2414)
  Add Portuguese Localization (#2407)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants