Luxup

Outstream VK

Jun 26th, 2026
25
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.15 KB | None | 0 0
  1. <div id="outstream_%SITE_AREA_ID%_%DIV_SUB_ID%" style="position: relative; width: 100%;"></div>
  2. <script type="text/javascript">
  3. (() => {
  4.  
  5. const CONFIG = {
  6. containerId: 'outstream_%SITE_AREA_ID%_%DIV_SUB_ID%',
  7. siteId: 221350,
  8. baseBanner: { blockId: 723779, viewTimeMs: 2500 },
  9. fallbackBanner: { blockId: 723777 },
  10. videoDemand: {
  11. network: 'vk',
  12. placementId: '2014812',
  13. timeoutMs: 5000
  14. }
  15. };
  16.  
  17. const mainContainer = document.getElementById(CONFIG.containerId);
  18. if (!mainContainer) return;
  19.  
  20. const isDebug = /[?&]outstream_debug=1/.test(window.location.search);
  21. const logger = (...args) => {
  22. if (isDebug) console.log(`[Waterfall | ${CONFIG.containerId}] ->`, ...args);
  23. };
  24.  
  25. mainContainer.style.position = 'relative';
  26.  
  27. const layerVideo = document.createElement('div');
  28. layerVideo.style.cssText = 'position: relative; width: 100%; min-height: 0px; z-index: 1;';
  29. mainContainer.appendChild(layerVideo);
  30.  
  31. const layerBase = document.createElement('div');
  32. layerBase.style.cssText = 'position: relative; width: 100%; z-index: 10000; background: #fff; transition: opacity 0.3s;';
  33. layerVideo.appendChild(layerBase);
  34.  
  35. const demandWrapper = document.createElement('div');
  36. demandWrapper.style.cssText = 'position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; opacity: 0;';
  37. layerVideo.appendChild(demandWrapper);
  38.  
  39. let state = {
  40. baseViewed: false, baseEmpty: false, demandStarted: false,
  41. videoReady: false, videoFailed: false, videoCompleted: false,
  42. videoShown: false, finalized: false
  43. };
  44.  
  45. const loadLuxupBlock = (wrapperDiv, siteId, blockId) => {
  46. const divId = "lx_" + blockId + "_" + Math.floor(Math.random() * 10000);
  47. const el = document.createElement('div'); el.id = divId; wrapperDiv.appendChild(el);
  48. (window['__lxGc__'] = window['__lxGc__'] || {'s':{},'b':0})['cmd'] = window['__lxGc__']['cmd'] || [];
  49. window['__lxGc__']['cmd'].push(() => window['__lxGc__']['display'](divId, "_"+siteId, "_"+blockId));
  50. };
  51.  
  52. const showFallback = (reason) => {
  53. if (state.finalized) return;
  54. state.finalized = true;
  55. mainContainer.innerHTML = '';
  56. logger("Заглушка. Причина: " + reason);
  57. const fallbackWrapper = document.createElement('div');
  58. mainContainer.appendChild(fallbackWrapper);
  59. // Используем единый siteId из CONFIG
  60. loadLuxupBlock(fallbackWrapper, CONFIG.siteId, CONFIG.fallbackBanner.blockId);
  61. };
  62.  
  63. const checkFlow = () => {
  64. if (state.finalized) return;
  65. const baseDone = state.baseViewed || state.baseEmpty;
  66.  
  67. if (baseDone) {
  68. if (!state.demandStarted) { startVideoDemand(); return; }
  69.  
  70. if (state.videoCompleted) {
  71. showFallback('Видео завершило показ креатива');
  72. }
  73. else if (state.videoFailed) {
  74. logger('Видео недоступно. Оставляем первый баннер.');
  75. demandWrapper.style.display = 'none';
  76. layerBase.style.display = '';
  77. state.finalized = true;
  78. }
  79. else if (state.videoReady && !state.videoShown) {
  80. state.videoShown = true;
  81. logger('Показываем Video Demand');
  82. layerBase.style.display = 'none';
  83. demandWrapper.style.position = 'relative';
  84. demandWrapper.style.opacity = '1';
  85. demandWrapper.style.pointerEvents = 'auto';
  86. }
  87. }
  88. };
  89.  
  90. const startVideoDemand = () => {
  91. if (state.demandStarted) return;
  92. state.demandStarted = true;
  93. Adapters[CONFIG.videoDemand.network](demandWrapper, CONFIG.videoDemand, {
  94. onReady: (msg) => { logger(msg); state.videoReady = true; checkFlow(); },
  95. onFail: (msg) => { logger(msg); state.videoFailed = true; checkFlow(); },
  96. onComplete: (msg) => { logger(msg); state.videoCompleted = true; checkFlow(); }
  97. });
  98. };
  99.  
  100. const Adapters = {
  101. vk: (wrapper, config, callbacks) => {
  102. let isStarted = false;
  103. let isEnded = false;
  104. const currentSpot = config.placementId.toString();
  105.  
  106. const vkDivId = 'adman-ads-' + currentSpot + '-' + Math.floor(Math.random() * 10000);
  107. const vkDiv = document.createElement('div');
  108. vkDiv.id = vkDivId;
  109. vkDiv.className = 'vk-adman-wrapper';
  110. vkDiv.style.cssText = 'display:inline-block;width:100%;height:100%;';
  111. wrapper.appendChild(vkDiv);
  112.  
  113. const hideCloseButton = () => {
  114. const parentContainer = mainContainer.closest('[id^="lx_"]');
  115. if (parentContainer) {
  116. const style = document.createElement('style');
  117. style.innerHTML = `#${parentContainer.id} > .lx_close_button { display: none !important; }`;
  118. document.head.appendChild(style);
  119. logger(`Кнопка закрытия скрыта для блока #${parentContainer.id}`);
  120. }
  121. };
  122.  
  123. const currentTimeoutMs = config.timeoutMs || 5000;
  124. const fallbackTimeout = setTimeout(() => {
  125. if (!isStarted && !isEnded) {
  126. isEnded = true;
  127. wrapper.innerHTML = '';
  128. callbacks.onFail(`VK: Таймаут ответа аукциона (${currentTimeoutMs / 1000} сек)`);
  129. }
  130. }, currentTimeoutMs);
  131.  
  132. if (!window.AdManPlayer) {
  133. const func = function() { this.push(Array.prototype.slice.apply(arguments)); };
  134. window.AdManPlayer = func.bind(window._AdManPlayerInit = []);
  135. window.AdManSDK = func.bind(window._AdManSDKInit = []);
  136. }
  137.  
  138. window.AdManPlayer({
  139. container: '#' + vkDivId,
  140. slot: currentSpot,
  141. onReady: () => {
  142. if (isEnded) return;
  143. if (!isStarted) {
  144. isStarted = true;
  145. clearTimeout(fallbackTimeout);
  146. callbacks.onReady('VK: Реклама готова (onReady)');
  147. }
  148. },
  149. onStarted: () => {
  150. if (isEnded) return;
  151. if (!isStarted) {
  152. isStarted = true;
  153. clearTimeout(fallbackTimeout);
  154. callbacks.onReady('VK: Реклама стартовала (onStarted)');
  155. }
  156. },
  157. onError: (err) => {
  158. if (isEnded) return;
  159. isEnded = true;
  160. clearTimeout(fallbackTimeout);
  161. wrapper.innerHTML = '';
  162.  
  163. hideCloseButton();
  164.  
  165. if (isStarted) {
  166. callbacks.onComplete('VK: Ошибка во время показа');
  167. } else {
  168. callbacks.onFail('VK: Нет рекламы (False Fill / Error)');
  169. }
  170. },
  171. onCompleted: () => {
  172. if (isEnded) return;
  173. isEnded = true;
  174. wrapper.innerHTML = '';
  175. hideCloseButton();
  176. callbacks.onComplete('VK: Видео завершено');
  177. },
  178. onSkipped: () => {
  179. if (isEnded) return;
  180. isEnded = true;
  181. wrapper.innerHTML = '';
  182. hideCloseButton();
  183. callbacks.onComplete('VK: Видео пропущено');
  184. },
  185. onClosed: () => {
  186. if (isEnded) return;
  187. isEnded = true;
  188. wrapper.innerHTML = '';
  189. hideCloseButton();
  190. callbacks.onComplete('VK: Блок закрыт');
  191. },
  192. onPlayed: () => logger(`VK: Воспроизведение`),
  193. onPaused: () => logger(`VK: Пауза`),
  194. onClicked: () => logger(`VK: Клик по рекламе`)
  195. });
  196.  
  197. logger(`Загрузка vk-adman.js (Advanced Mode)...`);
  198. const s = document.createElement("script");
  199. s.src = 'https://ad.mail.ru/static/vk-adman.js';
  200. s.async = true;
  201. s.onerror = () => {
  202. if (!isEnded) {
  203. isEnded = true;
  204. clearTimeout(fallbackTimeout);
  205. wrapper.innerHTML = '';
  206. callbacks.onFail('Ошибка загрузки библиотеки VK');
  207. }
  208. };
  209. document.head.appendChild(s);
  210. }
  211. };
  212.  
  213. logger('Старт Base Banner');
  214. // Использование единого siteId
  215. loadLuxupBlock(layerBase, CONFIG.siteId, CONFIG.baseBanner.blockId);
  216.  
  217. const startViewabilityTracker = () => {
  218. let timeInView = 0;
  219. let isVisible = false;
  220. const requiredTime = CONFIG.baseBanner.viewTimeMs;
  221. const tickRate = 100;
  222.  
  223. const io = new IntersectionObserver((entries) => {
  224. isVisible = entries[0].isIntersecting;
  225. }, { threshold: 0.1 });
  226.  
  227. io.observe(mainContainer);
  228.  
  229. const timer = setInterval(() => {
  230. if (isVisible && !document.hidden) {
  231. timeInView += tickRate;
  232.  
  233. if (timeInView >= requiredTime) {
  234. clearInterval(timer);
  235. io.disconnect();
  236. logger(`Баннер просмотрен ${requiredTime}мс`);
  237. state.baseViewed = true;
  238. checkFlow();
  239. }
  240. }
  241. }, tickRate);
  242. };
  243.  
  244. const checkBaseStatus = () => {
  245. if (state.finalized) return false;
  246.  
  247. if (layerBase.querySelector('.__lxG__not_empty_block')) {
  248. logger('Баннер загружен, ждем попадания во вьюпорт');
  249. startViewabilityTracker();
  250. return true;
  251. }
  252. if (layerBase.querySelector('.__lxG__rendered_none')) {
  253. state.baseEmpty = true;
  254. checkFlow();
  255. return true;
  256. }
  257.  
  258. return false;
  259. };
  260.  
  261. const baseObserver = new MutationObserver(() => {
  262. if (checkBaseStatus()) {
  263. baseObserver.disconnect();
  264. }
  265. });
  266.  
  267. baseObserver.observe(layerBase, {
  268. childList: true,
  269. subtree: true,
  270. attributes: true,
  271. attributeFilter: ['class']
  272. });
  273.  
  274. if (checkBaseStatus()) {
  275. baseObserver.disconnect();
  276. }
  277. })();
  278. </script>
Advertisement
Add Comment
Please, Sign In to add comment