import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:url_launcher/url_launcher.dart'; class VastWebViewPlayer extends StatefulWidget { final String? vastUrl; final String? customXmlContent; final VoidCallback? onAdComplete; final VoidCallback? onAdError; final VoidCallback? onAdClick; final VoidCallback? onAdStarted; final VoidCallback? onAdStopped; final VoidCallback? onAdLoaded; final VoidCallback? onAdSkipped; final VoidCallback? onAdSkippableStateChange; final VoidCallback? onAdSizeChange; final VoidCallback? onAdLinearChange; final VoidCallback? onAdDurationChange; final VoidCallback? onAdExpandedChange; final VoidCallback? onAdRemainingTimeChange; final VoidCallback? onAdVolumeChange; final VoidCallback? onAdImpression; final VoidCallback? onAdVideoStart; final VoidCallback? onAdVideoFirstQuartile; final VoidCallback? onAdVideoMidpoint; final VoidCallback? onAdVideoThirdQuartile; final VoidCallback? onAdVideoComplete; final VoidCallback? onAdClickThru; final VoidCallback? onAdInteraction; final VoidCallback? onAdUserAcceptInvitation; final VoidCallback? onAdUserMinimize; final VoidCallback? onAdUserClose; final VoidCallback? onAdPaused; final VoidCallback? onAdPlaying; final VoidCallback? onAdLog; const VastWebViewPlayer({ super.key, this.vastUrl, this.customXmlContent, this.onAdComplete, this.onAdError, this.onAdClick, this.onAdStarted, this.onAdStopped, this.onAdLoaded, this.onAdSkipped, this.onAdSkippableStateChange, this.onAdSizeChange, this.onAdLinearChange, this.onAdDurationChange, this.onAdExpandedChange, this.onAdRemainingTimeChange, this.onAdVolumeChange, this.onAdImpression, this.onAdVideoStart, this.onAdVideoFirstQuartile, this.onAdVideoMidpoint, this.onAdVideoThirdQuartile, this.onAdVideoComplete, this.onAdClickThru, this.onAdInteraction, this.onAdUserAcceptInvitation, this.onAdUserMinimize, this.onAdUserClose, this.onAdPaused, this.onAdPlaying, this.onAdLog, }); @override State createState() => _VastWebViewPlayerState(); } class _VastWebViewPlayerState extends State { InAppWebViewController? _webViewController; @override Widget build(BuildContext context) { return InAppWebView( initialFile: "assets/vast_player_new.html", initialOptions: InAppWebViewGroupOptions( crossPlatform: InAppWebViewOptions( useShouldOverrideUrlLoading: true, mediaPlaybackRequiresUserGesture: false, javaScriptEnabled: true, ), android: AndroidInAppWebViewOptions( useHybridComposition: true, mixedContentMode: AndroidMixedContentMode.MIXED_CONTENT_ALWAYS_ALLOW, ), ios: IOSInAppWebViewOptions(allowsInlineMediaPlayback: true), ), onWebViewCreated: (InAppWebViewController controller) { _webViewController = controller; _setupJavaScriptHandlers(); }, onLoadStart: (controller, url) { setState(() {}); }, onLoadStop: (controller, url) { setState(() {}); // Загружаем рекламу с указанным URL или пользовательским XML if (widget.customXmlContent != null && widget.customXmlContent!.isNotEmpty) { _loadCustomXml(widget.customXmlContent!); } else if (widget.vastUrl != null && widget.vastUrl!.isNotEmpty) { _loadVastAd(widget.vastUrl!); } }, onLoadError: (controller, url, code, message) { setState(() {}); widget.onAdError?.call(); }, onConsoleMessage: (controller, consoleMessage) { print('WebView Console: ${consoleMessage.message}'); }, shouldOverrideUrlLoading: (controller, navigationAction) async { // Разрешаем переходы по ссылкам рекламы if (navigationAction.request.url != null) { final url = navigationAction.request.url.toString(); print('Attempting to navigate to: $url'); // Вызываем callback для обработки клика widget.onAdClick?.call(); // Открываем URL в браузере try { final uri = Uri.parse(url); if (await canLaunchUrl(uri)) { await launchUrl(uri, mode: LaunchMode.externalApplication); print('Opened URL in browser: $url'); } else { print('Cannot open URL: $url'); } } catch (e) { print('Error opening URL: $e'); } // Блокируем переход в WebView, так как открываем в браузере return NavigationActionPolicy.CANCEL; } return NavigationActionPolicy.ALLOW; }, ); } Future _loadVastAd(String vastUrl) async { try { print('Loading VAST ad from URL: $vastUrl'); await _webViewController?.evaluateJavascript( source: 'loadVastAd("$vastUrl");', ); } catch (e) { print('Error loading VAST ad from URL: $e'); setState(() {}); widget.onAdError?.call(); } } Future _loadCustomXml(String xmlContent) async { try { print('Loading custom XML ad, content length: ${xmlContent.length}'); // Экранируем специальные символы в XML для JavaScript final escapedXml = xmlContent .replaceAll('\\', '\\\\') .replaceAll('"', '\\"') .replaceAll('\n', '\\n') .replaceAll('\r', '\\r'); await _webViewController?.evaluateJavascript( source: 'loadCustomXml("$escapedXml");', ); } catch (e) { print('Error loading custom XML: $e'); setState(() {}); widget.onAdError?.call(); } } void _setupJavaScriptHandlers() { if (_webViewController == null) return; // Основные события _webViewController!.addJavaScriptHandler( handlerName: 'onAdComplete', callback: (args) { print('Ad completed'); widget.onAdComplete?.call(); }, ); _webViewController!.addJavaScriptHandler( handlerName: 'onAdError', callback: (args) { print('Ad error received: $args'); widget.onAdError?.call(); }, ); _webViewController!.addJavaScriptHandler( handlerName: 'onAdClick', callback: (args) { print('Ad clicked ${args}'); widget.onAdClick?.call(); }, ); _webViewController!.addJavaScriptHandler( handlerName: 'onAdStarted', callback: (args) { print('Ad started'); widget.onAdStarted?.call(); }, ); _webViewController!.addJavaScriptHandler( handlerName: 'onAdStopped', callback: (args) { print('Ad stopped'); widget.onAdStopped?.call(); }, ); // Дополнительные события из нового HTML _webViewController!.addJavaScriptHandler( handlerName: 'onAdLoaded', callback: (args) { print('Ad loaded'); widget.onAdLoaded?.call(); }, ); _webViewController!.addJavaScriptHandler( handlerName: 'onAdSkipped', callback: (args) { print('Ad skipped'); widget.onAdSkipped?.call(); }, ); _webViewController!.addJavaScriptHandler( handlerName: 'onAdSkippableStateChange', callback: (args) { print('Ad skippable state changed: $args'); widget.onAdSkippableStateChange?.call(); }, ); _webViewController!.addJavaScriptHandler( handlerName: 'onAdSizeChange', callback: (args) { print('Ad size changed: $args'); widget.onAdSizeChange?.call(); }, ); _webViewController!.addJavaScriptHandler( handlerName: 'onAdLinearChange', callback: (args) { print('Ad linear changed: $args'); widget.onAdLinearChange?.call(); }, ); _webViewController!.addJavaScriptHandler( handlerName: 'onAdDurationChange', callback: (args) { print('Ad duration changed: $args'); widget.onAdDurationChange?.call(); }, ); _webViewController!.addJavaScriptHandler( handlerName: 'onAdExpandedChange', callback: (args) { print('Ad expanded changed: $args'); widget.onAdExpandedChange?.call(); }, ); _webViewController!.addJavaScriptHandler( handlerName: 'onAdRemainingTimeChange', callback: (args) { print('Ad remaining time changed: $args'); widget.onAdRemainingTimeChange?.call(); }, ); _webViewController!.addJavaScriptHandler( handlerName: 'onAdVolumeChange', callback: (args) { print('Ad volume changed: $args'); widget.onAdVolumeChange?.call(); }, ); _webViewController!.addJavaScriptHandler( handlerName: 'onAdImpression', callback: (args) { print('Ad impression'); widget.onAdImpression?.call(); }, ); _webViewController!.addJavaScriptHandler( handlerName: 'onAdVideoStart', callback: (args) { print('Ad video start'); widget.onAdVideoStart?.call(); }, ); _webViewController!.addJavaScriptHandler( handlerName: 'onAdVideoFirstQuartile', callback: (args) { print('Ad video first quartile'); widget.onAdVideoFirstQuartile?.call(); }, ); _webViewController!.addJavaScriptHandler( handlerName: 'onAdVideoMidpoint', callback: (args) { print('Ad video midpoint'); widget.onAdVideoMidpoint?.call(); }, ); _webViewController!.addJavaScriptHandler( handlerName: 'onAdVideoThirdQuartile', callback: (args) { print('Ad video third quartile'); widget.onAdVideoThirdQuartile?.call(); }, ); _webViewController!.addJavaScriptHandler( handlerName: 'onAdVideoComplete', callback: (args) { print('Ad video complete'); widget.onAdVideoComplete?.call(); }, ); _webViewController!.addJavaScriptHandler( handlerName: 'onAdClickThru', callback: (args) { print('Ad click through'); widget.onAdClickThru?.call(); }, ); _webViewController!.addJavaScriptHandler( handlerName: 'onAdInteraction', callback: (args) { print('Ad interaction: $args'); widget.onAdInteraction?.call(); }, ); _webViewController!.addJavaScriptHandler( handlerName: 'onAdUserAcceptInvitation', callback: (args) { print('Ad user accept invitation'); widget.onAdUserAcceptInvitation?.call(); }, ); _webViewController!.addJavaScriptHandler( handlerName: 'onAdUserMinimize', callback: (args) { print('Ad user minimize'); widget.onAdUserMinimize?.call(); }, ); _webViewController!.addJavaScriptHandler( handlerName: 'onAdUserClose', callback: (args) { print('Ad user close'); widget.onAdUserClose?.call(); }, ); _webViewController!.addJavaScriptHandler( handlerName: 'onAdPaused', callback: (args) { print('Ad paused'); widget.onAdPaused?.call(); }, ); _webViewController!.addJavaScriptHandler( handlerName: 'onAdPlaying', callback: (args) { print('Ad playing'); widget.onAdPlaying?.call(); }, ); _webViewController!.addJavaScriptHandler( handlerName: 'onAdLog', callback: (args) { print('Ad log: $args'); widget.onAdLog?.call(); }, ); } @override void dispose() { _webViewController?.dispose(); super.dispose(); } }