From 0bacf37998904278b46bca17d6029f054150fc51 Mon Sep 17 00:00:00 2001 From: olga-jwp Date: Wed, 20 Dec 2023 19:21:43 +0100 Subject: [PATCH 01/16] feat: add user_id and profile_id for CDN analytics --- src/components/Player/Player.tsx | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/components/Player/Player.tsx b/src/components/Player/Player.tsx index 249c4000a..4573788d9 100644 --- a/src/components/Player/Player.tsx +++ b/src/components/Player/Player.tsx @@ -11,6 +11,8 @@ import useOttAnalytics from '#src/hooks/useOttAnalytics'; import { logDev, testId } from '#src/utils/common'; import { useConfigStore } from '#src/stores/ConfigStore'; import type { AdSchedule } from '#types/ad-schedule'; +import { useAccountStore } from '#src/stores/AccountStore'; +import { useProfileStore } from '#src/stores/ProfileStore'; type Props = { feedId?: string; @@ -56,6 +58,11 @@ const Player: React.FC = ({ const loadingRef = useRef(false); const [libLoaded, setLibLoaded] = useState(!!window.jwplayer); const startTimeRef = useRef(startTime); + + const { config } = useConfigStore((s) => s); + const { user } = useAccountStore((s) => s); + const { profile } = useProfileStore(); + const setPlayer = useOttAnalytics(item, feedId); const { settings } = useConfigStore((s) => s); @@ -63,6 +70,10 @@ const Player: React.FC = ({ const playerId = settings.playerId; const playerLicenseKey = settings.playerLicenseKey; + const isJwIntegration = config?.integrations?.jwp; + const userId = user?.id; + const profileId = profile?.id; + const handleBeforePlay = useEventCallback(onBeforePlay); const handlePlay = useEventCallback(onPlay); const handlePause = useEventCallback(onPause); @@ -161,6 +172,23 @@ const Player: React.FC = ({ playerRef.current = window.jwplayer(playerElementRef.current) as JWPlayer; + // Add user_id and profile_id for CDN analytics + const { sources } = item; + + sources?.map((source) => { + const url = new URL(source.file); + const isVOD = url.pathname.includes('manifests'); + const isBCL = url.pathname.includes('broadcast'); + const isDRM = url.searchParams.has('exp') || url.searchParams.has('sig'); + + if ((isVOD || isBCL) && !isDRM && userId) { + url.searchParams.set('user_id', userId); + if (isJwIntegration && profileId) url.searchParams.append('profile_id', profileId); + } + + source.file = url.toString(); + }); + // Player options are untyped const playerOptions: { [key: string]: unknown } = { advertising: { @@ -213,7 +241,7 @@ const Player: React.FC = ({ if (libLoaded) { initializePlayer(); } - }, [libLoaded, item, detachEvents, attachEvents, playerId, setPlayer, autostart, adsData, playerLicenseKey, feedId]); + }, [libLoaded, item, detachEvents, attachEvents, playerId, setPlayer, autostart, adsData, playerLicenseKey, feedId, isJwIntegration, profileId, userId]); useEffect(() => { return () => { From 114150e6cdd8aaf436c06df30ea785f458b641f0 Mon Sep 17 00:00:00 2001 From: olga-jwp Date: Thu, 11 Jan 2024 18:05:56 +0100 Subject: [PATCH 02/16] feat: add user_id and profile_id for CDN analytics --- src/components/Player/Player.tsx | 22 +++++----------------- src/utils/player.ts | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 17 deletions(-) create mode 100644 src/utils/player.ts diff --git a/src/components/Player/Player.tsx b/src/components/Player/Player.tsx index 4573788d9..e062e8d10 100644 --- a/src/components/Player/Player.tsx +++ b/src/components/Player/Player.tsx @@ -13,6 +13,7 @@ import { useConfigStore } from '#src/stores/ConfigStore'; import type { AdSchedule } from '#types/ad-schedule'; import { useAccountStore } from '#src/stores/AccountStore'; import { useProfileStore } from '#src/stores/ProfileStore'; +import { attachAnalyticsParams } from '#src/utils/player'; type Props = { feedId?: string; @@ -70,7 +71,7 @@ const Player: React.FC = ({ const playerId = settings.playerId; const playerLicenseKey = settings.playerLicenseKey; - const isJwIntegration = config?.integrations?.jwp; + const isJwIntegration = !!config?.integrations?.jwp; const userId = user?.id; const profileId = profile?.id; @@ -172,22 +173,9 @@ const Player: React.FC = ({ playerRef.current = window.jwplayer(playerElementRef.current) as JWPlayer; - // Add user_id and profile_id for CDN analytics - const { sources } = item; - - sources?.map((source) => { - const url = new URL(source.file); - const isVOD = url.pathname.includes('manifests'); - const isBCL = url.pathname.includes('broadcast'); - const isDRM = url.searchParams.has('exp') || url.searchParams.has('sig'); - - if ((isVOD || isBCL) && !isDRM && userId) { - url.searchParams.set('user_id', userId); - if (isJwIntegration && profileId) url.searchParams.append('profile_id', profileId); - } - - source.file = url.toString(); - }); + // Inject user_id and profile_id into the CDN analytics + const { sources, mediaid } = item; + attachAnalyticsParams(sources, mediaid, isJwIntegration, userId, profileId); // Player options are untyped const playerOptions: { [key: string]: unknown } = { diff --git a/src/utils/player.ts b/src/utils/player.ts new file mode 100644 index 000000000..d9faf4378 --- /dev/null +++ b/src/utils/player.ts @@ -0,0 +1,21 @@ +import type { Source } from '#types/playlist'; + +export const attachAnalyticsParams = (sources: Source[], mediaid: string, isJwIntegration: boolean, userId?: string, profileId?: string) => { + return sources.map((source: Source) => { + const url = new URL(source.file); + + // Attach user_id and profile_id only for VOD and BCL SaaS Live Streams + const isVOD = url.href === `https://cdn.jwplayer.com/manifests/${mediaid}.m3u8`; + const isBCL = url.href === `https://content.jwplatform.com/live/broadcast/${mediaid}.m3u8`; + + if ((isVOD || isBCL) && userId) { + url.searchParams.set('user_id', userId); + + if (isJwIntegration && profileId) { + url.searchParams.append('profile_id', profileId); + } + } + + source.file = url.toString(); + }); +}; From d92937da7592c580df8230a4c1ee2fad3765ff55 Mon Sep 17 00:00:00 2001 From: olga-jwp Date: Fri, 12 Jan 2024 16:26:20 +0100 Subject: [PATCH 03/16] feat: cdn analytics code cleanup --- src/components/Player/Player.tsx | 5 ++--- src/utils/{player.ts => analytics.ts} | 8 +++++--- 2 files changed, 7 insertions(+), 6 deletions(-) rename src/utils/{player.ts => analytics.ts} (65%) diff --git a/src/components/Player/Player.tsx b/src/components/Player/Player.tsx index e062e8d10..98efda785 100644 --- a/src/components/Player/Player.tsx +++ b/src/components/Player/Player.tsx @@ -13,7 +13,7 @@ import { useConfigStore } from '#src/stores/ConfigStore'; import type { AdSchedule } from '#types/ad-schedule'; import { useAccountStore } from '#src/stores/AccountStore'; import { useProfileStore } from '#src/stores/ProfileStore'; -import { attachAnalyticsParams } from '#src/utils/player'; +import { attachAnalyticsParams } from '#src/utils/analytics'; type Props = { feedId?: string; @@ -174,8 +174,7 @@ const Player: React.FC = ({ playerRef.current = window.jwplayer(playerElementRef.current) as JWPlayer; // Inject user_id and profile_id into the CDN analytics - const { sources, mediaid } = item; - attachAnalyticsParams(sources, mediaid, isJwIntegration, userId, profileId); + attachAnalyticsParams(item, isJwIntegration, userId, profileId); // Player options are untyped const playerOptions: { [key: string]: unknown } = { diff --git a/src/utils/player.ts b/src/utils/analytics.ts similarity index 65% rename from src/utils/player.ts rename to src/utils/analytics.ts index d9faf4378..0b46edb5e 100644 --- a/src/utils/player.ts +++ b/src/utils/analytics.ts @@ -1,6 +1,8 @@ -import type { Source } from '#types/playlist'; +import type { PlaylistItem, Source } from '#types/playlist'; + +export const attachAnalyticsParams = (item: PlaylistItem, isJwIntegration: boolean, userId?: string, profileId?: string) => { + const { sources, mediaid } = item; -export const attachAnalyticsParams = (sources: Source[], mediaid: string, isJwIntegration: boolean, userId?: string, profileId?: string) => { return sources.map((source: Source) => { const url = new URL(source.file); @@ -12,7 +14,7 @@ export const attachAnalyticsParams = (sources: Source[], mediaid: string, isJwIn url.searchParams.set('user_id', userId); if (isJwIntegration && profileId) { - url.searchParams.append('profile_id', profileId); + url.searchParams.set('profile_id', profileId); } } From 68d31c46df3b5999f8bee79ed269e597b30993bf Mon Sep 17 00:00:00 2001 From: olga-jwp Date: Fri, 12 Jan 2024 17:33:59 +0100 Subject: [PATCH 04/16] fix: transform url to lowerCase --- src/utils/analytics.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/utils/analytics.ts b/src/utils/analytics.ts index 0b46edb5e..3deaa69c1 100644 --- a/src/utils/analytics.ts +++ b/src/utils/analytics.ts @@ -6,9 +6,12 @@ export const attachAnalyticsParams = (item: PlaylistItem, isJwIntegration: boole return sources.map((source: Source) => { const url = new URL(source.file); + const mediaId = mediaid.toLowerCase(); + const sourceUrl = url.href.toLowerCase(); + // Attach user_id and profile_id only for VOD and BCL SaaS Live Streams - const isVOD = url.href === `https://cdn.jwplayer.com/manifests/${mediaid}.m3u8`; - const isBCL = url.href === `https://content.jwplatform.com/live/broadcast/${mediaid}.m3u8`; + const isVOD = sourceUrl === `https://cdn.jwplayer.com/manifests/${mediaId}.m3u8`; + const isBCL = sourceUrl === `https://content.jwplatform.com/live/broadcast/${mediaId}.m3u8`; if ((isVOD || isBCL) && userId) { url.searchParams.set('user_id', userId); From aeef40f6f375783cd0820fd27154b616570d3cf0 Mon Sep 17 00:00:00 2001 From: olga-jwp Date: Fri, 12 Jan 2024 17:45:24 +0100 Subject: [PATCH 05/16] feat: cdn analytics code cleanup --- src/components/Player/Player.tsx | 14 ++------------ src/utils/analytics.ts | 13 ++++++++++++- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/components/Player/Player.tsx b/src/components/Player/Player.tsx index 98efda785..a8e51fbf9 100644 --- a/src/components/Player/Player.tsx +++ b/src/components/Player/Player.tsx @@ -11,8 +11,6 @@ import useOttAnalytics from '#src/hooks/useOttAnalytics'; import { logDev, testId } from '#src/utils/common'; import { useConfigStore } from '#src/stores/ConfigStore'; import type { AdSchedule } from '#types/ad-schedule'; -import { useAccountStore } from '#src/stores/AccountStore'; -import { useProfileStore } from '#src/stores/ProfileStore'; import { attachAnalyticsParams } from '#src/utils/analytics'; type Props = { @@ -60,10 +58,6 @@ const Player: React.FC = ({ const [libLoaded, setLibLoaded] = useState(!!window.jwplayer); const startTimeRef = useRef(startTime); - const { config } = useConfigStore((s) => s); - const { user } = useAccountStore((s) => s); - const { profile } = useProfileStore(); - const setPlayer = useOttAnalytics(item, feedId); const { settings } = useConfigStore((s) => s); @@ -71,10 +65,6 @@ const Player: React.FC = ({ const playerId = settings.playerId; const playerLicenseKey = settings.playerLicenseKey; - const isJwIntegration = !!config?.integrations?.jwp; - const userId = user?.id; - const profileId = profile?.id; - const handleBeforePlay = useEventCallback(onBeforePlay); const handlePlay = useEventCallback(onPlay); const handlePause = useEventCallback(onPause); @@ -174,7 +164,7 @@ const Player: React.FC = ({ playerRef.current = window.jwplayer(playerElementRef.current) as JWPlayer; // Inject user_id and profile_id into the CDN analytics - attachAnalyticsParams(item, isJwIntegration, userId, profileId); + attachAnalyticsParams(item); // Player options are untyped const playerOptions: { [key: string]: unknown } = { @@ -228,7 +218,7 @@ const Player: React.FC = ({ if (libLoaded) { initializePlayer(); } - }, [libLoaded, item, detachEvents, attachEvents, playerId, setPlayer, autostart, adsData, playerLicenseKey, feedId, isJwIntegration, profileId, userId]); + }, [libLoaded, item, detachEvents, attachEvents, playerId, setPlayer, autostart, adsData, playerLicenseKey, feedId]); useEffect(() => { return () => { diff --git a/src/utils/analytics.ts b/src/utils/analytics.ts index 3deaa69c1..11501f7ee 100644 --- a/src/utils/analytics.ts +++ b/src/utils/analytics.ts @@ -1,8 +1,19 @@ +import { useAccountStore } from '#src/stores/AccountStore'; +import { useConfigStore } from '#src/stores/ConfigStore'; +import { useProfileStore } from '#src/stores/ProfileStore'; import type { PlaylistItem, Source } from '#types/playlist'; -export const attachAnalyticsParams = (item: PlaylistItem, isJwIntegration: boolean, userId?: string, profileId?: string) => { +export const attachAnalyticsParams = (item: PlaylistItem) => { + const { config } = useConfigStore.getState(); + const { user } = useAccountStore.getState(); + const { profile } = useProfileStore.getState(); + const { sources, mediaid } = item; + const userId = user?.id; + const profileId = profile?.id; + const isJwIntegration = !!config?.integrations?.jwp; + return sources.map((source: Source) => { const url = new URL(source.file); From ff3b2bcfc3c237df0973d57acb9f4f3757ed602d Mon Sep 17 00:00:00 2001 From: borkopetrovicc <104987342+borkopetrovicc@users.noreply.github.com> Date: Tue, 16 Jan 2024 16:59:49 +0100 Subject: [PATCH 06/16] Chore: Viewers are able to subvert the Simultaneous login limit (#419) * fix: logout behavior in useNotifications hook * fix: added try and await --- src/hooks/useNotifications.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/hooks/useNotifications.ts b/src/hooks/useNotifications.ts index 834abcc52..0415cd6e0 100644 --- a/src/hooks/useNotifications.ts +++ b/src/hooks/useNotifications.ts @@ -60,10 +60,12 @@ export default async function useNotifications(uuid: string = '') { window.location.href = notification.resource?.redirect_to_url; break; case NotificationsTypes.ACCOUNT_LOGOUT: - if (notification.resource?.reason === 'sessions_limit') { - navigate(addQueryParams(window.location.pathname, { u: 'login', message: simultaneousLoginWarningKey })); - } else { - await accountController.logout(); + try { + await accountController?.logout(); + } finally { + if (notification.resource?.reason === 'sessions_limit') { + navigate(addQueryParams(window.location.pathname, { u: 'login', message: simultaneousLoginWarningKey })); + } } break; default: From 6281640fc4fb0f6f7b3b56cc34749465348cc7a2 Mon Sep 17 00:00:00 2001 From: mirovladimitrovski Date: Wed, 17 Jan 2024 13:26:07 +0100 Subject: [PATCH 07/16] fix: place consents in appropriate section --- src/components/Account/Account.tsx | 2 +- .../__snapshots__/Account.test.tsx.snap | 48 +++++-------------- 2 files changed, 13 insertions(+), 37 deletions(-) diff --git a/src/components/Account/Account.tsx b/src/components/Account/Account.tsx index 5342090d0..32af8496b 100644 --- a/src/components/Account/Account.tsx +++ b/src/components/Account/Account.tsx @@ -78,7 +78,7 @@ const Account = ({ panelClassName, panelHeaderClassName, canUpdateEmail = true } const nonTerms: Consent[] = []; publisherConsents?.forEach((consent) => { - if (consent?.type === 'checkbox') { + if (!consent?.type || consent?.type === 'checkbox') { terms.push(consent); } else { nonTerms.push(consent); diff --git a/src/components/Account/__snapshots__/Account.test.tsx.snap b/src/components/Account/__snapshots__/Account.test.tsx.snap index 57fdb4bda..2a20a13d7 100644 --- a/src/components/Account/__snapshots__/Account.test.tsx.snap +++ b/src/components/Account/__snapshots__/Account.test.tsx.snap @@ -134,51 +134,27 @@ exports[` > renders and matches snapshot 1`] = ` account.terms_and_tracking -
-
-
-
-
-

- account.other_registration_details -

-
-
+
+ Receive Marketing Emails +
From ba1e0de5b60c8a5bfed1e52bacaf15d977b69894 Mon Sep 17 00:00:00 2001 From: mirovladimitrovski Date: Wed, 17 Jan 2024 14:10:28 +0100 Subject: [PATCH 08/16] fix: fix e2e test --- test-e2e/tests/account_test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-e2e/tests/account_test.ts b/test-e2e/tests/account_test.ts index d5979e09b..519575898 100644 --- a/test-e2e/tests/account_test.ts +++ b/test-e2e/tests/account_test.ts @@ -253,7 +253,7 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string, res Scenario(`I can update my consents - ${providerName}`, async ({ I }) => { I.amOnPage(constants.accountsUrl); I.waitForText('Profile info', longTimeout); - I.scrollTo('//*[text() = "Other registration details"]'); + I.scrollTo('//*[text() = "Legal & Marketing"]', undefined, -100); I.dontSeeCheckboxIsChecked(consentCheckbox); I.dontSee('Save'); From e1003844da97ba97618ed46e9eba076e49a6159c Mon Sep 17 00:00:00 2001 From: Anton Lantukh Date: Fri, 12 Jan 2024 13:20:58 +0100 Subject: [PATCH 09/16] feat(project): change content type default schemas - use 'live_bcl' for streams - remove sections from 'episode' schema - update 'hub' schema - update 'liveChannel' and 'liveEvent' schemas - update `trailer` schema --- scripts/content-types/content-types.json | 242 +++++++----------- src/config.ts | 2 +- src/pages/ScreenRouting/MediaScreenRouter.tsx | 2 +- 3 files changed, 91 insertions(+), 155 deletions(-) diff --git a/scripts/content-types/content-types.json b/scripts/content-types/content-types.json index 2162962f5..328ad4f80 100644 --- a/scripts/content-types/content-types.json +++ b/scripts/content-types/content-types.json @@ -8,47 +8,47 @@ "field_type": "select", "placeholder": "Select a genre", "options": [ - {"value": "Action", "label": "Action"}, - {"value": "Thriller", "label": "Thriller"}, - {"value": "Horror", "label": "Horror"}, - {"value": "Drama", "label": "Drama"}, - {"value": "Romance", "label": "Romance"}, - {"value": "Western", "label": "Western"}, - {"value": "Comedy", "label": "Comedy"}, - {"value": "Science fiction", "label": "Science fiction"}, - {"value": "Adventure", "label": "Adventure"}, - {"value": "Music", "label": "Music"}, - {"value": "Animation", "label": "Animation"}, - {"value": "Crime film", "label": "Crime film"}, - {"value": "History", "label": "History"}, - {"value": "Musical genre", "label": "Musical genre"}, - {"value": "Narrative", "label": "Narrative"}, - {"value": "Documentary", "label": "Documentary"}, - {"value": "Mystery", "label": "Mystery"}, - {"value": "Noir", "label": "Noir"}, - {"value": "Fantasy", "label": "Fantasy"}, - {"value": "Romantic comedy", "label": "Romantic comedy"}, - {"value": "Musical", "label": "Musical"}, - {"value": "War", "label": "War"}, - {"value": "Television", "label": "Television"}, - {"value": "Fiction", "label": "Fiction"}, - {"value": "Historical drama", "label": "Historical drama"}, - {"value": "Sports", "label": "Sports"}, - {"value": "Epic", "label": "Epic"}, - {"value": "Thriller", "label": "Thriller"}, - {"value": "Disaster", "label": "Disaster"}, - {"value": "Martial Arts", "label": "Martial Arts"}, - {"value": "Hindi cinema", "label": "Hindi cinema"}, - {"value": "Satire", "label": "Satire"}, - {"value": "Experimental", "label": "Experimental"}, - {"value": "Slasher", "label": "Slasher"}, - {"value": "Short", "label": "Short"}, - {"value": "Biographical", "label": "Biographical"}, - {"value": "Animated film", "label": "Animated film"}, - {"value": "Narrative", "label": "Narrative"}, - {"value": "Educational", "label": "Educational"}, - {"value": "Cult film", "label": "Cult film"}, - {"value": "Action/Adventure", "label": "Action/Adventure"} + { "value": "Action", "label": "Action" }, + { "value": "Thriller", "label": "Thriller" }, + { "value": "Horror", "label": "Horror" }, + { "value": "Drama", "label": "Drama" }, + { "value": "Romance", "label": "Romance" }, + { "value": "Western", "label": "Western" }, + { "value": "Comedy", "label": "Comedy" }, + { "value": "Science fiction", "label": "Science fiction" }, + { "value": "Adventure", "label": "Adventure" }, + { "value": "Music", "label": "Music" }, + { "value": "Animation", "label": "Animation" }, + { "value": "Crime film", "label": "Crime film" }, + { "value": "History", "label": "History" }, + { "value": "Musical genre", "label": "Musical genre" }, + { "value": "Narrative", "label": "Narrative" }, + { "value": "Documentary", "label": "Documentary" }, + { "value": "Mystery", "label": "Mystery" }, + { "value": "Noir", "label": "Noir" }, + { "value": "Fantasy", "label": "Fantasy" }, + { "value": "Romantic comedy", "label": "Romantic comedy" }, + { "value": "Musical", "label": "Musical" }, + { "value": "War", "label": "War" }, + { "value": "Television", "label": "Television" }, + { "value": "Fiction", "label": "Fiction" }, + { "value": "Historical drama", "label": "Historical drama" }, + { "value": "Sports", "label": "Sports" }, + { "value": "Epic", "label": "Epic" }, + { "value": "Thriller", "label": "Thriller" }, + { "value": "Disaster", "label": "Disaster" }, + { "value": "Martial Arts", "label": "Martial Arts" }, + { "value": "Hindi cinema", "label": "Hindi cinema" }, + { "value": "Satire", "label": "Satire" }, + { "value": "Experimental", "label": "Experimental" }, + { "value": "Slasher", "label": "Slasher" }, + { "value": "Short", "label": "Short" }, + { "value": "Biographical", "label": "Biographical" }, + { "value": "Animated film", "label": "Animated film" }, + { "value": "Narrative", "label": "Narrative" }, + { "value": "Educational", "label": "Educational" }, + { "value": "Cult film", "label": "Cult film" }, + { "value": "Action/Adventure", "label": "Action/Adventure" } ] } }, @@ -79,7 +79,7 @@ ] } }, - "trailer_id": { + "trailerId": { "param": "trailerId", "label": "Trailer", "description": "If this item has a trailer, select it here", @@ -87,7 +87,7 @@ "field_type": "media_select" } }, - "product_ids": { + "productIds": { "param": "productIds", "label": "Product IDs", "description": "Enter a CSV list of subscription assets that allow access to this content", @@ -96,7 +96,7 @@ "placeholder": "CSV subscription IDs" } }, - "live_status": { + "liveStatus": { "param": "VCH.EventState", "label": "Status", "description": "Do Not Modify - This is the state of the live event (populated automatically)", @@ -105,7 +105,7 @@ "placeholder": "This value will be populated automatically" } }, - "live_start_time": { + "liveStartTime": { "param": "VCH.ScheduledStart", "label": "Start Time", "description": "Do Not Modify - This is the schedules start time of the live event (populated automatically)", @@ -118,66 +118,20 @@ "sections": { "general": { "title": "General", - "fields": [ - "genre", - "rating", - "trailer_id" - ] + "fields": ["genre", "rating", "trailerId"] }, "access": { "title": "Access", "fields": [ { "param": "free", - "label": "Free to Watch?", + "label": "Free", "description": "If this item can be watched for free and doesn't require a login or subscription, you can set this value to true. Otherwise, if you leave this setting false, the application level subscription and authentication level rules will apply.", "details": { "field_type": "toggle" } }, - "product_ids" - ] - }, - "live_custom_params": { - "title": "Live Params", - "fields": [ - { - "param": "VCH.ID", - "label": "VCH ID", - "description": "Do Not Modify - This is the ID of the live event (populated automatically)", - "details": { - "field_type": "input", - "placeholder": "This value will be populated automatically" - } - }, - { - "param": "VCH.M3U8", - "label": "VCH M3U8", - "description": "Do Not Modify - Live event data (populated automatically)", - "details": { - "field_type": "input", - "placeholder": "This value will be populated automatically" - } - }, - { - "param": "VCH.MPD", - "label": "VCH MPD", - "description": "Do Not Modify - Live event data (populated automatically)", - "details": { - "field_type": "input", - "placeholder": "This value will be populated automatically" - } - }, - { - "param": "VCH.SmoothStream", - "label": "VCH SmoothStream", - "description": "Do Not Modify - Live event data (populated automatically)", - - "details": { - "field_type": "input", - "placeholder": "This value will be populated automatically" - } - } + "productIds" ] } }, @@ -189,10 +143,7 @@ "hosting_type": "hosted", "is_active": true, "is_series": false, - "sections": [ - "general", - "access" - ] + "sections": ["general", "access"] }, { "name": "series", @@ -201,9 +152,7 @@ "hosting_type": "ott_data", "is_active": true, "is_series": true, - "sections": [ - "general" - ] + "sections": ["general"] }, { "name": "episode", @@ -212,30 +161,52 @@ "hosting_type": "hosted", "is_active": true, "is_series": false, - "sections": [ - "general", - "access" - ] + "sections": [] }, { - "name": "channel", + "name": "liveChannel", "description": "Live Channel Schema", "display_name": "Live Channel", - "hosting_type": "external", + "hosting_type": "live_bcl", "is_active": true, "is_series": false, "sections": [ { "title": "Status", + "fields": ["liveStatus", "liveStartTime"] + }, + { + "title": "General", "fields": [ - "live_status", - "live_start_time" + { + "param": "liveChannelsId", + "label": "Playlist", + "description": "Playlist ID for the dynamic playlist containing your live channels ", + "required": true, + "details": { + "field_type": "playlist_multiselect" + } + } ] }, "access", { - "title": "Schedule", + "title": "Schedule (EPG)", "fields": [ + { + "param": "scheduleType", + "label": "Schedule Type", + "description": "EPG schedule type", + "details": { + "field_type": "select", + "placeholder": "Select a schedule type", + "default": "jwp", + "options": [ + { "value": "jwp", "label": "Default" }, + { "value": "viewnexa", "label": "ViewNexa" } + ] + } + }, { "param": "scheduleUrl", "label": "EPG Schedule URL", @@ -246,30 +217,29 @@ }, { "param": "scheduleDemo", - "label": "Use EPG Demo", + "label": "Demo Mode", "description": "Only enable this for non-production (demo) sites where you want the EPG schedule to loop", "details": { "field_type": "toggle" } } ] - }, - "live_custom_params" + } ] }, { - "name": "event", + "name": "liveEvent", "description": "Live Event Schema", "display_name": "Live Event", - "hosting_type": "external", + "hosting_type": "live_bcl", "is_active": true, "is_series": false, "sections": [ { "title": "Status", "fields": [ - "live_status", - "live_start_time", + "liveStatus", + "liveStartTime", { "param": "VCH.ScheduledEnd", "label": "End Time", @@ -281,8 +251,7 @@ } ] }, - "access", - "live_custom_params" + "access" ] }, { @@ -292,22 +261,7 @@ "hosting_type": "hosted", "is_active": true, "is_series": false, - "sections": [ - { - "title": "Access", - "fields": [ - { - "param": "free", - "label": "Free to Watch?", - "description": "Trailers can usually be watched for free, even if you have subscription based apps. If that is the case, set this value to true. Otherwise, set this value to false if you want to restrict viewing this trailer to only authenticated / paying customers.", - "details": { - "field_type": "toggle", - "default": true - } - } - ] - } - ] + "sections": [] }, { "name": "hub", @@ -328,24 +282,6 @@ "details": { "field_type": "playlist_multiselect" } - }, - { - "param": "logo", - "label": "Logo", - "description": "Enter the absolute path to a logo to display for this page", - "details": { - "field_type": "input", - "placeholder": "Enter the url of a logo" - } - }, - { - "param": "backgroundImage", - "label": "Background Image", - "description": "Enter the absolute path for a background image to display for this page", - "details": { - "field_type": "input", - "placeholder": "Enter the url of an image for the background" - } } ] } diff --git a/src/config.ts b/src/config.ts index 0259d9a98..a664f3984 100644 --- a/src/config.ts +++ b/src/config.ts @@ -42,7 +42,7 @@ export const CONTENT_TYPE = { // Page with a list of channels live: 'live', // Separate channel page - livechannel: 'livechannel', + liveChannel: 'livechannel', // Static page with markdown page: 'page', // Page with shelves list diff --git a/src/pages/ScreenRouting/MediaScreenRouter.tsx b/src/pages/ScreenRouting/MediaScreenRouter.tsx index cc8c6c4a3..b99942204 100644 --- a/src/pages/ScreenRouting/MediaScreenRouter.tsx +++ b/src/pages/ScreenRouting/MediaScreenRouter.tsx @@ -21,7 +21,7 @@ export const mediaScreenMap = new ScreenMap(); // Register media screens mediaScreenMap.registerByContentType(MediaSeries, CONTENT_TYPE.series); mediaScreenMap.registerByContentType(MediaEpisode, CONTENT_TYPE.episode); -mediaScreenMap.registerByContentType(MediaLiveChannel, CONTENT_TYPE.livechannel); +mediaScreenMap.registerByContentType(MediaLiveChannel, CONTENT_TYPE.liveChannel); mediaScreenMap.registerByContentType(MediaStaticPage, CONTENT_TYPE.page); mediaScreenMap.registerDefault(MediaMovie); From fc2668d587c041312deaae9fd47cf7a6f6f34681 Mon Sep 17 00:00:00 2001 From: Conventional Changelog Action Date: Thu, 18 Jan 2024 14:13:39 +0000 Subject: [PATCH 10/16] chore(release): v5.1.0 --- CHANGELOG.md | 25 +++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72d3deda7..a226dd35c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,28 @@ +## [5.1.0](https://github.com/jwplayer/ott-web-app/compare/v5.0.0...v5.1.0) (2024-01-18) + + +### Features + +* add user_id and profile_id for CDN analytics ([114150e](https://github.com/jwplayer/ott-web-app/commit/114150e6cdd8aaf436c06df30ea785f458b641f0)) +* add user_id and profile_id for CDN analytics ([0bacf37](https://github.com/jwplayer/ott-web-app/commit/0bacf37998904278b46bca17d6029f054150fc51)) +* cdn analytics code cleanup ([aeef40f](https://github.com/jwplayer/ott-web-app/commit/aeef40f6f375783cd0820fd27154b616570d3cf0)) +* cdn analytics code cleanup ([d92937d](https://github.com/jwplayer/ott-web-app/commit/d92937da7592c580df8230a4c1ee2fad3765ff55)) +* **epg:** fix live channel casing ([43c487c](https://github.com/jwplayer/ott-web-app/commit/43c487ca72bfdbf5977e6f873f3505fe5bf6011a)) +* **epg:** use getNamedModule ([0394daf](https://github.com/jwplayer/ott-web-app/commit/0394daf6ce8d73103aee5f5667a98fa089325569)) +* **project:** add view nexa epg provider ([9a71457](https://github.com/jwplayer/ott-web-app/commit/9a71457ae51dec38538e5b5ac719426250d3cae4)) +* **project:** change content type default schemas ([e100384](https://github.com/jwplayer/ott-web-app/commit/e1003844da97ba97618ed46e9eba076e49a6159c)) +* **project:** change the way of DI ([4154488](https://github.com/jwplayer/ott-web-app/commit/4154488dbe0e157ea92434382cc5d2d646cdd2bb)) +* **project:** review comments ([3359612](https://github.com/jwplayer/ott-web-app/commit/3359612cd029858a2ea3f596d23cfc835bda2fc8)) + + +### Bug Fixes + +* **epg:** check lower case, log error ([be774d4](https://github.com/jwplayer/ott-web-app/commit/be774d47780a238800796caf22700c3e4ce7c28f)) +* fix e2e test ([ba1e0de](https://github.com/jwplayer/ott-web-app/commit/ba1e0de5b60c8a5bfed1e52bacaf15d977b69894)) +* missing getState on useConfigStore under updateCardDetails ([#433](https://github.com/jwplayer/ott-web-app/issues/433)) ([ce32908](https://github.com/jwplayer/ott-web-app/commit/ce329084bf8565c475ea37e04006aaa08766c255)) +* place consents in appropriate section ([6281640](https://github.com/jwplayer/ott-web-app/commit/6281640fc4fb0f6f7b3b56cc34749465348cc7a2)) +* transform url to lowerCase ([68d31c4](https://github.com/jwplayer/ott-web-app/commit/68d31c46df3b5999f8bee79ed269e597b30993bf)) + ## [5.0.0](https://github.com/jwplayer/ott-web-app/compare/v4.31.1...v5.0.0) (2024-01-11) diff --git a/package.json b/package.json index 9ed6dfdd2..73deb7e56 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jw-ott-webapp", - "version": "5.0.0", + "version": "5.1.0", "main": "index.js", "repository": "https://github.com/jwplayer/ott-web-app.git", "author": "JW Player", From 066971ab03a074d65e74746642a90ecce108b042 Mon Sep 17 00:00:00 2001 From: Anton Lantukh Date: Thu, 18 Jan 2024 16:55:36 +0100 Subject: [PATCH 11/16] fix(epg): fix repeatable keys for demo + empty service --- src/stores/EpgController.ts | 11 ++++++----- test-e2e/tests/live_channel_test.ts | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/stores/EpgController.ts b/src/stores/EpgController.ts index bc1783437..8bb30f219 100644 --- a/src/stores/EpgController.ts +++ b/src/stores/EpgController.ts @@ -34,8 +34,9 @@ export default class EpgController { const utcStartDate = new Date(Date.UTC(startDate.getUTCFullYear(), startDate.getUTCMonth(), startDate.getUTCDate())); const daysDelta = differenceInDays(today, utcStartDate); - return programs.map((program) => ({ + return programs.map((program, idx) => ({ ...program, + id: `${program.id}_${idx}`, startTime: addDays(new Date(program.startTime), daysDelta).toJSON(), endTime: addDays(new Date(program.endTime), daysDelta).toJSON(), })); @@ -54,7 +55,7 @@ export default class EpgController { const transformResults = await Promise.allSettled( data.map((program) => epgService - .transformProgram(program) + ?.transformProgram(program) // This quiets promise resolution errors in the console .catch((error) => { logDev(error); @@ -78,7 +79,7 @@ export default class EpgController { getSchedule = async (item: PlaylistItem) => { const epgService = this.getEpgService(item); - const schedule = await epgService.fetchSchedule(item); + const schedule = await epgService?.fetchSchedule(item); const programs = await this.parseSchedule(schedule, item); const catchupHours = item.catchupHours && parseInt(item.catchupHours); @@ -95,8 +96,8 @@ export default class EpgController { }; getEpgService = (item: PlaylistItem) => { - const scheduleType = item?.scheduleType || EPG_TYPE.jwp; - const service = getNamedModule(EpgService, scheduleType?.toLowerCase()); + const scheduleType = item?.scheduleType?.toLocaleLowerCase() || EPG_TYPE.jwp; + const service = getNamedModule(EpgService, scheduleType, false); if (!service) { console.error(`No epg service was added for the ${scheduleType} schedule type`); diff --git a/test-e2e/tests/live_channel_test.ts b/test-e2e/tests/live_channel_test.ts index 855c40562..3b6e56d78 100644 --- a/test-e2e/tests/live_channel_test.ts +++ b/test-e2e/tests/live_channel_test.ts @@ -41,8 +41,8 @@ const shelfContainerLocator = locate({ css: 'div[role="row"]' }); const shelfLocator = locate({ css: 'div[role="cell"]' }).inside(shelfContainerLocator); const epgContainerLocator = locate({ css: 'div[data-testid="container"]' }); -const makeEpgProgramLocator = (id: string) => locate({ css: `div[data-testid="${id}"]` }).inside(epgContainerLocator); -const makeEpgChannelLocator = (id: string) => locate({ css: `div[data-testid="${id}"]` }).inside(epgContainerLocator); +const makeEpgProgramLocator = (id: string) => locate({ css: `div[data-testid*="${id}"]` }).inside(epgContainerLocator); +const makeEpgChannelLocator = (id: string) => locate({ css: `div[data-testid*="${id}"]` }).inside(epgContainerLocator); const channel1Locator = makeEpgChannelLocator(channel1Id); const channel2Locator = makeEpgChannelLocator(channel2Id); From e827affd97f4cd3dbff4296c8686b77fab4a06b4 Mon Sep 17 00:00:00 2001 From: Danny Budzinski Date: Fri, 19 Jan 2024 11:28:23 +0100 Subject: [PATCH 12/16] fix: prevent slider from getting stuck in non-anmiated mode --- src/components/TileDock/TileDock.tsx | 56 ++++++++++++++++------------ 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/src/components/TileDock/TileDock.tsx b/src/components/TileDock/TileDock.tsx index 6375ea0a2..ff44868fe 100644 --- a/src/components/TileDock/TileDock.tsx +++ b/src/components/TileDock/TileDock.tsx @@ -1,5 +1,5 @@ import classNames from 'classnames'; -import React, { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react'; +import React, { type ReactNode, useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react'; import styles from './TileDock.module.scss'; @@ -19,10 +19,10 @@ export type TileDockProps = { animated?: boolean; wrapWithEmptyTiles?: boolean; transitionTime?: string; - renderTile: (item: T, isInView: boolean) => JSX.Element; - renderLeftControl?: (handleClick: () => void) => JSX.Element; - renderRightControl?: (handleClick: () => void) => JSX.Element; - renderPaginationDots?: (index: number, pageIndex: number) => JSX.Element; + renderTile: (item: T, isInView: boolean) => ReactNode; + renderLeftControl?: (handleClick: () => void) => ReactNode; + renderRightControl?: (handleClick: () => void) => ReactNode; + renderPaginationDots?: (index: number, pageIndex: number) => ReactNode; }; type Tile = { @@ -75,11 +75,11 @@ function TileDock({ renderRightControl, renderPaginationDots, }: TileDockProps) { - const [index, setIndex] = useState(0); - const [slideToIndex, setSlideToIndex] = useState(0); - const [transform, setTransform] = useState(-100); - const [doAnimationReset, setDoAnimationReset] = useState(false); - const [hasTransition, setHasTransition] = useState(false); + const [index, setIndex] = useState(0); + const [slideToIndex, setSlideToIndex] = useState(0); + const [transform, setTransform] = useState(-100); + const [animationDone, setAnimationDone] = useState(false); + const [isAnimationRunning, setIsAnimationRunning] = useState(false); const frameRef = useRef() as React.MutableRefObject; const tileWidth: number = 100 / tilesToShow; @@ -90,7 +90,7 @@ function TileDock({ return sliceItems(items, isMultiPage, index, tilesToShow, cycleMode); }, [items, isMultiPage, index, tilesToShow, cycleMode]); - const transitionBasis: string = isMultiPage && animated && hasTransition ? `transform ${transitionTime} ease` : ''; + const transitionBasis: string = isMultiPage && animated && isAnimationRunning ? `transform ${transitionTime} ease` : ''; const needControls: boolean = showControls && isMultiPage; const showLeftControl: boolean = needControls && !(cycleMode === 'stop' && index === 0); @@ -98,7 +98,8 @@ function TileDock({ const slide = useCallback( (direction: Direction): void => { - if (hasTransition) { + // Debounce slide events based on if the animation is running + if (isAnimationRunning) { return; } @@ -120,11 +121,17 @@ function TileDock({ setSlideToIndex(nextIndex); setTransform(-100 + movement); - setHasTransition(true); - if (!animated) setDoAnimationReset(true); + // If this is an animated slider, start the animation 'slide' + if (animated) { + setIsAnimationRunning(true); + } + // If not anmiated, trigger the post animation code right away + else { + setAnimationDone(true); + } }, - [animated, cycleMode, index, items.length, tileWidth, tilesToShow, hasTransition], + [animated, cycleMode, index, items.length, tileWidth, tilesToShow, isAnimationRunning], ); const handleTouchStart = useCallback( @@ -134,7 +141,7 @@ function TileDock({ y: event.touches[0].clientY, }; - function handleTouchMove(this: HTMLDocument, event: TouchEvent): void { + function handleTouchMove(this: Document, event: TouchEvent): void { const newPosition: Position = { x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY, @@ -148,7 +155,7 @@ function TileDock({ } } - function handleTouchEnd(this: HTMLDocument, event: TouchEvent): void { + function handleTouchEnd(this: Document, event: TouchEvent): void { const newPosition = { x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY, @@ -182,8 +189,9 @@ function TileDock({ [minimalTouchMovement, slide], ); + // Run code after the slide animation to set the new index useLayoutEffect(() => { - const resetAnimation = (): void => { + const postAnimationCleanup = (): void => { let resetIndex: number = slideToIndex; resetIndex = resetIndex >= items.length ? slideToIndex - items.length : resetIndex; @@ -195,16 +203,18 @@ function TileDock({ setIndex(resetIndex); setTransform(-100); - setDoAnimationReset(false); + setIsAnimationRunning(false); + setAnimationDone(false); }; - if (doAnimationReset) resetAnimation(); - }, [doAnimationReset, index, items.length, slideToIndex, tileWidth, tilesToShow, transitionBasis]); + if (animationDone) { + postAnimationCleanup(); + } + }, [animationDone, index, items.length, slideToIndex, tileWidth, tilesToShow, transitionBasis]); const handleTransitionEnd = (event: React.TransitionEvent) => { if (event.target === frameRef.current) { - setDoAnimationReset(true); - setHasTransition(false); + setAnimationDone(true); } }; From 1b36ae0e391d9ec977fdd6e8b0ef3befa43ddc5a Mon Sep 17 00:00:00 2001 From: Danny Budzinski Date: Fri, 19 Jan 2024 11:57:19 +0100 Subject: [PATCH 13/16] fix: lock animation mode on first load --- src/components/TileDock/TileDock.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/TileDock/TileDock.tsx b/src/components/TileDock/TileDock.tsx index ff44868fe..a95ea47df 100644 --- a/src/components/TileDock/TileDock.tsx +++ b/src/components/TileDock/TileDock.tsx @@ -16,7 +16,7 @@ export type TileDockProps = { minimalTouchMovement?: number; showControls?: boolean; showDots?: boolean; - animated?: boolean; + animatedOverride?: boolean; wrapWithEmptyTiles?: boolean; transitionTime?: string; renderTile: (item: T, isInView: boolean) => ReactNode; @@ -66,7 +66,7 @@ function TileDock({ spacing = 12, minimalTouchMovement = 30, showControls = true, - animated = !window.matchMedia('(prefers-reduced-motion)').matches, + animatedOverride, transitionTime = '0.6s', wrapWithEmptyTiles = false, showDots = false, @@ -78,6 +78,8 @@ function TileDock({ const [index, setIndex] = useState(0); const [slideToIndex, setSlideToIndex] = useState(0); const [transform, setTransform] = useState(-100); + // Prevent animation mode from changing after first load + const [isAnimated] = useState(animatedOverride ?? !window.matchMedia('(prefers-reduced-motion)').matches); const [animationDone, setAnimationDone] = useState(false); const [isAnimationRunning, setIsAnimationRunning] = useState(false); @@ -90,7 +92,7 @@ function TileDock({ return sliceItems(items, isMultiPage, index, tilesToShow, cycleMode); }, [items, isMultiPage, index, tilesToShow, cycleMode]); - const transitionBasis: string = isMultiPage && animated && isAnimationRunning ? `transform ${transitionTime} ease` : ''; + const transitionBasis: string = isMultiPage && isAnimated && isAnimationRunning ? `transform ${transitionTime} ease` : ''; const needControls: boolean = showControls && isMultiPage; const showLeftControl: boolean = needControls && !(cycleMode === 'stop' && index === 0); @@ -123,7 +125,7 @@ function TileDock({ setTransform(-100 + movement); // If this is an animated slider, start the animation 'slide' - if (animated) { + if (isAnimated) { setIsAnimationRunning(true); } // If not anmiated, trigger the post animation code right away @@ -131,7 +133,7 @@ function TileDock({ setAnimationDone(true); } }, - [animated, cycleMode, index, items.length, tileWidth, tilesToShow, isAnimationRunning], + [isAnimated, cycleMode, index, items.length, tileWidth, tilesToShow, isAnimationRunning], ); const handleTouchStart = useCallback( From 9bed444a3ce66daad52789243fa1acd3303996ce Mon Sep 17 00:00:00 2001 From: Danny Budzinski Date: Fri, 19 Jan 2024 12:02:36 +0100 Subject: [PATCH 14/16] chore: rename variables for consistency --- src/components/TileDock/TileDock.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/TileDock/TileDock.tsx b/src/components/TileDock/TileDock.tsx index a95ea47df..885501148 100644 --- a/src/components/TileDock/TileDock.tsx +++ b/src/components/TileDock/TileDock.tsx @@ -16,7 +16,7 @@ export type TileDockProps = { minimalTouchMovement?: number; showControls?: boolean; showDots?: boolean; - animatedOverride?: boolean; + animationModeOverride?: boolean; wrapWithEmptyTiles?: boolean; transitionTime?: string; renderTile: (item: T, isInView: boolean) => ReactNode; @@ -66,7 +66,7 @@ function TileDock({ spacing = 12, minimalTouchMovement = 30, showControls = true, - animatedOverride, + animationModeOverride, transitionTime = '0.6s', wrapWithEmptyTiles = false, showDots = false, @@ -79,8 +79,8 @@ function TileDock({ const [slideToIndex, setSlideToIndex] = useState(0); const [transform, setTransform] = useState(-100); // Prevent animation mode from changing after first load - const [isAnimated] = useState(animatedOverride ?? !window.matchMedia('(prefers-reduced-motion)').matches); - const [animationDone, setAnimationDone] = useState(false); + const [isAnimated] = useState(animationModeOverride ?? !window.matchMedia('(prefers-reduced-motion)').matches); + const [isAnimationDone, setIsAnimationDone] = useState(false); const [isAnimationRunning, setIsAnimationRunning] = useState(false); const frameRef = useRef() as React.MutableRefObject; @@ -130,7 +130,7 @@ function TileDock({ } // If not anmiated, trigger the post animation code right away else { - setAnimationDone(true); + setIsAnimationDone(true); } }, [isAnimated, cycleMode, index, items.length, tileWidth, tilesToShow, isAnimationRunning], @@ -206,17 +206,17 @@ function TileDock({ setIndex(resetIndex); setTransform(-100); setIsAnimationRunning(false); - setAnimationDone(false); + setIsAnimationDone(false); }; - if (animationDone) { + if (isAnimationDone) { postAnimationCleanup(); } - }, [animationDone, index, items.length, slideToIndex, tileWidth, tilesToShow, transitionBasis]); + }, [isAnimationDone, index, items.length, slideToIndex, tileWidth, tilesToShow, transitionBasis]); const handleTransitionEnd = (event: React.TransitionEvent) => { if (event.target === frameRef.current) { - setAnimationDone(true); + setIsAnimationDone(true); } }; From 4f6e4b0af50c26b523c1fc8f12469baa5280f5a1 Mon Sep 17 00:00:00 2001 From: Anton Lantukh Date: Wed, 24 Jan 2024 10:38:21 +0100 Subject: [PATCH 15/16] fix(project): fix dfault schemas --- scripts/content-types/content-types.json | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/scripts/content-types/content-types.json b/scripts/content-types/content-types.json index 328ad4f80..c086bc2ab 100644 --- a/scripts/content-types/content-types.json +++ b/scripts/content-types/content-types.json @@ -161,7 +161,7 @@ "hosting_type": "hosted", "is_active": true, "is_series": false, - "sections": [] + "sections": ["access"] }, { "name": "liveChannel", @@ -261,7 +261,22 @@ "hosting_type": "hosted", "is_active": true, "is_series": false, - "sections": [] + "sections": [ + { + "title": "Access", + "fields": [ + { + "param": "free", + "label": "Free", + "description": "If this item can be watched for free and doesn't require a login or subscription, you can set this value to true. Otherwise, if you leave this setting false, the application level subscription and authentication level rules will apply.", + "details": { + "field_type": "toggle", + "default": true + } + } + ] + } + ] }, { "name": "hub", From 472ad06b638d1358851f36a25c8f6819f7598b29 Mon Sep 17 00:00:00 2001 From: Conventional Changelog Action Date: Wed, 24 Jan 2024 10:20:09 +0000 Subject: [PATCH 16/16] chore(release): v5.1.1 --- CHANGELOG.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a226dd35c..5402f754e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [5.1.1](https://github.com/jwplayer/ott-web-app/compare/v5.1.0...v5.1.1) (2024-01-24) + + +### Bug Fixes + +* lock animation mode on first load ([1b36ae0](https://github.com/jwplayer/ott-web-app/commit/1b36ae0e391d9ec977fdd6e8b0ef3befa43ddc5a)) +* prevent slider from getting stuck in non-anmiated mode ([e827aff](https://github.com/jwplayer/ott-web-app/commit/e827affd97f4cd3dbff4296c8686b77fab4a06b4)) +* **project:** fix dfault schemas ([4f6e4b0](https://github.com/jwplayer/ott-web-app/commit/4f6e4b0af50c26b523c1fc8f12469baa5280f5a1)) + ## [5.1.0](https://github.com/jwplayer/ott-web-app/compare/v5.0.0...v5.1.0) (2024-01-18) diff --git a/package.json b/package.json index 73deb7e56..9ae97447c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jw-ott-webapp", - "version": "5.1.0", + "version": "5.1.1", "main": "index.js", "repository": "https://github.com/jwplayer/ott-web-app.git", "author": "JW Player",