pre-roll ads demo
This commit is contained in:
parent
e3ea004f67
commit
6041c53748
11 changed files with 344 additions and 112 deletions
|
@ -32,6 +32,7 @@ ENABLE_FILE_REACTIONS=false
|
|||
ENABLE_CREATOR_REACTIONS=false
|
||||
ENABLE_NO_SOURCE_CLAIMS=false
|
||||
CHANNEL_STAKED_LEVEL_VIDEO_COMMENTS=4
|
||||
ENABLE_PREROLL_ADS=false
|
||||
|
||||
# OG
|
||||
OG_TITLE_SUFFIX=| lbry.tv
|
||||
|
|
|
@ -37,6 +37,7 @@ const config = {
|
|||
ENABLE_CREATOR_REACTIONS: process.env.ENABLE_CREATOR_REACTIONS === 'true',
|
||||
ENABLE_NO_SOURCE_CLAIMS: process.env.ENABLE_NO_SOURCE_CLAIMS === 'true',
|
||||
CHANNEL_STAKED_LEVEL_VIDEO_COMMENTS: process.env.CHANNEL_STAKED_LEVEL_VIDEO_COMMENTS,
|
||||
ENABLE_PREROLL_ADS: process.env.ENABLE_PREROLL_ADS === 'true',
|
||||
SIMPLE_SITE: process.env.SIMPLE_SITE === 'true',
|
||||
SHOW_ADS: process.env.SHOW_ADS === 'true',
|
||||
PINNED_URI_1: process.env.PINNED_URI_1,
|
||||
|
|
|
@ -203,6 +203,7 @@
|
|||
"tree-kill": "^1.1.0",
|
||||
"unist-util-visit": "^2.0.3",
|
||||
"uuid": "^8.3.2",
|
||||
"vast-client": "^3.1.1",
|
||||
"video.js": "^7.10.1",
|
||||
"videojs-contrib-quality-levels": "^2.0.9",
|
||||
"videojs-event-tracking": "^1.0.1",
|
||||
|
|
|
@ -26,10 +26,10 @@ if (isProduction) {
|
|||
// @endif
|
||||
|
||||
type Analytics = {
|
||||
error: string => Promise<any>,
|
||||
error: (string) => Promise<any>,
|
||||
sentryError: ({} | string, {}) => Promise<any>,
|
||||
pageView: (string, ?string) => void,
|
||||
setUser: Object => void,
|
||||
setUser: (Object) => void,
|
||||
toggleInternal: (boolean, ?boolean) => void,
|
||||
apiLogView: (string, string, string, ?number, ?() => void) => Promise<any>,
|
||||
apiLogPublish: (ChannelClaim | StreamClaim) => void,
|
||||
|
@ -51,13 +51,16 @@ type Analytics = {
|
|||
readyState: number,
|
||||
}
|
||||
) => void,
|
||||
adsFetchedEvent: () => void,
|
||||
adsReceivedEvent: (any) => void,
|
||||
adsErrorEvent: (any) => void,
|
||||
emailProvidedEvent: () => void,
|
||||
emailVerifiedEvent: () => void,
|
||||
rewardEligibleEvent: () => void,
|
||||
startupEvent: () => void,
|
||||
purchaseEvent: number => void,
|
||||
readyEvent: number => void,
|
||||
openUrlEvent: string => void,
|
||||
purchaseEvent: (number) => void,
|
||||
readyEvent: (number) => void,
|
||||
openUrlEvent: (string) => void,
|
||||
};
|
||||
|
||||
type LogPublishParams = {
|
||||
|
@ -75,8 +78,8 @@ if (window.localStorage.getItem(SHARE_INTERNAL) === 'true') internalAnalyticsEna
|
|||
// @endif
|
||||
|
||||
const analytics: Analytics = {
|
||||
error: message => {
|
||||
return new Promise(resolve => {
|
||||
error: (message) => {
|
||||
return new Promise((resolve) => {
|
||||
if (internalAnalyticsEnabled && isProduction) {
|
||||
return Lbryio.call('event', 'desktop_error', { error_message: message }).then(() => {
|
||||
resolve(true);
|
||||
|
@ -87,9 +90,9 @@ const analytics: Analytics = {
|
|||
});
|
||||
},
|
||||
sentryError: (error, errorInfo) => {
|
||||
return new Promise(resolve => {
|
||||
return new Promise((resolve) => {
|
||||
if (internalAnalyticsEnabled && isProduction) {
|
||||
Sentry.withScope(scope => {
|
||||
Sentry.withScope((scope) => {
|
||||
scope.setExtras(errorInfo);
|
||||
const eventId = Sentry.captureException(error);
|
||||
resolve(eventId);
|
||||
|
@ -114,7 +117,7 @@ const analytics: Analytics = {
|
|||
MatomoInstance.trackPageView(params);
|
||||
}
|
||||
},
|
||||
setUser: userId => {
|
||||
setUser: (userId) => {
|
||||
if (internalAnalyticsEnabled && userId) {
|
||||
window._paq.push(['setUserId', String(userId)]);
|
||||
// @if TARGET='app'
|
||||
|
@ -188,7 +191,7 @@ const analytics: Analytics = {
|
|||
}
|
||||
},
|
||||
|
||||
apiSyncTags: params => {
|
||||
apiSyncTags: (params) => {
|
||||
if (internalAnalyticsEnabled && isProduction) {
|
||||
Lbryio.call('content_tags', 'sync', params);
|
||||
}
|
||||
|
@ -238,10 +241,19 @@ const analytics: Analytics = {
|
|||
});
|
||||
}
|
||||
},
|
||||
playerLoadedEvent: embedded => {
|
||||
adsFetchedEvent: () => {
|
||||
sendMatomoEvent('Media', 'AdsFetched');
|
||||
},
|
||||
adsReceivedEvent: (response) => {
|
||||
sendMatomoEvent('Media', 'AdsReceived', JSON.stringify(response));
|
||||
},
|
||||
adsErrorEvent: (response) => {
|
||||
sendMatomoEvent('Media', 'AdsError', JSON.stringify(response));
|
||||
},
|
||||
playerLoadedEvent: (embedded) => {
|
||||
sendMatomoEvent('Player', 'Loaded', embedded ? 'embedded' : 'onsite');
|
||||
},
|
||||
playerStartedEvent: embedded => {
|
||||
playerStartedEvent: (embedded) => {
|
||||
sendMatomoEvent('Player', 'Started', embedded ? 'embedded' : 'onsite');
|
||||
},
|
||||
tagFollowEvent: (tag, following) => {
|
||||
|
@ -316,7 +328,7 @@ analytics.pageView(
|
|||
|
||||
// Listen for url changes and report
|
||||
// This will include search queries
|
||||
history.listen(location => {
|
||||
history.listen((location) => {
|
||||
const { pathname, search } = location;
|
||||
|
||||
const page = `${pathname}${search}`;
|
||||
|
|
|
@ -7,8 +7,9 @@ import { makeSelectContentPositionForUri } from 'redux/selectors/content';
|
|||
import VideoViewer from './view';
|
||||
import { withRouter } from 'react-router';
|
||||
import { doClaimEligiblePurchaseRewards } from 'redux/actions/rewards';
|
||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import { makeSelectClientSetting, selectHomepageData } from 'redux/selectors/settings';
|
||||
import { toggleVideoTheaterMode, doSetClientSetting } from 'redux/actions/settings';
|
||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
|
||||
const select = (state, props) => {
|
||||
const { search } = props.location;
|
||||
|
@ -26,19 +27,21 @@ const select = (state, props) => {
|
|||
hasFileInfo: Boolean(makeSelectFileInfoForUri(props.uri)(state)),
|
||||
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
homepageData: selectHomepageData(state),
|
||||
authenticated: selectUserVerifiedEmail(state),
|
||||
};
|
||||
};
|
||||
|
||||
const perform = dispatch => ({
|
||||
changeVolume: volume => dispatch(doChangeVolume(volume)),
|
||||
const perform = (dispatch) => ({
|
||||
changeVolume: (volume) => dispatch(doChangeVolume(volume)),
|
||||
savePosition: (uri, position) => dispatch(savePosition(uri, position)),
|
||||
clearPosition: uri => dispatch(clearPosition(uri)),
|
||||
changeMute: muted => dispatch(doChangeMute(muted)),
|
||||
clearPosition: (uri) => dispatch(clearPosition(uri)),
|
||||
changeMute: (muted) => dispatch(doChangeMute(muted)),
|
||||
doAnalyticsView: (uri, timeToStart) => dispatch(doAnalyticsView(uri, timeToStart)),
|
||||
doAnalyticsBuffer: (uri, bufferData) => dispatch(doAnalyticsBuffer(uri, bufferData)),
|
||||
claimRewards: () => dispatch(doClaimEligiblePurchaseRewards()),
|
||||
toggleVideoTheaterMode: () => dispatch(toggleVideoTheaterMode()),
|
||||
setVideoPlaybackRate: rate => dispatch(doSetClientSetting(SETTINGS.VIDEO_PLAYBACK_RATE, rate)),
|
||||
setVideoPlaybackRate: (rate) => dispatch(doSetClientSetting(SETTINGS.VIDEO_PLAYBACK_RATE, rate)),
|
||||
});
|
||||
|
||||
export default withRouter(connect(select, perform)(VideoViewer));
|
||||
|
|
|
@ -49,6 +49,7 @@ type Props = {
|
|||
startMuted: boolean,
|
||||
autoplay: boolean,
|
||||
toggleVideoTheaterMode: () => void,
|
||||
adUrl: ?string,
|
||||
};
|
||||
|
||||
type VideoJSOptions = {
|
||||
|
@ -171,7 +172,17 @@ class LbryVolumeBarClass extends videojs.getComponent(VIDEOJS_VOLUME_BAR_CLASS)
|
|||
properties for this component should be kept to ONLY those that if changed should REQUIRE an entirely new videojs element
|
||||
*/
|
||||
export default React.memo<Props>(function VideoJs(props: Props) {
|
||||
const { autoplay, startMuted, source, sourceType, poster, isAudio, onPlayerReady, toggleVideoTheaterMode } = props;
|
||||
const {
|
||||
autoplay,
|
||||
startMuted,
|
||||
source,
|
||||
sourceType,
|
||||
poster,
|
||||
isAudio,
|
||||
onPlayerReady,
|
||||
toggleVideoTheaterMode,
|
||||
adUrl,
|
||||
} = props;
|
||||
|
||||
const [reload, setReload] = useState('initial');
|
||||
|
||||
|
@ -194,6 +205,13 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
|||
},
|
||||
};
|
||||
|
||||
// if (adUrl) {
|
||||
// // Add the adUrl to the first entry in `sources`
|
||||
// // After the ad is finished, it will be removed as a prop to this component
|
||||
// videoJsOptions.sources.unshift({ src: adUrl, type: 'video/mp4' });
|
||||
// console.log('added ad');
|
||||
// }
|
||||
|
||||
const tapToUnmuteRef = useRef();
|
||||
const tapToRetryRef = useRef();
|
||||
|
||||
|
@ -333,9 +351,11 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
|||
}
|
||||
}
|
||||
|
||||
function onEnded() {
|
||||
showTapButton(TAP.NONE);
|
||||
}
|
||||
const onEnded = React.useCallback(() => {
|
||||
if (!adUrl) {
|
||||
showTapButton(TAP.NONE);
|
||||
}
|
||||
}, [adUrl]);
|
||||
|
||||
function handleKeyDown(e: KeyboardEvent) {
|
||||
const player = playerRef.current;
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
// @flow
|
||||
import { ENABLE_PREROLL_ADS } from 'config';
|
||||
import * as PAGES from 'constants/pages';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import React, { useEffect, useState, useContext, useCallback } from 'react';
|
||||
import { stopContextMenu } from 'util/context-menu';
|
||||
import type { Player } from './internal/videojs';
|
||||
|
@ -13,6 +16,10 @@ import FileViewerEmbeddedEnded from 'web/component/fileViewerEmbeddedEnded';
|
|||
import FileViewerEmbeddedTitle from 'component/fileViewerEmbeddedTitle';
|
||||
import LoadingScreen from 'component/common/loading-screen';
|
||||
import { addTheaterModeButton } from './internal/theater-mode';
|
||||
import { useGetAds } from 'effects/use-get-ads';
|
||||
import Button from 'component/button';
|
||||
import I18nMessage from 'component/i18nMessage';
|
||||
import { useHistory } from 'react-router';
|
||||
|
||||
const PLAY_TIMEOUT_ERROR = 'play_timeout_error';
|
||||
const PLAY_TIMEOUT_LIMIT = 2000;
|
||||
|
@ -39,6 +46,16 @@ type Props = {
|
|||
clearPosition: (string) => void,
|
||||
toggleVideoTheaterMode: () => void,
|
||||
setVideoPlaybackRate: (number) => void,
|
||||
authenticated: boolean,
|
||||
homepageData: {
|
||||
PRIMARY_CONTENT_CHANNEL_IDS?: Array<string>,
|
||||
ENLIGHTENMENT_CHANNEL_IDS?: Array<string>,
|
||||
GAMING_CHANNEL_IDS?: Array<string>,
|
||||
SCIENCE_CHANNEL_IDS?: Array<string>,
|
||||
TECHNOLOGY_CHANNEL_IDS?: Array<string>,
|
||||
COMMUNITY_CHANNEL_IDS?: Array<string>,
|
||||
FINCANCE_CHANNEL_IDS?: Array<string>,
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -69,22 +86,48 @@ function VideoViewer(props: Props) {
|
|||
desktopPlayStartTime,
|
||||
toggleVideoTheaterMode,
|
||||
setVideoPlaybackRate,
|
||||
homepageData,
|
||||
authenticated,
|
||||
} = props;
|
||||
const {
|
||||
PRIMARY_CONTENT_CHANNEL_IDS = [],
|
||||
ENLIGHTENMENT_CHANNEL_IDS = [],
|
||||
GAMING_CHANNEL_IDS = [],
|
||||
SCIENCE_CHANNEL_IDS = [],
|
||||
TECHNOLOGY_CHANNEL_IDS = [],
|
||||
COMMUNITY_CHANNEL_IDS = [],
|
||||
FINCANCE_CHANNEL_IDS = [],
|
||||
} = homepageData;
|
||||
const adApprovedChannelIds = [
|
||||
...PRIMARY_CONTENT_CHANNEL_IDS,
|
||||
...ENLIGHTENMENT_CHANNEL_IDS,
|
||||
...GAMING_CHANNEL_IDS,
|
||||
...SCIENCE_CHANNEL_IDS,
|
||||
...TECHNOLOGY_CHANNEL_IDS,
|
||||
...COMMUNITY_CHANNEL_IDS,
|
||||
...FINCANCE_CHANNEL_IDS,
|
||||
];
|
||||
const claimId = claim && claim.claim_id;
|
||||
const channelClaimId = claim && claim.signing_channel && claim.signing_channel.claim_id;
|
||||
const isAudio = contentType.includes('audio');
|
||||
const forcePlayer = FORCE_CONTENT_TYPE_PLAYER.includes(contentType);
|
||||
const {
|
||||
location: { pathname },
|
||||
} = useHistory();
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [showAutoplayCountdown, setShowAutoplayCountdown] = useState(false);
|
||||
const [isEndededEmbed, setIsEndededEmbed] = useState(false);
|
||||
const vjsCallbackDataRef: any = React.useRef();
|
||||
|
||||
const previousUri = usePrevious(uri);
|
||||
const embedded = useContext(EmbedContext);
|
||||
const approvedVideo = Boolean(channelClaimId) && adApprovedChannelIds.includes(channelClaimId);
|
||||
const adsEnabled = ENABLE_PREROLL_ADS && !authenticated && !embedded && approvedVideo;
|
||||
const [adUrl, setAdUrl, isFetchingAd] = useGetAds(adsEnabled);
|
||||
/* isLoading was designed to show loading screen on first play press, rather than completely black screen, but
|
||||
breaks because some browsers (e.g. Firefox) block autoplay but leave the player.play Promise pending */
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const previousUri = usePrevious(uri);
|
||||
const embedded = useContext(EmbedContext);
|
||||
|
||||
// force everything to recent when URI changes, can cause weird corner cases otherwise (e.g. navigate while autoplay is true)
|
||||
useEffect(() => {
|
||||
if (uri && previousUri && uri !== previousUri) {
|
||||
|
@ -123,13 +166,18 @@ function VideoViewer(props: Props) {
|
|||
});
|
||||
}
|
||||
|
||||
function onEnded() {
|
||||
const onEnded = React.useCallback(() => {
|
||||
if (adUrl) {
|
||||
setAdUrl(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (embedded) {
|
||||
setIsEndededEmbed(true);
|
||||
} else if (autoplaySetting) {
|
||||
setShowAutoplayCountdown(true);
|
||||
}
|
||||
}
|
||||
}, [embedded, setIsEndededEmbed, autoplaySetting, setShowAutoplayCountdown, adUrl, setAdUrl]);
|
||||
|
||||
function onPlay() {
|
||||
setIsLoading(false);
|
||||
|
@ -151,86 +199,83 @@ function VideoViewer(props: Props) {
|
|||
player.playbackRate(vjsCallbackDataRef.current.videoPlaybackRate);
|
||||
}
|
||||
}
|
||||
const playerReadyDependencyList = [uri, adUrl, embedded, autoplayIfEmbedded];
|
||||
if (!IS_WEB) {
|
||||
playerReadyDependencyList.push(desktopPlayStartTime);
|
||||
}
|
||||
|
||||
const onPlayerReady = useCallback(
|
||||
(player: Player) => {
|
||||
if (!embedded) {
|
||||
player.muted(muted);
|
||||
player.volume(volume);
|
||||
player.playbackRate(videoPlaybackRate);
|
||||
addTheaterModeButton(player, toggleVideoTheaterMode);
|
||||
}
|
||||
const onPlayerReady = useCallback((player: Player) => {
|
||||
if (!embedded) {
|
||||
player.muted(muted);
|
||||
player.volume(volume);
|
||||
player.playbackRate(videoPlaybackRate);
|
||||
addTheaterModeButton(player, toggleVideoTheaterMode);
|
||||
}
|
||||
|
||||
const shouldPlay = !embedded || autoplayIfEmbedded;
|
||||
// https://blog.videojs.com/autoplay-best-practices-with-video-js/#Programmatic-Autoplay-and-Success-Failure-Detection
|
||||
if (shouldPlay) {
|
||||
const playPromise = player.play();
|
||||
const timeoutPromise = new Promise((resolve, reject) =>
|
||||
setTimeout(() => reject(PLAY_TIMEOUT_ERROR), PLAY_TIMEOUT_LIMIT)
|
||||
);
|
||||
const shouldPlay = !embedded || autoplayIfEmbedded;
|
||||
// https://blog.videojs.com/autoplay-best-practices-with-video-js/#Programmatic-Autoplay-and-Success-Failure-Detection
|
||||
if (shouldPlay) {
|
||||
const playPromise = player.play();
|
||||
const timeoutPromise = new Promise((resolve, reject) =>
|
||||
setTimeout(() => reject(PLAY_TIMEOUT_ERROR), PLAY_TIMEOUT_LIMIT)
|
||||
);
|
||||
|
||||
Promise.race([playPromise, timeoutPromise]).catch((error) => {
|
||||
if (PLAY_TIMEOUT_ERROR) {
|
||||
const retryPlayPromise = player.play();
|
||||
Promise.race([retryPlayPromise, timeoutPromise]).catch((error) => {
|
||||
setIsLoading(false);
|
||||
setIsPlaying(false);
|
||||
});
|
||||
} else {
|
||||
Promise.race([playPromise, timeoutPromise]).catch((error) => {
|
||||
if (PLAY_TIMEOUT_ERROR) {
|
||||
const retryPlayPromise = player.play();
|
||||
Promise.race([retryPlayPromise, timeoutPromise]).catch((error) => {
|
||||
setIsLoading(false);
|
||||
setIsPlaying(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
setIsLoading(false);
|
||||
setIsPlaying(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setIsLoading(shouldPlay); // if we are here outside of an embed, we're playing
|
||||
|
||||
// PR: #5535
|
||||
// Move the restoration to a later `loadedmetadata` phase to counter the
|
||||
// delay from the header-fetch. This is a temp change until the next
|
||||
// re-factoring.
|
||||
player.on('loadedmetadata', () => restorePlaybackRate(player));
|
||||
|
||||
player.on('tracking:buffered', doTrackingBuffered);
|
||||
player.on('tracking:firstplay', doTrackingFirstPlay);
|
||||
player.on('ended', onEnded);
|
||||
player.on('play', onPlay);
|
||||
player.on('pause', () => {
|
||||
setIsPlaying(false);
|
||||
handlePosition(player);
|
||||
});
|
||||
player.on('error', () => {
|
||||
const error = player.error();
|
||||
|
||||
if (error) {
|
||||
analytics.sentryError('Video.js error', error);
|
||||
}
|
||||
|
||||
setIsLoading(shouldPlay); // if we are here outside of an embed, we're playing
|
||||
|
||||
// PR: #5535
|
||||
// Move the restoration to a later `loadedmetadata` phase to counter the
|
||||
// delay from the header-fetch. This is a temp change until the next
|
||||
// re-factoring.
|
||||
player.on('loadedmetadata', () => restorePlaybackRate(player));
|
||||
|
||||
player.on('tracking:buffered', doTrackingBuffered);
|
||||
player.on('tracking:firstplay', doTrackingFirstPlay);
|
||||
player.on('ended', onEnded);
|
||||
player.on('play', onPlay);
|
||||
player.on('pause', () => {
|
||||
setIsPlaying(false);
|
||||
handlePosition(player);
|
||||
});
|
||||
player.on('error', () => {
|
||||
const error = player.error();
|
||||
if (error) {
|
||||
analytics.sentryError('Video.js error', error);
|
||||
}
|
||||
});
|
||||
player.on('volumechange', () => {
|
||||
if (player) {
|
||||
changeVolume(player.volume());
|
||||
changeMute(player.muted());
|
||||
}
|
||||
});
|
||||
player.on('ratechange', () => {
|
||||
const HAVE_NOTHING = 0; // https://docs.videojs.com/player#readyState
|
||||
if (player && player.readyState() !== HAVE_NOTHING) {
|
||||
// The playbackRate occasionally resets to 1, typically when loading a fresh video or when 'src' changes.
|
||||
// Videojs says it's a browser quirk (https://github.com/videojs/video.js/issues/2516).
|
||||
// [x] Don't update 'videoPlaybackRate' in this scenario.
|
||||
// [ ] Ideally, the controlBar should be hidden to prevent users from changing the rate while loading.
|
||||
setVideoPlaybackRate(player.playbackRate());
|
||||
}
|
||||
});
|
||||
|
||||
if (position) {
|
||||
player.currentTime(position);
|
||||
});
|
||||
player.on('volumechange', () => {
|
||||
if (player) {
|
||||
changeVolume(player.volume());
|
||||
changeMute(player.muted());
|
||||
}
|
||||
player.on('dispose', () => {
|
||||
handlePosition(player);
|
||||
});
|
||||
},
|
||||
IS_WEB ? [uri] : [uri, desktopPlayStartTime]
|
||||
);
|
||||
});
|
||||
player.on('ratechange', () => {
|
||||
if (player) {
|
||||
setVideoPlaybackRate(player.playbackRate());
|
||||
}
|
||||
});
|
||||
|
||||
if (position) {
|
||||
player.currentTime(position);
|
||||
}
|
||||
player.on('dispose', () => {
|
||||
handlePosition(player);
|
||||
});
|
||||
}, playerReadyDependencyList);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -245,16 +290,50 @@ function VideoViewer(props: Props) {
|
|||
{embedded && !isEndededEmbed && <FileViewerEmbeddedTitle uri={uri} />}
|
||||
{/* disable this loading behavior because it breaks when player.play() promise hangs */}
|
||||
{isLoading && <LoadingScreen status={__('Loading')} />}
|
||||
<VideoJs
|
||||
source={source}
|
||||
isAudio={isAudio}
|
||||
poster={isAudio || (embedded && !autoplayIfEmbedded) ? thumbnail : ''}
|
||||
sourceType={forcePlayer ? 'video/mp4' : contentType}
|
||||
onPlayerReady={onPlayerReady}
|
||||
startMuted={autoplayIfEmbedded}
|
||||
toggleVideoTheaterMode={toggleVideoTheaterMode}
|
||||
autoplay={!embedded || autoplayIfEmbedded}
|
||||
/>
|
||||
|
||||
{!isFetchingAd && adUrl && (
|
||||
<>
|
||||
<span className="ads__video-notify">
|
||||
{__('Advertisement')}{' '}
|
||||
<Button
|
||||
className="ads__video-close"
|
||||
icon={ICONS.REMOVE}
|
||||
title={__('Close')}
|
||||
onClick={() => setAdUrl(null)}
|
||||
/>
|
||||
</span>
|
||||
<span className="ads__video-nudge">
|
||||
<I18nMessage
|
||||
tokens={{
|
||||
sign_up: (
|
||||
<Button
|
||||
button="secondary"
|
||||
className="ads__video-link"
|
||||
label={__('Sign Up')}
|
||||
navigate={`/$/${PAGES.AUTH}?redirect=${pathname}&src=video-ad`}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
>
|
||||
%sign_up% to turn ads off.
|
||||
</I18nMessage>
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
|
||||
{!isFetchingAd && (
|
||||
<VideoJs
|
||||
adUrl={adUrl}
|
||||
source={adUrl || source}
|
||||
sourceType={forcePlayer || adUrl ? 'video/mp4' : contentType}
|
||||
isAudio={isAudio}
|
||||
poster={isAudio || (embedded && !autoplayIfEmbedded) ? thumbnail : ''}
|
||||
onPlayerReady={onPlayerReady}
|
||||
startMuted={autoplayIfEmbedded}
|
||||
toggleVideoTheaterMode={toggleVideoTheaterMode}
|
||||
autoplay={!embedded || autoplayIfEmbedded}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
53
ui/effects/use-get-ads.js
Normal file
53
ui/effects/use-get-ads.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { VASTClient } from 'vast-client';
|
||||
import analytics from 'analytics';
|
||||
|
||||
// const PRE_ROLL_ADS_PROVIDER = '`https://tag.targeting.unrulymedia.com/rmp/216276/0/vast2?vastfw=vpaid&w=300&h=500&url=';
|
||||
|
||||
// Ignores any call made 1 minutes or less after the last successful ad
|
||||
const ADS_CAP_LEVEL = 1 * 60 * 1000;
|
||||
const vastClient = new VASTClient(0, ADS_CAP_LEVEL);
|
||||
|
||||
export function useGetAds(adsEnabled: boolean): [?string, (?string) => void, boolean] {
|
||||
const [isFetching, setIsFetching] = React.useState(true);
|
||||
const [adUrl, setAdUrl] = React.useState();
|
||||
|
||||
React.useEffect(() => {
|
||||
// if (!adsEnabled) {
|
||||
// setIsFetching(false);
|
||||
// return;
|
||||
// }
|
||||
|
||||
analytics.adsFetchedEvent();
|
||||
// const encodedHref = encodeURI(window.location.href);
|
||||
// const url = `${PRE_ROLL_ADS_PROVIDER}${encodedHref}`;
|
||||
|
||||
const url = 'https://raw.githubusercontent.com/dailymotion/vast-client-js/master/test/vastfiles/sample.xml';
|
||||
|
||||
vastClient
|
||||
.get(url)
|
||||
.then((res) => {
|
||||
if (res.ads.length > 0) {
|
||||
// Let this line error if res.ads is empty
|
||||
// I took this from an example response from Dailymotion
|
||||
// It will be caught below and sent to matomo to figure out if there if this needs to be something changed to deal with unrulys data
|
||||
// const adUrl = res.ads[0].creatives[0].mediaFiles[0].fileURL;
|
||||
|
||||
// Dummy video file
|
||||
const adUrl = 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4';
|
||||
|
||||
if (adUrl) {
|
||||
setAdUrl(adUrl);
|
||||
}
|
||||
}
|
||||
|
||||
setIsFetching(false);
|
||||
})
|
||||
.catch(() => {
|
||||
setIsFetching(false);
|
||||
});
|
||||
}, [adsEnabled]);
|
||||
|
||||
return [adUrl, setAdUrl, isFetching];
|
||||
}
|
|
@ -83,3 +83,52 @@
|
|||
.ads__claim-text--small {
|
||||
font-size: var(--font-small);
|
||||
}
|
||||
|
||||
// Pre-roll ads
|
||||
.ads__video-nudge,
|
||||
.ads__video-notify {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.ads__video-nudge {
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-white);
|
||||
font-weight: bold;
|
||||
padding: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.ads__video-notify {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
right: 0;
|
||||
top: 0;
|
||||
background-color: black;
|
||||
border-bottom-left-radius: var(--border-radius);
|
||||
color: var(--color-white);
|
||||
font-size: var(--font-small);
|
||||
padding: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.ads__video-link.button--secondary {
|
||||
font-size: var(--font-small);
|
||||
padding: var(--spacing-xs);
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
.ads__video-close {
|
||||
margin-left: var(--spacing-s);
|
||||
border-radius: var(--border-radius);
|
||||
|
||||
.icon {
|
||||
stroke: var(--color-white);
|
||||
|
||||
&:hover {
|
||||
stroke: var(--color-black);
|
||||
background-color: var(--color-white);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -137,6 +137,7 @@
|
|||
padding-right: 0;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
margin-top: 0;
|
||||
width: 100vw;
|
||||
max-width: none;
|
||||
|
||||
|
|
12
yarn.lock
12
yarn.lock
|
@ -11498,6 +11498,13 @@ vary@~1.1.2:
|
|||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
||||
|
||||
vast-client@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/vast-client/-/vast-client-3.1.1.tgz#75f044ff1554e0f193302dfa1c20c7f7006fd1f8"
|
||||
integrity sha512-ED32RnLthWgAjQiEPsbqqC4LkN8+EhFyevHVh2SsmlPr6auugjswdbv+VgaQ/d7KUH/vpZ675HzVkIqkB2ibiQ==
|
||||
dependencies:
|
||||
xmldom "^0.3.0"
|
||||
|
||||
vendors@^1.0.0:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e"
|
||||
|
@ -11998,6 +12005,11 @@ xmldom@^0.1.27:
|
|||
resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.31.tgz#b76c9a1bd9f0a9737e5a72dc37231cf38375e2ff"
|
||||
integrity sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==
|
||||
|
||||
xmldom@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.3.0.tgz#e625457f4300b5df9c2e1ecb776147ece47f3e5a"
|
||||
integrity sha512-z9s6k3wxE+aZHgXYxSTpGDo7BYOUfJsIRyoZiX6HTjwpwfS2wpQBQKa2fD+ShLyPkqDYo5ud7KitmLZ2Cd6r0g==
|
||||
|
||||
xpipe@*:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/xpipe/-/xpipe-1.0.5.tgz#8dd8bf45fc3f7f55f0e054b878f43a62614dafdf"
|
||||
|
|
Loading…
Reference in a new issue