Merge branch 'autoplay-videos' into 'develop'

Add preference to auto-play videos

See merge request soapbox-pub/soapbox!1882
This commit is contained in:
Chewbacca 2022-11-14 17:42:41 +00:00
commit 5d5a29b9f0
7 changed files with 85 additions and 28 deletions

View file

@ -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",

View file

@ -6,7 +6,8 @@ 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';
import type { Status, Attachment } from 'soapbox/types/entities';
@ -33,6 +34,9 @@ const StatusMedia: React.FC<IStatusMedia> = ({
onToggleVisibility = () => { },
}) => {
const dispatch = useAppDispatch();
const settings = useSettings();
const shouldAutoPlayVideo = settings.get('autoPlayVideo');
const [mediaWrapperWidth, setMediaWrapperWidth] = useState<number | undefined>(undefined);
const size = status.media_attachments.size;
@ -93,7 +97,9 @@ const StatusMedia: React.FC<IStatusMedia> = ({
ref={setRef}
className='status-card__image status-card-video'
style={height ? { height } : undefined}
dangerouslySetInnerHTML={{ __html: status.card.html }}
dangerouslySetInnerHTML={{
__html: shouldAutoPlayVideo ? addAutoPlay(status.card.html) : status.card.html,
}}
/>
</div>
);

View file

@ -193,6 +193,10 @@ const Preferences = () => {
<SettingToggle settings={settings} settingPath={['autoPlayGif']} onChange={onToggleChange} />
</ListItem>
<ListItem label={<FormattedMessage id='preferences.fields.auto_play_video_label' defaultMessage='Auto-play videos' />}>
<SettingToggle settings={settings} settingPath={['autoPlayVideo']} onChange={onToggleChange} />
</ListItem>
{features.spoilers && <ListItem label={<FormattedMessage id='preferences.fields.expand_spoilers_label' defaultMessage='Always expand posts marked with content warnings' />}>
<SettingToggle settings={settings} settingPath={['expandSpoilers']} onChange={onToggleChange} />
</ListItem>}

View file

@ -5,7 +5,9 @@ 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';
import type { Card as CardEntity, Attachment } from 'soapbox/types/entities';
@ -19,30 +21,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,
@ -64,6 +42,9 @@ const Card: React.FC<ICard> = ({
onOpenMedia,
horizontal,
}): JSX.Element => {
const settings = useSettings();
const shouldAutoPlayVideo = settings.get('autoPlayVideo');
const [width, setWidth] = useState(defaultWidth);
const [embedded, setEmbedded] = useState(false);
@ -111,7 +92,7 @@ const Card: React.FC<ICard> = ({
};
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;

View file

@ -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",

View file

@ -0,0 +1,31 @@
import { addAutoPlay } from '../media';
describe('addAutoPlay()', () => {
describe('when the provider is Rumble', () => {
it('adds the correct query parameters to the src', () => {
const html = '<iframe src="https://rumble.com/embed/123456/" width="1920" height="1080" frameborder="0" title="Video upload for 1" allowfullscreen=""></iframe>';
expect(addAutoPlay(html)).toEqual('<iframe src="https://rumble.com/embed/123456/?pub=7a20&amp;autoplay=2" width="1920" height="1080" frameborder="0" title="Video upload for 1" allowfullscreen=""></iframe>');
});
describe('when the iframe src already has params', () => {
it('adds the correct query parameters to the src', () => {
const html = '<iframe src="https://rumble.com/embed/123456/?foo=bar" width="1920" height="1080" frameborder="0" title="Video upload for 1" allowfullscreen=""></iframe>';
expect(addAutoPlay(html)).toEqual('<iframe src="https://rumble.com/embed/123456/?foo=bar&amp;pub=7a20&amp;autoplay=2" width="1920" height="1080" frameborder="0" title="Video upload for 1" allowfullscreen=""></iframe>');
});
});
});
describe('when the provider is not Rumble', () => {
it('adds the correct query parameters to the src', () => {
const html = '<iframe src="https://youtube.com/embed/123456/" width="1920" height="1080" frameborder="0" title="Video upload for 1" allowfullscreen=""></iframe>';
expect(addAutoPlay(html)).toEqual('<iframe src="https://youtube.com/embed/123456/?autoplay=1&amp;auto_play=1" width="1920" height="1080" frameborder="0" title="Video upload for 1" allowfullscreen=""></iframe>');
});
describe('when the iframe src already has params', () => {
it('adds the correct query parameters to the src', () => {
const html = '<iframe src="https://youtube.com/embed/123456?foo=bar" width="1920" height="1080" frameborder="0" title="Video upload for 1" allowfullscreen=""></iframe>';
expect(addAutoPlay(html)).toEqual('<iframe src="https://youtube.com/embed/123456?foo=bar&amp;autoplay=1&amp;auto_play=1" width="1920" height="1080" frameborder="0" title="Video upload for 1" allowfullscreen=""></iframe>');
});
});
});
});

View file

@ -51,4 +51,37 @@ const getVideoDuration = (file: File): Promise<number> => {
return promise;
};
export { getVideoDuration, formatBytes, truncateFilename };
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) {
const url = new URL(iframe.src);
const provider = new URL(iframe.src).host;
if (provider === VideoProviders.RUMBLE) {
url.searchParams.append('pub', '7a20');
url.searchParams.append('autoplay', '2');
} else {
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;
}
return html;
};
export { getVideoDuration, formatBytes, truncateFilename, addAutoPlay };