Skip to content

Commit

Permalink
feat(project): add ssai ads for vod (#583)
Browse files Browse the repository at this point in the history
* feat(project): add ssai ads for vod, refactor sources function
  • Loading branch information
AntonLantukh authored Jul 25, 2024
1 parent 6e4d263 commit d3a4750
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 51 deletions.
28 changes: 0 additions & 28 deletions packages/common/src/utils/analytics.ts

This file was deleted.

2 changes: 2 additions & 0 deletions packages/common/src/utils/configSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export const configSchema: SchemaOf<Config> = object({
description: string().defined(),
analyticsToken: string().nullable(),
adSchedule: string().nullable(),
adDeliveryMethod: mixed().oneOf(['csai', 'ssai']).notRequired(),
adConfig: string().nullable(),
siteId: string().defined(),
assets: object({
banner: string().notRequired().nullable(),
Expand Down
43 changes: 43 additions & 0 deletions packages/common/src/utils/sources.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { PlaylistItem, Source } from '@jwp/ott-common/types/playlist';
import type { Config } from '@jwp/ott-common/types/config';
import type { Customer } from '@jwp/ott-common/types/account';

const isVODManifestType = (sourceUrl: string, baseUrl: string, mediaId: string, extensions: ('m3u8' | 'mpd')[]) => {
return extensions.some((ext) => sourceUrl === `${baseUrl}/manifests/${mediaId}.${ext}`);
};

const isBCLManifestType = (sourceUrl: string, baseUrl: string, mediaId: string, extensions: ('m3u8' | 'mpd')[]) => {
return extensions.some((ext) => sourceUrl === `${baseUrl}/live/broadcast/${mediaId}.${ext}`);
};

export const getSources = ({ item, baseUrl, config, user }: { item: PlaylistItem; baseUrl: string; config: Config; user: Customer | null }) => {
const { sources, mediaid } = item;
const { adConfig, siteId, adDeliveryMethod } = config;

const userId = user?.id;
const hasServerAds = !!adConfig && adDeliveryMethod === 'ssai';

return sources.map((source: Source) => {
const url = new URL(source.file);

const sourceUrl = url.href;

const isBCLManifest = isBCLManifestType(sourceUrl, baseUrl, mediaid, ['m3u8', 'mpd']);
const isVODManifest = isVODManifestType(sourceUrl, baseUrl, mediaid, ['m3u8', 'mpd']);
const isDRM = url.searchParams.has('exp') && url.searchParams.has('sig');

// Use SSAI URL for configs with server side ads, DRM is not supported
if (isVODManifest && hasServerAds && !isDRM) {
// Only HLS is supported now
url.href = `${baseUrl}/v2/sites/${siteId}/media/${mediaid}/ssai.m3u8`;
url.searchParams.set('ad_config_id', adConfig);
// Attach user_id only for VOD and BCL SaaS Live Streams (doesn't work with SSAI items)
} else if ((isVODManifest || isBCLManifest) && userId) {
url.searchParams.set('user_id', userId);
}

source.file = url.toString();

return source;
});
};
2 changes: 2 additions & 0 deletions packages/common/types/ad-schedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ export type AdScheduleUrls = {
json?: string | null;
xml?: string | null;
};

export type AdDeliveryMethod = 'csai' | 'ssai';
4 changes: 3 additions & 1 deletion packages/common/types/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { PLAYLIST_TYPE } from '../src/constants';

import type { AdScheduleUrls } from './ad-schedule';
import type { AdScheduleUrls, AdDeliveryMethod } from './ad-schedule';

/**
* Set config setup changes in both config.services.ts and config.d.ts
Expand All @@ -11,6 +11,8 @@ export type Config = {
description: string;
analyticsToken?: string | null;
adSchedule?: string | null;
adConfig?: string | null;
adDeliveryMethod?: AdDeliveryMethod;
adScheduleUrls?: AdScheduleUrls;
integrations: {
cleeng?: Cleeng;
Expand Down
33 changes: 18 additions & 15 deletions packages/hooks-react/src/useAds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { createURL } from '@jwp/ott-common/src/utils/urlFormatting';
const CACHE_TIME = 60 * 1000 * 20;

/**
* @deprecated Use adScheduleUrls.xml form the config instead.
* @deprecated Use ad-config instead.
*/
const useLegacyStandaloneAds = ({ adScheduleId, enabled }: { adScheduleId: string | null | undefined; enabled: boolean }) => {
const apiService = getModule(ApiService);
Expand All @@ -25,21 +25,24 @@ const useLegacyStandaloneAds = ({ adScheduleId, enabled }: { adScheduleId: strin
};

export const useAds = ({ mediaId }: { mediaId: string }) => {
const { adSchedule: adScheduleId, adScheduleUrls } = useConfigStore((s) => s.config);

// adScheduleUrls.xml prop exists when ad-config is attached to the App Config
const useAppBasedFlow = !!adScheduleUrls?.xml;

const { data: adSchedule, isLoading: isAdScheduleLoading } = useLegacyStandaloneAds({ adScheduleId, enabled: !useAppBasedFlow });
const adConfig = {
client: 'vast',
schedule: createURL(adScheduleUrls?.xml || '', {
media_id: mediaId,
}),
};
const { adSchedule: adScheduleId, adConfig: adConfigId, adScheduleUrls, adDeliveryMethod } = useConfigStore((s) => s.config);

// We use client side ads only when delivery method is not pointing at server ads
// adConfig and adScheduled can't be enabled at the same time
const useAdConfigFlow = !!adConfigId && adDeliveryMethod !== 'ssai';

const { data: adSchedule, isLoading: isAdScheduleLoading } = useLegacyStandaloneAds({ adScheduleId, enabled: !!adScheduleId });
const adConfig = useAdConfigFlow
? {
client: 'vast',
schedule: createURL(adScheduleUrls?.xml || '', {
media_id: mediaId,
}),
}
: undefined;

return {
isLoading: useAppBasedFlow ? false : isAdScheduleLoading,
data: useAppBasedFlow ? adConfig : adSchedule,
isLoading: useAdConfigFlow ? false : isAdScheduleLoading,
data: useAdConfigFlow ? adConfig : adSchedule,
};
};
13 changes: 13 additions & 0 deletions packages/hooks-react/src/useMediaSources.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useMemo } from 'react';
import { useConfigStore } from '@jwp/ott-common/src/stores/ConfigStore';
import { useAccountStore } from '@jwp/ott-common/src/stores/AccountStore';
import type { PlaylistItem, Source } from '@jwp/ott-common/types/playlist';
import { getSources } from '@jwp/ott-common/src/utils/sources';

/** Modify manifest URLs to handle server ads and analytics params */
export const useMediaSources = ({ item, baseUrl }: { item: PlaylistItem; baseUrl: string }): Source[] => {
const config = useConfigStore((s) => s.config);
const user = useAccountStore((s) => s.user);

return useMemo(() => getSources({ item, baseUrl, config, user }), [item, baseUrl, config, user]);
};
11 changes: 4 additions & 7 deletions packages/ui-react/src/components/Player/Player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { testId } from '@jwp/ott-common/src/utils/common';
import { logInfo } from '@jwp/ott-common/src/logger';
import useEventCallback from '@jwp/ott-hooks-react/src/useEventCallback';
import useOttAnalytics from '@jwp/ott-hooks-react/src/useOttAnalytics';
import { attachAnalyticsParams } from '@jwp/ott-common/src/utils/analytics';
import { useMediaSources } from '@jwp/ott-hooks-react/src/useMediaSources';
import env from '@jwp/ott-common/src/env';

import type { JWPlayer } from '../../../types/jwplayer';
Expand Down Expand Up @@ -62,6 +62,7 @@ const Player: React.FC<Props> = ({
const backClickRef = useRef(false);
const [libLoaded, setLibLoaded] = useState(!!window.jwplayer);
const startTimeRef = useRef(startTime);
const sources = useMediaSources({ item, baseUrl: env.APP_API_BASE_URL });

const setPlayer = useOttAnalytics(item, feedId);

Expand Down Expand Up @@ -183,10 +184,6 @@ const Player: React.FC<Props> = ({

playerRef.current = window.jwplayer(playerElementRef.current) as JWPlayer;

// Inject user_id into the CDN analytics
// @todo this currently depends on stores
attachAnalyticsParams(item);

// Player options are untyped
const playerOptions: { [key: string]: unknown } = {
advertising: {
Expand All @@ -209,7 +206,7 @@ const Player: React.FC<Props> = ({
mute: false,
playbackRateControls: true,
pipIcon: 'disabled',
playlist: [deepCopy({ ...item, starttime: startTimeRef.current, feedid: feedId })],
playlist: [deepCopy({ ...item, starttime: startTimeRef.current, feedid: feedId, sources })],
repeat: false,
cast: {},
stretching: 'uniform',
Expand Down Expand Up @@ -239,7 +236,7 @@ const Player: React.FC<Props> = ({
if (libLoaded) {
initializePlayer();
}
}, [libLoaded, item, detachEvents, attachEvents, playerId, setPlayer, autostart, adsData, playerLicenseKey, feedId]);
}, [libLoaded, item, detachEvents, attachEvents, playerId, setPlayer, autostart, adsData, playerLicenseKey, sources, feedId]);

useEffect(() => {
return () => {
Expand Down

0 comments on commit d3a4750

Please sign in to comment.