JJ_MKVMaker/lib/ui/browser/web_nav.dart

131 lines
4.5 KiB
Dart

import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import '../../core/download_detect.dart' show CookieExport, CookieRecord, toNetscapeCookies;
/// 브라우저 조작 (앱 안 웹뷰)
abstract class WebNav {
Future<void> load(String url);
Future<void> back();
Future<void> forward();
Future<void> reload();
Future<void> stop();
/// YouTube · Google 로그인 쿠키를 yt-dlp 용 cookies.txt 로 내보내기
Future<void> exportCookies();
/// 페이지의 동영상 · 소리 멈추기 (다른 화면으로 갈 때)
Future<void> pauseMedia();
/// 페이지에서 스크립트 실행 (YouTube 광고 건너뛰기 등)
Future<void> runScript(String js);
/// 53: 쿠키 · 사이트 데이터 · 캐시 · 방문 기록 (뒤로 · 앞으로 목록) 지우기 - 엔진이 지원하는 것만
Future<void> clearData();
/// 페이지에서 스크립트를 실행하고 결과를 받는다 (페이지 번역). 실패하면 null.
Future<Object?> evaluate(String js);
/// 페이지의 JavaScript 켜기 · 끄기 (바뀌면 지금 페이지를 다시 읽는다)
Future<void> setJavaScript(bool on);
}
/// 250: 사용자가 페이지를 옮길 때 (주소 · 뒤로 · 앞으로 · 새로고침 · 다시 시도) 알린다 - DNS 덮개는 이때만 걷는다.
/// 진행률로 걷으면 Android 는 본문 오류 뒤에 엔진의 오류 페이지를 또 읽어 (진행률 0 → 100) 덮개가 뜨자마자 걷혔다 (932 노트 10+)
class NavWatch implements WebNav {
final WebNav n;
final void Function() onUserNavigate;
NavWatch(this.n, this.onUserNavigate);
@override
Future<void> load(String url) {
onUserNavigate();
return n.load(url);
}
@override
Future<void> back() {
onUserNavigate();
return n.back();
}
@override
Future<void> forward() {
onUserNavigate();
return n.forward();
}
@override
Future<void> reload() {
onUserNavigate();
return n.reload();
}
@override
Future<void> stop() => n.stop();
@override
Future<void> exportCookies() => n.exportCookies();
@override
Future<void> pauseMedia() => n.pauseMedia();
@override
Future<void> runScript(String js) => n.runScript(js);
@override
Future<void> clearData() => n.clearData();
@override
Future<Object?> evaluate(String js) => n.evaluate(js);
@override
Future<void> setJavaScript(bool on) => n.setJavaScript(on);
}
/// 페이지의 모든 동영상 · 소리를 멈추는 스크립트
const pauseMediaScript = "document.querySelectorAll('video,audio').forEach(function(m){try{m.pause()}catch(e){}})";
/// 화면 이동 알림 (브라우저 위에 다른 화면이 올라오면 동영상을 멈춘다). MaterialApp.navigatorObservers 에 넣는다.
final browserRouteObserver = RouteObserver<ModalRoute<void>>();
/// 웹뷰 → 화면으로 알리는 통로
class BrowserHost {
final void Function(String url) onUrl;
final void Function(String title) onTitle;
final void Function(double progress) onProgress;
final void Function(bool hasVideo) onVideo;
final void Function(bool back, bool forward) onHistory;
final void Function(WebNav nav) attach;
/// 250: 본문 (main frame) 을 읽지 못함 - 엔진이 알려 준 주소와 오류 (예: net::ERR_TUNNEL_CONNECTION_FAILED)
final void Function(String url, String error)? onMainFrameError;
const BrowserHost({
required this.onUrl,
required this.onTitle,
required this.onProgress,
required this.onVideo,
required this.onHistory,
required this.attach,
this.onMainFrameError,
});
}
/// 53: 다운로드용으로 내보낸 로그인 쿠키 파일 (평문) 을 지운다
Future<void> deleteExportedCookies(String dataDir) async {
if (dataDir.isEmpty) return;
for (final name in [CookieExport.fileName, '${CookieExport.fileName}.tmp']) {
try {
final f = File('$dataDir${Platform.pathSeparator}$name');
if (await f.exists()) await f.delete();
} catch (_) {}
}
}
/// 로그인 쿠키를 yt-dlp 용 cookies.txt 로 쓴다 (Edge · Chrome 같은 파일). 임시 파일에 다 쓴 뒤 한 번에 바꿔 넣는다
/// (yt-dlp 가 쓰는 도중의 파일을 읽지 않도록)
Future<void> writeLoginCookies(String dataDir, List<CookieRecord> all) async {
if (dataDir.isEmpty || all.isEmpty) return;
final target = '$dataDir${Platform.pathSeparator}cookies_youtube.txt';
final tmp = File('$target.tmp');
await tmp.parent.create(recursive: true);
await tmp.writeAsString(toNetscapeCookies(all), flush: true);
await tmp.rename(target);
}