418 lines
12 KiB
Dart
418 lines
12 KiB
Dart
|
|
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<VastWebViewPlayer> createState() => _VastWebViewPlayerState();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class _VastWebViewPlayerState extends State<VastWebViewPlayer> {
|
|||
|
|
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<void> _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<void> _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();
|
|||
|
|
}
|
|||
|
|
}
|