From 4a4e0daa1a8d1a78d4d1cb823ce310882317c8cd Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Fri, 11 Nov 2022 09:57:39 -0500 Subject: [PATCH 1/3] Autoplay videos from Rumble --- app/soapbox/components/status-media.tsx | 3 +- .../features/status/components/card.tsx | 25 +--------------- app/soapbox/utils/__tests__/media.test.ts | 17 +++++++++++ app/soapbox/utils/media.ts | 30 ++++++++++++++++++- 4 files changed, 49 insertions(+), 26 deletions(-) create mode 100644 app/soapbox/utils/__tests__/media.test.ts diff --git a/app/soapbox/components/status-media.tsx b/app/soapbox/components/status-media.tsx index 4445e67b5..195e749f1 100644 --- a/app/soapbox/components/status-media.tsx +++ b/app/soapbox/components/status-media.tsx @@ -7,6 +7,7 @@ import Card from 'soapbox/features/status/components/card'; import Bundle from 'soapbox/features/ui/components/bundle'; import { MediaGallery, Video, Audio } from 'soapbox/features/ui/util/async-components'; import { useAppDispatch } from 'soapbox/hooks'; +import { addAutoPlay } from 'soapbox/utils/media'; import type { List as ImmutableList } from 'immutable'; import type { Status, Attachment } from 'soapbox/types/entities'; @@ -93,7 +94,7 @@ const StatusMedia: React.FC = ({ ref={setRef} className='status-card__image status-card-video' style={height ? { height } : undefined} - dangerouslySetInnerHTML={{ __html: status.card.html }} + dangerouslySetInnerHTML={{ __html: addAutoPlay(status.card.html) }} /> ); diff --git a/app/soapbox/features/status/components/card.tsx b/app/soapbox/features/status/components/card.tsx index 3d83020fe..7e976e5bb 100644 --- a/app/soapbox/features/status/components/card.tsx +++ b/app/soapbox/features/status/components/card.tsx @@ -6,6 +6,7 @@ import Blurhash from 'soapbox/components/blurhash'; import Icon from 'soapbox/components/icon'; import { HStack, Stack, Text } from 'soapbox/components/ui'; import { normalizeAttachment } from 'soapbox/normalizers'; +import { addAutoPlay } from 'soapbox/utils/media'; import type { Card as CardEntity, Attachment } from 'soapbox/types/entities'; @@ -19,30 +20,6 @@ const trim = (text: string, len: number): string => { return text.substring(0, cut) + (text.length > len ? '…' : ''); }; -const domParser = new DOMParser(); - -const addAutoPlay = (html: string): string => { - const document = domParser.parseFromString(html, 'text/html').documentElement; - const iframe = document.querySelector('iframe'); - - if (iframe) { - if (iframe.src.includes('?')) { - iframe.src += '&'; - } else { - iframe.src += '?'; - } - - iframe.src += 'autoplay=1&auto_play=1'; - iframe.allow = 'autoplay'; - - // DOM parser creates html/body elements around original HTML fragment, - // so we need to get innerHTML out of the body and not the entire document - return (document.querySelector('body') as HTMLBodyElement).innerHTML; - } - - return html; -}; - interface ICard { card: CardEntity, maxTitle?: number, diff --git a/app/soapbox/utils/__tests__/media.test.ts b/app/soapbox/utils/__tests__/media.test.ts new file mode 100644 index 000000000..815b12321 --- /dev/null +++ b/app/soapbox/utils/__tests__/media.test.ts @@ -0,0 +1,17 @@ +import { addAutoPlay } from '../media'; + +describe('addAutoPlay()', () => { + describe('when the provider is Rumble', () => { + it('adds the correct query parameters to the src', () => { + const html = ''; + expect(addAutoPlay(html)).toEqual(''); + }); + }); + + describe('when the provider is not Rumble', () => { + it('adds the correct query parameters to the src', () => { + const html = ''; + expect(addAutoPlay(html)).toEqual(''); + }); + }); +}); diff --git a/app/soapbox/utils/media.ts b/app/soapbox/utils/media.ts index 7947ef91f..27a1f6ba0 100644 --- a/app/soapbox/utils/media.ts +++ b/app/soapbox/utils/media.ts @@ -51,4 +51,32 @@ const getVideoDuration = (file: File): Promise => { return promise; }; -export { getVideoDuration, formatBytes, truncateFilename }; +const domParser = new DOMParser(); + +const addAutoPlay = (html: string): string => { + const document = domParser.parseFromString(html, 'text/html').documentElement; + const iframe = document.querySelector('iframe'); + + if (iframe) { + if (iframe.src.includes('?')) { + iframe.src += '&'; + } else { + iframe.src += '?'; + } + + if (new URL(iframe.src).host === 'rumble.com') { + iframe.src += 'pub=7a20&autoplay=2'; + } else { + iframe.src += 'autoplay=1&auto_play=1'; + iframe.allow = 'autoplay'; + } + + // DOM parser creates html/body elements around original HTML fragment, + // so we need to get innerHTML out of the body and not the entire document + return (document.querySelector('body') as HTMLBodyElement).innerHTML; + } + + return html; +}; + +export { getVideoDuration, formatBytes, truncateFilename, addAutoPlay }; From 5c6ae4d6da1af2ab671efe20d6b9072142c2eaec Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Fri, 11 Nov 2022 10:21:32 -0500 Subject: [PATCH 2/3] Make auto-play video optional config --- app/soapbox/__fixtures__/intlMessages.json | 1 + app/soapbox/components/status-media.tsx | 9 +++++++-- app/soapbox/features/preferences/index.tsx | 4 ++++ app/soapbox/features/status/components/card.tsx | 6 +++++- app/soapbox/locales/en.json | 1 + 5 files changed, 18 insertions(+), 3 deletions(-) diff --git a/app/soapbox/__fixtures__/intlMessages.json b/app/soapbox/__fixtures__/intlMessages.json index 54c919e6a..eb2a35806 100644 --- a/app/soapbox/__fixtures__/intlMessages.json +++ b/app/soapbox/__fixtures__/intlMessages.json @@ -319,6 +319,7 @@ "poll_button.add_poll": "Add a poll", "poll_button.remove_poll": "Remove poll", "preferences.fields.auto_play_gif_label": "Auto-play animated GIFs", + "preferences.fields.auto_play_video_label": "Auto-play videos", "preferences.fields.boost_modal_label": "Show confirmation dialog before reposting", "preferences.fields.delete_modal_label": "Show confirmation dialog before deleting a post", "preferences.fields.demetricator_label": "Use Demetricator", diff --git a/app/soapbox/components/status-media.tsx b/app/soapbox/components/status-media.tsx index 195e749f1..b159d4fe5 100644 --- a/app/soapbox/components/status-media.tsx +++ b/app/soapbox/components/status-media.tsx @@ -6,7 +6,7 @@ import PlaceholderCard from 'soapbox/features/placeholder/components/placeholder import Card from 'soapbox/features/status/components/card'; import Bundle from 'soapbox/features/ui/components/bundle'; import { MediaGallery, Video, Audio } from 'soapbox/features/ui/util/async-components'; -import { useAppDispatch } from 'soapbox/hooks'; +import { useAppDispatch, useSettings } from 'soapbox/hooks'; import { addAutoPlay } from 'soapbox/utils/media'; import type { List as ImmutableList } from 'immutable'; @@ -34,6 +34,9 @@ const StatusMedia: React.FC = ({ onToggleVisibility = () => { }, }) => { const dispatch = useAppDispatch(); + const settings = useSettings(); + const shouldAutoPlayVideo = settings.get('autoPlayVideo'); + const [mediaWrapperWidth, setMediaWrapperWidth] = useState(undefined); const size = status.media_attachments.size; @@ -94,7 +97,9 @@ const StatusMedia: React.FC = ({ ref={setRef} className='status-card__image status-card-video' style={height ? { height } : undefined} - dangerouslySetInnerHTML={{ __html: addAutoPlay(status.card.html) }} + dangerouslySetInnerHTML={{ + __html: shouldAutoPlayVideo ? addAutoPlay(status.card.html) : status.card.html, + }} /> ); diff --git a/app/soapbox/features/preferences/index.tsx b/app/soapbox/features/preferences/index.tsx index 3adb90861..98221a8fd 100644 --- a/app/soapbox/features/preferences/index.tsx +++ b/app/soapbox/features/preferences/index.tsx @@ -193,6 +193,10 @@ const Preferences = () => { + }> + + + {features.spoilers && }> } diff --git a/app/soapbox/features/status/components/card.tsx b/app/soapbox/features/status/components/card.tsx index 7e976e5bb..18dd427a5 100644 --- a/app/soapbox/features/status/components/card.tsx +++ b/app/soapbox/features/status/components/card.tsx @@ -5,6 +5,7 @@ import React, { useState, useEffect } from 'react'; import Blurhash from 'soapbox/components/blurhash'; import Icon from 'soapbox/components/icon'; import { HStack, Stack, Text } from 'soapbox/components/ui'; +import { useSettings } from 'soapbox/hooks'; import { normalizeAttachment } from 'soapbox/normalizers'; import { addAutoPlay } from 'soapbox/utils/media'; @@ -41,6 +42,9 @@ const Card: React.FC = ({ onOpenMedia, horizontal, }): JSX.Element => { + const settings = useSettings(); + const shouldAutoPlayVideo = settings.get('autoPlayVideo'); + const [width, setWidth] = useState(defaultWidth); const [embedded, setEmbedded] = useState(false); @@ -88,7 +92,7 @@ const Card: React.FC = ({ }; const renderVideo = () => { - const content = { __html: addAutoPlay(card.html) }; + const content = { __html: shouldAutoPlayVideo ? addAutoPlay(card.html) : card.html }; const ratio = getRatio(card); const height = width / ratio; diff --git a/app/soapbox/locales/en.json b/app/soapbox/locales/en.json index f2eaf32c8..b7c280137 100644 --- a/app/soapbox/locales/en.json +++ b/app/soapbox/locales/en.json @@ -790,6 +790,7 @@ "poll_button.remove_poll": "Remove poll", "pre_header.close": "Close", "preferences.fields.auto_play_gif_label": "Auto-play animated GIFs", + "preferences.fields.auto_play_video_label": "Auto-play videos", "preferences.fields.autoload_more_label": "Automatically load more items when scrolled to the bottom of the page", "preferences.fields.autoload_timelines_label": "Automatically load new posts when scrolled to the top of the page", "preferences.fields.boost_modal_label": "Show confirmation dialog before reposting", From c421819202fb84813c87da019436e194a43891c1 Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Mon, 14 Nov 2022 11:50:21 -0500 Subject: [PATCH 3/3] Refactor to use URL and URLSearchParams --- app/soapbox/utils/__tests__/media.test.ts | 14 ++++++++++++++ app/soapbox/utils/media.ts | 21 +++++++++++++-------- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/app/soapbox/utils/__tests__/media.test.ts b/app/soapbox/utils/__tests__/media.test.ts index 815b12321..56a02aebc 100644 --- a/app/soapbox/utils/__tests__/media.test.ts +++ b/app/soapbox/utils/__tests__/media.test.ts @@ -6,6 +6,13 @@ describe('addAutoPlay()', () => { const html = ''; expect(addAutoPlay(html)).toEqual(''); }); + + describe('when the iframe src already has params', () => { + it('adds the correct query parameters to the src', () => { + const html = ''; + expect(addAutoPlay(html)).toEqual(''); + }); + }); }); describe('when the provider is not Rumble', () => { @@ -13,5 +20,12 @@ describe('addAutoPlay()', () => { const html = ''; expect(addAutoPlay(html)).toEqual(''); }); + + describe('when the iframe src already has params', () => { + it('adds the correct query parameters to the src', () => { + const html = ''; + expect(addAutoPlay(html)).toEqual(''); + }); + }); }); }); diff --git a/app/soapbox/utils/media.ts b/app/soapbox/utils/media.ts index 27a1f6ba0..ff0bf502e 100644 --- a/app/soapbox/utils/media.ts +++ b/app/soapbox/utils/media.ts @@ -53,24 +53,29 @@ const getVideoDuration = (file: File): Promise => { const domParser = new DOMParser(); +enum VideoProviders { + RUMBLE = 'rumble.com' +} + const addAutoPlay = (html: string): string => { const document = domParser.parseFromString(html, 'text/html').documentElement; const iframe = document.querySelector('iframe'); if (iframe) { - if (iframe.src.includes('?')) { - iframe.src += '&'; - } else { - iframe.src += '?'; - } + const url = new URL(iframe.src); + const provider = new URL(iframe.src).host; - if (new URL(iframe.src).host === 'rumble.com') { - iframe.src += 'pub=7a20&autoplay=2'; + if (provider === VideoProviders.RUMBLE) { + url.searchParams.append('pub', '7a20'); + url.searchParams.append('autoplay', '2'); } else { - iframe.src += 'autoplay=1&auto_play=1'; + url.searchParams.append('autoplay', '1'); + url.searchParams.append('auto_play', '1'); iframe.allow = 'autoplay'; } + iframe.src = url.toString(); + // DOM parser creates html/body elements around original HTML fragment, // so we need to get innerHTML out of the body and not the entire document return (document.querySelector('body') as HTMLBodyElement).innerHTML;