Я создаю полноэкранный видеоплеер во Flutter, используя webview_flutter: ^4.13.0. Игрок должен:
- Запустить непосредственно в альбомном режиме
- Скрыть полноэкранную кнопку WebView по умолчанию
- Отслеживать время воспроизведения (currentTime и длительность) из JavaScript.
- Сохранять историю просмотра (время паузы, процент завершения и т. д.)
- Восстанавливать воспроизведение, когда пользователь возвращается
- Безопасный выход из полноэкранного режима и восстановление наложений пользовательского интерфейса
- Предотвращение множественного выхода вызовы
- Мой подход правильный
- Я не оставляю утечек памяти (интервалы, блокировки ориентации, очистка WebView)
- Я правильно использую WebView API для отслеживания хода видео
- Логика _handleSafeExit верна как для Android, так и для iOS.
- Внедрение JavaScript с полноэкранной блокировкой безопасно.
1. Виджет VideoPlayerScreen
class VideoPlayerScreen extends StatefulWidget {
final String streamUrl;
final String videoId;
final String videoType;
final String? watchHistoryId;
final String? savedPauseTime;
const VideoPlayerScreen({
required this.streamUrl,
required this.videoId,
required this.videoType,
this.watchHistoryId,
this.savedPauseTime,
super.key,
});
@override
State createState() => _VideoPlayerScreenState();
}
2. Настройка WebView
Загружает URL-адрес потока, подключает прослушиватель JavaScript и отслеживает ход выполнения.
void _setupVideoPlayer() {
final controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..addJavaScriptChannel('flutterVideoPlayer', onMessageReceived: _onJsMessage)
..setNavigationDelegate(NavigationDelegate(onPageFinished: _onPageLoaded))
..loadRequest(Uri.parse(widget.streamUrl));
setState(() {
_controller = controller;
_isControllerReady = true;
});
}
3. Внедрите JavaScript, чтобы отключить полноэкранную кнопку
Встроенный iframe имеет собственный полноэкранный пользовательский интерфейс, поэтому мы скрываем его:
void _hideFullscreenButton() {
_controller.runJavaScript("""
// JS that hides fullscreen button on video element
""");
}
4. Синхронизируйте продолжительность и текущее время с помощью JS → Flutter Bridge
addJavaScriptChannel(
'flutterVideoPlayer',
onMessageReceived: (message) {
final data = json.decode(message.message);
setState(() {
_currentTime = (data['currentTime'] ?? 0).toDouble();
_duration = (data['duration'] ?? 0).toDouble();
});
},
);
5. Логика сохранения истории просмотра
Обновляет время паузы, общую продолжительность и процент завершения:
Future _handleWatchHistory() async {
if (_currentWatchHistoryId != null) {
...
await _videoPlayerAPI.addPauseTime(...);
await _videoPlayerAPI.updateCompletionPercentage(...);
}
}
6. Безопасный выход
Гарантирует отсутствие двойного нажатия и восстанавливает ориентацию/пользовательский интерфейс:
Future _handleSafeExit() async {
if (_isDisposed) return;
_isDisposed = true;
await SystemChrome.setEnabledSystemUIMode(...);
await SystemChrome.setPreferredOrientations([...]);
unawaited(_handleWatchHistory());
if (mounted) Navigator.of(context).pop();
}
7. Dispose
Очищает таймеры, интервалы JS, ориентацию и контроллер WebView:
@override
void dispose() {
_isDisposed = true;
_hideTimer?.cancel();
_controller.runJavaScript(
'if (window.__videoInterval) clearInterval(window.__videoInterval);'
);
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual);
_controller.clearCache();
super.dispose();
}
Актуальная проблема/вопросы
Код работает нормально на Android, но на iOS он полностью приводит к сбою приложения.
Мои основные вопросы:
- Правильно ли это отслеживать прогресс видео внутри WebView с помощью JavaScript?
- Это мой метод блокировать полноэкранные кнопки безопасно и надежно?
- Правильно ли написан _handleSafeExit() для предотвращения множественных всплывающих окон?
- Правильно ли я управляю ориентацией и системным пользовательским интерфейсом?
- Правильна ли очистка удаления для интервалов WebView и JS?
Вот для справки функция истории просмотра:
Future _handleWatchHistory() async {
if (_currentWatchHistoryId != null) {
...
await _videoPlayerAPI.addPauseTime(...);
await _videoPlayerAPI.updateCompletionPercentage(...);
}
}
Подробнее здесь: https://stackoverflow.com/questions/798 ... es-in-flut
Мобильная версия