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

Adding extraction of videoID from youtube videos url which contain '/live/' #5426

Merged
merged 5 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/5416.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Refactoring the code for extraction of videoDetails from the video URL, adding code for extracting videoDetails from youtube video URLs with '/live/' in its URL which previously used to throw an error and adding jest tests for same. @IshaanDasgupta
74 changes: 52 additions & 22 deletions src/components/manage/Blocks/Video/Body.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,68 @@ import { Embed, Message } from 'semantic-ui-react';
import cx from 'classnames';
import { isInternalURL, flattenToAppURL } from '@plone/volto/helpers';

const Body = ({ data, isEditMode }) => {
let placeholder = data.preview_image
? isInternalURL(data.preview_image)
? `${flattenToAppURL(data.preview_image)}/@@images/image`
: data.preview_image
: null;

//Extracting videoID, listID and thumbnailURL from the video URL
const getVideoIDAndPlaceholder = (url) => {
let videoID = null;
let listID = null;
let thumbnailURL = null;

if (data.url) {
if (data.url.match('youtu')) {
if (data.url.match('list')) {
const matches = data.url.match(/^.*\?list=(.*)|^.*&list=(.*)$/);
if (url) {
if (url.match('youtu')) {
if (url.match('list')) {
const matches = url.match(/^.*\?list=(.*)|^.*&list=(.*)$/);
listID = matches[1] || matches[2];
} else {
videoID = data.url.match(/.be\//)
? data.url.match(/^.*\.be\/(.*)/)[1]
: data.url.match(/^.*\?v=(.*)$/)[1];

let thumbnailID = null;
if (url.match(/\?v=(.*)&list/)) {
thumbnailID = url.match(/^.*\?v=(.*)&list(.*)/)[1];
}
if (url.match(/\?v=(.*)\?list/)) {
thumbnailID = url.match(/^.*\?v=(.*)\?list(.*)/)[1];
}
thumbnailURL =
'https://img.youtube.com/vi/' + thumbnailID + '/sddefault.jpg';
} else if (url.match('live')) {
videoID = url.match(/^.*\/live\/(.*)/)[1];
} else if (url.match(/\.be\//)) {
videoID = url.match(/^.*\.be\/(.*)/)[1];
} else if (url.match(/\?v=/)) {
videoID = url.match(/^.*\?v=(.*)$/)[1];
}

if (!placeholder) {
if (videoID) {
let thumbnailID = videoID;
if (videoID.match(/\?si=/)) {
thumbnailID = videoID.match(/(.*)\?si=(.*)/)[1];
}
//load video preview image from youtube
placeholder =
'https://img.youtube.com/vi/' + videoID + '/sddefault.jpg';
thumbnailURL =
'https://img.youtube.com/vi/' + thumbnailID + '/sddefault.jpg';
}
} else if (data.url.match('vimeo')) {
videoID = data.url.match(/^.*\.com\/(.*)/)[1];
if (!placeholder) {
placeholder = 'https://vumbnail.com/' + videoID + '.jpg';
} else if (url.match('vimeo')) {
videoID = url.match(/^.*\.com\/(.*)/)[1];
if (videoID) {
let thumbnailID = videoID;
if (videoID.match(/\?si=/)) {
thumbnailID = videoID.match(/(.*)\?si=(.*)/)[1];
}
thumbnailURL = 'https://vumbnail.com/' + thumbnailID + '.jpg';
}
}
}
return { videoID, listID, thumbnailURL };
};

const Body = ({ data, isEditMode }) => {
let placeholder = data.preview_image
? isInternalURL(data.preview_image)
? `${flattenToAppURL(data.preview_image)}/@@images/image`
: data.preview_image
: null;

const { videoID, listID, thumbnailURL } = getVideoIDAndPlaceholder(data.url);

placeholder = !placeholder ? thumbnailURL : placeholder;

const ref = React.createRef();
const onKeyDown = (e) => {
Expand Down Expand Up @@ -130,3 +159,4 @@ Body.propTypes = {
};

export default Body;
export { getVideoIDAndPlaceholder };
167 changes: 167 additions & 0 deletions src/components/manage/Blocks/Video/Body.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import React from 'react';
import renderer from 'react-test-renderer';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
import Body from './Body';
import { getVideoIDAndPlaceholder } from './Body';
import config from '@plone/volto/registry';

config.blocks.blocksConfig = {
video: {
id: 'video',
title: 'Video',
group: 'media',
extensions: {},
variations: [],
restricted: false,
mostUsed: true,
sidebarTab: 1,
security: {
addPermission: [],
view: [],
},
},
};

const mockStore = configureStore();

test('renders a youtube video component with "list" in its url', () => {
const url =
'https://www.youtube.com/watch?v=KwRSRRyuk-Q&list=PLGN9BI-OAQkQmEqf6O8jeyoFY1b2hD1uL&index=1';
const videoDetails = getVideoIDAndPlaceholder(url);
expect(videoDetails).toEqual({
videoID: null,
listID: 'PLGN9BI-OAQkQmEqf6O8jeyoFY1b2hD1uL&index=1',
thumbnailURL: 'https://img.youtube.com/vi/KwRSRRyuk-Q/sddefault.jpg',
});
});

test('extracts video details from a youtube video with "/live/" in its url', () => {
const url = 'https://www.youtube.com/live/ISdHvS6Ck3k?si=COeVakmC1lI6jQy3';
const videoDetails = getVideoIDAndPlaceholder(url);
expect(videoDetails).toEqual({
videoID: 'ISdHvS6Ck3k?si=COeVakmC1lI6jQy3',
listID: null,
thumbnailURL: 'https://img.youtube.com/vi/ISdHvS6Ck3k/sddefault.jpg',
});
});

test('extracts video details from a youtube video with ".be/" in its url', () => {
const url = 'https://youtu.be/P9j-xYdWT28?si=zZ2putStJbPBLCdt';
const videoDetails = getVideoIDAndPlaceholder(url);
expect(videoDetails).toEqual({
videoID: 'P9j-xYdWT28?si=zZ2putStJbPBLCdt',
listID: null,
thumbnailURL: 'https://img.youtube.com/vi/P9j-xYdWT28/sddefault.jpg',
});
});

test('extracts video details from a youtube video with "?v=" in its url', () => {
const url = 'https://www.youtube.com/watch?v=KUd6e105u_I';
const videoDetails = getVideoIDAndPlaceholder(url);
expect(videoDetails).toEqual({
videoID: 'KUd6e105u_I',
listID: null,
thumbnailURL: 'https://img.youtube.com/vi/KUd6e105u_I/sddefault.jpg',
});
});

test('extracts video details from a vimeo video url', () => {
const url = 'https://vimeo.com/639449679';
const videoDetails = getVideoIDAndPlaceholder(url);
expect(videoDetails).toEqual({
videoID: '639449679',
listID: null,
thumbnailURL: 'https://vumbnail.com/639449679.jpg',
});
});

test('renders a youtube video body component', () => {
const store = mockStore({
intl: {
locale: 'en',
messages: {},
},
});

const component = renderer.create(
<Provider store={store}>
<Body
data={{
'@type': 'video',
url: 'https://www.youtube.com/watch?v=KwRSRRyuk-Q&list=PLGN9BI-OAQkQmEqf6O8jeyoFY1b2hD1uL&index=1',
}}
/>
</Provider>,
);
const json = component.toJSON();
expect(json).toMatchSnapshot();
});

test('renders a youtube video body component in edit mode', () => {
const store = mockStore({
intl: {
locale: 'en',
messages: {},
},
});

const component = renderer.create(
<Provider store={store}>
<Body
data={{
'@type': 'video',
url: 'https://www.youtube.com/watch?v=KwRSRRyuk-Q&list=PLGN9BI-OAQkQmEqf6O8jeyoFY1b2hD1uL&index=1',
}}
isEditMode={true}
/>
</Provider>,
);
const json = component.toJSON();
expect(json).toMatchSnapshot();
});

test('renders invalid video body component with invalid URL', () => {
const store = mockStore({
intl: {
locale: 'en',
messages: {},
},
});

const component = renderer.create(
<Provider store={store}>
<Body
data={{
'@type': 'video',
url: 'https://www.google.com',
}}
/>
</Provider>,
);
const json = component.toJSON();
expect(json).toMatchSnapshot();
});

test('renders a error message for invalid video URL in edit mode', () => {
const store = mockStore({
intl: {
locale: 'en',
messages: {},
},
});

const component = renderer.create(
<Provider store={store}>
<Body
data={{
'@type': 'video',
url: 'https://www.google.com',
}}
isEditMode={true}
/>
</Provider>,
);
const json = component.toJSON();
expect(json).toMatchSnapshot();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renders a error message for invalid video URL in edit mode 1`] = `
<div
className="video-inner"
>
<div>
<div
className="ui message"
>
<center>
Please enter a valid URL by deleting the block and adding a new video block.
</center>
</div>
</div>
</div>
`;

exports[`renders a youtube video body component 1`] = `
<div
className="video-inner"
>
<div
className="ui 16:9 embed"
onClick={[Function]}
onKeyPress={[Function]}
tabIndex={0}
>
<i
aria-hidden="true"
className="play icon"
onClick={[Function]}
/>
<img
className="placeholder"
src="https://img.youtube.com/vi/KwRSRRyuk-Q/sddefault.jpg"
/>
</div>
</div>
`;

exports[`renders a youtube video body component in edit mode 1`] = `
<div
className="video-inner"
>
<div
className="ui 16:9 embed"
onClick={[Function]}
onKeyPress={[Function]}
tabIndex={0}
>
<i
aria-hidden="true"
className="play icon"
onClick={[Function]}
/>
<img
className="placeholder"
src="https://img.youtube.com/vi/KwRSRRyuk-Q/sddefault.jpg"
/>
</div>
</div>
`;

exports[`renders invalid video body component with invalid URL 1`] = `
<div
className="video-inner"
>
<div
className="invalidVideoFormat"
/>
</div>
`;