import * as React from "react"; import merge from "lodash/merge"; import { clone } from "@applicaster/zapp-react-native-utils/utils"; import { Platform, View, ViewStyle } from "react-native"; import { appStore } from "@applicaster/zapp-react-native-redux/AppStore"; import { isTV, isWeb } from "@applicaster/zapp-react-native-utils/reactUtils"; import { PlayerLiveImage } from "../../../VideoLive/PlayerLiveImageComponent"; import Image from "../Image"; import { useTrackCurrentAutoScrollingElement } from "@applicaster/zapp-react-native-utils/reactHooks/autoscrolling/useTrackCurrentAutoScrollingElement"; import { useUIComponentContext } from "@applicaster/zapp-react-native-ui-components/Contexts/UIComponentContext"; import { getPropComponentType } from "@applicaster/zapp-react-native-utils/cellUtils"; import { findPluginByIdentifier } from "@applicaster/zapp-react-native-utils/pluginUtils"; import { useAccessibilityState } from "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager/hooks"; type LiveImageProps = { item: ZappEntry; style: Record; cellUUID?: string; actionIdentifier?: string; imageKey?: string; state: "focused" | "unfocused" | "selected"; audio_muted_by_default?: boolean; uri: string; // cover image url }; const DEFAULT_PLAYER_IDENTIFIER = "QuickBrickPlayerPlugin"; const KALTURA_PLAYER_IDENTIFIER = "KalturaPlayerPlugin"; const knownMediaExtensions = { m3u8: "application/x-mpegURL", m4v: "video/mp4", mkv: "video/x-matroska", mov: "video/mp4", mp3: "audio/mpeg", mp4: "video/mp4", mpd: "application/dash+xml", oga: "audio/ogg", ogv: "video/ogg", opus: "video/ogg", }; const isSupportedTVForLiveImage = () => { if (isWeb()) { const osVersion = Platform.Version as string; const platformOS = Platform.OS as QuickBrickPlatforms; const parsedOsVersion = parseFloat(osVersion); if (platformOS === "lg_tv") { if (isNaN(parsedOsVersion)) { // it's browser return true; } return parsedOsVersion >= 5.0; } if (platformOS === "samsung_tv") { if (isNaN(parsedOsVersion)) { // it's browser return true; } return parsedOsVersion >= 4.0; } } else { return true; } }; const isVideoUrl = (streamUrl: string): boolean => { // TODO: Not the best. Will fail if there is query params for (const key in knownMediaExtensions) { if (streamUrl.endsWith(key)) { return true; } } return false; }; const removeAdsFromEntry = (entry) => { if (!entry?.extensions?.video_ads) { return entry; } const newEntry = clone(entry); delete newEntry.extensions.video_ads; return newEntry; }; export const prepareEntry = (entry: ZappEntry) => { if (!entry) { return null; } const previewPlaybackOverride = entry.extensions?.preview_playback_override; if (previewPlaybackOverride) { return merge({}, entry, previewPlaybackOverride); } const previewPlayback = entry.extensions?.brightcove?.preview_playback; if (previewPlayback) { return { ...entry, extensions: { ...entry.extensions, brightcove: { ...entry?.extensions?.brightcove, video_id: previewPlayback, }, }, }; } if (entry.extensions?.preview_playback) { return { ...entry, content: { ...entry?.content, src: entry?.extensions?.preview_playback }, }; } if (entry.content?.teaser) { return { ...entry, content: { ...entry?.content, src: entry?.content?.teaser }, }; } if (entry.extensions?.brightcove?.video_id) { return entry; } const src = entry.content?.src; if (!src) { return null; } if (entry.content?.type?.startsWith("video")) { return entry; } return isVideoUrl(src) ? entry : null; }; const getPlayableEntry = (entry) => { const preparedEntry = prepareEntry(entry); if (!preparedEntry) { return null; } return removeAdsFromEntry(preparedEntry); }; function getFocusedState( state: string, componentType: string, isCurrentlyFocused: boolean ) { if (isTV()) { return state === "focused"; } else { // only horizontal_list_qb should work with isCurrentlyFocused return componentType === "horizontal_list_qb" ? isCurrentlyFocused : true; } } const getPlayerConfig = (player_screen_id, actionIdentifier) => { if (player_screen_id) { const rivers = appStore.get("rivers"); const plugins = appStore.get("plugins"); const playerScreen = rivers?.[player_screen_id] ?? null; const preloadHooks = playerScreen?.hooks?.preload_plugins?.map((hook) => { const configuration = plugins.find((plugin) => plugin.identifier === hook.identifier) ?.configuration || {}; return { ...hook, configuration, ...rivers[hook.screen_id] }; }); // TODO: Check is it a player later // TODO: Add more dict if needed from the screen component, styles, data etc return { playerPluginId: playerScreen?.type ?? DEFAULT_PLAYER_IDENTIFIER, screenConfig: playerScreen?.general, preloadHooks, }; } if (!actionIdentifier) { return null; } const isPlayer: boolean = [ DEFAULT_PLAYER_IDENTIFIER, KALTURA_PLAYER_IDENTIFIER, ].includes(actionIdentifier); if (!isPlayer) { return null; } return { playerPluginId: actionIdentifier }; }; const positionAbsolute: ViewStyle = { position: "absolute" }; const LiveImageComponent = (props: LiveImageProps) => { const { item, style: componentStyle, cellUUID, imageKey, actionIdentifier, state, } = props; const accessibilityState = useAccessibilityState({}); const isScreenReaderEnabled = accessibilityState.screenReaderEnabled; const component = useUIComponentContext(); // Fix for blinking on state change const style = React.useMemo( () => ({ ...componentStyle, backgroundColor: "transparent" }), [componentStyle] ); const componentType = React.useMemo( () => getPropComponentType(component), [component?.id] ); const { id: componentId, rules: { player_screen_id, audio_muted_by_default = false }, } = component; const isCurrentlyFocused = useTrackCurrentAutoScrollingElement(componentId); const { playerPluginId, screenConfig, preloadHooks } = getPlayerConfig(player_screen_id, actionIdentifier) ?? {}; const playableEntry = playerPluginId ? getPlayableEntry(item) : null; const plugins = appStore.get("plugins"); const playerPlugin = findPluginByIdentifier(playerPluginId, plugins); const isShouldRender = playerPlugin && playableEntry && getFocusedState(state, componentType, isCurrentlyFocused) && cellUUID && isSupportedTVForLiveImage() && !isScreenReaderEnabled; return ( <> {isShouldRender ? ( ) : null} ); }; export const LiveImage = React.memo(LiveImageComponent);