refa player (#1848)

* tweak

* Reapply "opt: audio uri" (#1833)

This reverts commit 8e726f49b2.

* opt: player

* opt: player

* refa: create player

* refa: player

* opt: UaType

* fix: sb seek preview

* opt: setSub

* fix: screenshot

* opt: unnecessary final player state

* opt: player track

* opt FileSource constructor [skip ci]

* fixes

* fix: dispose player

* fix: quote

* update

* fix: multi ua & remove unused loop

* remove unneeded check [skip ci]

---------

Co-authored-by: dom <githubaccount56556@proton.me>
This commit is contained in:
My-Responsitories
2026-02-27 15:51:55 +08:00
committed by GitHub
parent 6782bee11a
commit 7276cde48a
39 changed files with 1063 additions and 1112 deletions

View File

@@ -1,13 +1,14 @@
import 'dart:async' show StreamSubscription, Timer;
import 'dart:convert' show ascii;
import 'dart:io' show Platform, File, Directory;
import 'dart:io' show Platform;
import 'dart:math' show max, min;
import 'dart:ui' as ui;
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/http/browser_ua.dart';
import 'package:PiliPlus/http/constants.dart';
import 'package:PiliPlus/http/init.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/ua_type.dart';
import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models/common/account_type.dart';
import 'package:PiliPlus/models/common/audio_normalization.dart';
@@ -31,6 +32,7 @@ import 'package:PiliPlus/plugin/pl_player/models/video_fit_type.dart';
import 'package:PiliPlus/plugin/pl_player/utils/fullscreen.dart';
import 'package:PiliPlus/services/service_locator.dart';
import 'package:PiliPlus/utils/accounts.dart';
import 'package:PiliPlus/utils/asset_utils.dart';
import 'package:PiliPlus/utils/extension/box_ext.dart';
import 'package:PiliPlus/utils/extension/num_ext.dart';
import 'package:PiliPlus/utils/extension/string_ext.dart';
@@ -50,8 +52,7 @@ import 'package:easy_debounce/easy_throttle.dart';
import 'package:floating/floating.dart';
import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart'
show rootBundle, HapticFeedback, Uint8List;
import 'package:flutter/services.dart' show HapticFeedback;
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:flutter_volume_controller/flutter_volume_controller.dart';
import 'package:get/get.dart';
@@ -156,8 +157,6 @@ class PlPlayerController with BlockConfigMixin {
///
final RxBool isSliderMoving = false.obs;
/// 是否循环
PlaylistMode _looping = PlaylistMode.none;
bool _autoPlay = false;
// 记录历史记录
@@ -177,7 +176,7 @@ class PlPlayerController with BlockConfigMixin {
late DataSource dataSource;
Timer? _timer;
Timer? _timerForSeek;
StreamSubscription<Duration>? _subForSeek;
Box setting = GStorage.setting;
@@ -255,10 +254,16 @@ class PlPlayerController with BlockConfigMixin {
windowManager.setTitleBarStyle(TitleBarStyle.hidden);
}
late final Size size;
final state = videoController!.player.state;
final width = state.width ?? this.width ?? 16;
final height = state.height ?? this.height ?? 9;
final Size size;
final state = videoPlayerController!.state;
int width = state.width;
int height = state.height;
if (width == 0) {
width = this.width ?? 16;
}
if (height == 0) {
height = this.height ?? 9;
}
if (height > width) {
size = Size(280.0, 280.0 * height / width);
} else {
@@ -299,13 +304,13 @@ class PlPlayerController with BlockConfigMixin {
}
void enterPip({bool isAuto = false}) {
if (videoController != null) {
if (videoPlayerController != null) {
controls = false;
final state = videoController!.player.state;
final state = videoPlayerController!.state;
PageUtils.enterPip(
isAuto: isAuto,
width: state.width ?? width,
height: state.height ?? height,
width: state.width == 0 ? width : state.width,
height: state.height == 0 ? height : state.height,
);
}
}
@@ -387,9 +392,7 @@ class PlPlayerController with BlockConfigMixin {
late int? cacheVideoQa = PlatformUtils.isMobile ? null : Pref.defaultVideoQa;
late int cacheAudioQa = Pref.defaultAudioQa;
bool enableHeart = true;
late final bool enableHA = Pref.enableHA;
late final String hwdec = Pref.hardwareDecoding;
late final String? hwdec = Pref.enableHA ? Pref.hardwareDecoding : null;
late final progressType = Pref.btmProgressBehavior;
late final enableQuickDouble = Pref.enableQuickDouble;
@@ -517,8 +520,8 @@ class PlPlayerController with BlockConfigMixin {
return _instance?.volume.value;
}
static Future<void> setVolumeIfExists(double volumeNew) async {
await _instance?.setVolume(volumeNew);
static Future<void>? setVolumeIfExists(double volumeNew) {
return _instance?.setVolume(volumeNew);
}
Box video = GStorage.video;
@@ -549,29 +552,22 @@ class PlPlayerController with BlockConfigMixin {
// 获取实例 传参
static PlPlayerController getInstance({bool isLive = false}) {
// 如果实例尚未创建,则创建一个新实例
_instance ??= PlPlayerController._();
_instance!
return (_instance ??= PlPlayerController._())
..isLive = isLive
.._playerCount += 1;
return _instance!;
}
bool _processing = false;
bool get processing => _processing;
// offline
bool isFileSource = false;
String? dirPath;
String? typeTag;
int? mediaType;
bool get isFileSource => dataSource is FileSource;
// 初始化资源
Future<void> setDataSource(
DataSource dataSource, {
bool isLive = false,
bool autoplay = true,
// 默认不循环
PlaylistMode looping = PlaylistMode.none,
// 初始化播放位置
Duration? seekTo,
// 初始化播放速度
@@ -591,15 +587,8 @@ class PlPlayerController with BlockConfigMixin {
VideoType? videoType,
VoidCallback? onInit,
Volume? volume,
String? dirPath,
String? typeTag,
int? mediaType,
}) async {
try {
this.dirPath = dirPath;
this.typeTag = typeTag;
this.mediaType = mediaType;
isFileSource = dataSource.type == DataSourceType.file;
_processing = true;
this.isLive = isLive;
_videoType = videoType ?? VideoType.ugc;
@@ -607,7 +596,6 @@ class PlPlayerController with BlockConfigMixin {
this.height = height;
this.dataSource = dataSource;
_autoPlay = autoplay;
_looping = looping;
// 初始化视频倍速
// _playbackSpeed.value = speed;
// 初始化数据加载状态
@@ -634,12 +622,15 @@ class PlPlayerController with BlockConfigMixin {
return;
}
// 配置Player 音轨、字幕等等
_videoPlayerController = await _createVideoController(
dataSource,
_looping,
seekTo,
volume,
);
await _createVideoController(dataSource, seekTo, volume);
if (_playerCount == 0) {
_videoPlayerController?.dispose();
_videoPlayerController = null;
_videoController = null;
return;
}
// 获取视频时长 00:00
this.duration.value = duration ?? _videoPlayerController!.state.duration;
position = buffered.value = sliderPosition = seekTo ?? Duration.zero;
@@ -670,32 +661,11 @@ class PlPlayerController with BlockConfigMixin {
return shadersDirPath!;
}
final dir = Directory(path.join(appSupportDirPath, 'anime_shaders'));
if (!dir.existsSync()) {
await dir.create(recursive: true);
}
final shaderFilesPath =
(Constants.mpvAnime4KShaders + Constants.mpvAnime4KShadersLite)
.map((e) => 'assets/shaders/$e')
.toList();
for (final filePath in shaderFilesPath) {
final fileName = filePath.split('/').last;
final targetFile = File(path.join(dir.path, fileName));
if (targetFile.existsSync()) {
continue;
}
try {
final data = await rootBundle.load(filePath);
final List<int> bytes = data.buffer.asUint8List();
await targetFile.writeAsBytes(bytes);
} catch (e) {
if (kDebugMode) debugPrint('$e');
}
}
return shadersDirPath = dir.path;
return shadersDirPath = await AssetUtils.getOrCopy(
'assets/shaders',
Constants.mpvAnime4KShaders.followedBy(Constants.mpvAnime4KShadersLite),
path.join(appSupportDirPath, 'anime_shaders'),
);
}
late final isAnim = _pgcType == 1 || _pgcType == 4;
@@ -710,12 +680,10 @@ class PlPlayerController with BlockConfigMixin {
setting.put(SettingBoxKey.superResolutionType, type.index);
}
}
pp ??= _videoPlayerController!.platform!;
await pp.waitForPlayerInitialization;
await pp.waitForVideoControllerInitializationIfAttached;
pp ??= _videoPlayerController!;
switch (type) {
case SuperResolutionType.disable:
return pp.command(['change-list', 'glsl-shaders', 'clr', '']);
return pp.command(const ['change-list', 'glsl-shaders', 'clr', '']);
case SuperResolutionType.efficiency:
return pp.command([
'change-list',
@@ -741,10 +709,54 @@ class PlPlayerController with BlockConfigMixin {
static final loudnormRegExp = RegExp('loudnorm=([^,]+)');
Future<Player> _initPlayer() async {
assert(_videoPlayerController == null);
final opt = {
'video-sync': Pref.videoSync,
};
if (Platform.isAndroid) {
opt['volume-max'] = '100';
opt['ao'] = Pref.audioOutput;
} else if (PlatformUtils.isDesktop) {
opt['volume'] = (volume.value * 100).toString();
}
final autosync = Pref.autosync;
if (autosync != '0') {
opt['autosync'] = autosync;
}
final player = await Player.create(
configuration: PlayerConfiguration(
bufferSize: Pref.expandBuffer
? (isLive ? 64 * 1024 * 1024 : 32 * 1024 * 1024)
: (isLive ? 16 * 1024 * 1024 : 4 * 1024 * 1024),
logLevel: kDebugMode ? .warn : .error,
options: opt,
),
);
assert(_videoController == null);
_videoController = await VideoController.create(
player,
configuration: VideoControllerConfiguration(
enableHardwareAcceleration: hwdec != null,
androidAttachSurfaceAfterVideoParameters: false,
hwdec: hwdec,
),
);
player.setMediaHeader(
userAgent: BrowserUa.pc,
referer: HttpString.baseUrl,
);
// await player.setAudioTrack(.auto());
return player;
}
// 配置播放器
Future<Player> _createVideoController(
Future<void> _createVideoController(
DataSource dataSource,
PlaylistMode looping,
Duration? seekTo,
Volume? volume,
) async {
@@ -757,81 +769,34 @@ class PlPlayerController with BlockConfigMixin {
// 初始化时清空弹幕,防止上次重叠
danmakuController?.clear();
Player player =
_videoPlayerController ??
Player(
configuration: PlayerConfiguration(
// 默认缓冲 4M 大小
bufferSize: Pref.expandBuffer
? (isLive ? 64 * 1024 * 1024 : 32 * 1024 * 1024)
: (isLive ? 16 * 1024 * 1024 : 4 * 1024 * 1024),
logLevel: kDebugMode ? MPVLogLevel.warn : MPVLogLevel.error,
),
);
final pp = player.platform!;
if (_videoPlayerController == null) {
if (PlatformUtils.isDesktop) {
pp.setVolume(this.volume.value * 100);
var player = _videoPlayerController;
if (player == null) {
player = await _initPlayer();
if (_playerCount == 0) {
player.dispose();
player = null;
_videoController = null;
return;
}
if (isAnim) {
setShader(superResolutionType.value, pp);
}
// await pp.setProperty('audio-pitch-correction', 'yes'); // default yes
if (Platform.isAndroid) {
await pp.setProperty("volume-max", "100");
await pp.setProperty("ao", Pref.audioOutput);
}
// video-sync=display-resample
await pp.setProperty("video-sync", Pref.videoSync);
final autosync = Pref.autosync;
if (autosync != '0') {
await pp.setProperty("autosync", autosync);
}
// vo=gpu-next & gpu-context=android & gpu-api=opengl
// await pp.setProperty("vo", "gpu-next");
// await pp.setProperty("gpu-context", "android");
// await pp.setProperty("gpu-api", "opengl");
await player.setAudioTrack(AudioTrack.auto());
if (Pref.enableSystemProxy) {
final systemProxyHost = Pref.systemProxyHost;
final systemProxyPort = int.tryParse(Pref.systemProxyPort);
if (systemProxyPort != null && systemProxyHost.isNotEmpty) {
await pp.setProperty(
"http-proxy",
'http://$systemProxyHost:$systemProxyPort',
);
}
_videoPlayerController = player;
}
if (isAnim) await setShader();
final Map<String, String> extras = {};
String video = dataSource.videoSource;
if (dataSource.audioSource case final audio? when (audio.isNotEmpty)) {
if (onlyPlayAudio.value) {
video = audio;
} else {
extras['audio-files'] =
'"${Platform.isWindows ? audio.replaceAll(';', r'\;') : audio.replaceAll(':', r'\:')}"';
}
}
// 音轨
final String audioUri;
if (isFileSource) {
audioUri = onlyPlayAudio.value || mediaType == 1
? ''
: path.join(dirPath!, typeTag!, PathUtils.audioNameType2);
} else if (dataSource.audioSource?.isNotEmpty == true) {
audioUri = Platform.isWindows
? dataSource.audioSource!.replaceAll(';', r'\;')
: dataSource.audioSource!.replaceAll(':', r'\:');
} else {
audioUri = '';
}
await pp.setProperty('audio-files', audioUri);
_videoController ??= VideoController(
player,
configuration: VideoControllerConfiguration(
enableHardwareAcceleration: enableHA,
androidAttachSurfaceAfterVideoParameters: false,
hwdec: enableHA ? hwdec : null,
),
);
player.setPlaylistMode(looping);
final Map<String, String>? filters;
if (Platform.isAndroid) {
if (kDebugMode || Platform.isAndroid) {
String audioNormalization = AudioNormalization.getParamFromConfig(
Pref.audioNormalization,
);
@@ -854,44 +819,23 @@ class PlPlayerController with BlockConfigMixin {
AudioNormalization.getParamFromConfig(Pref.fallbackNormalization),
);
}
filters = audioNormalization.isEmpty
? null
: {'lavfi-complex': '"[aid1] $audioNormalization [ao]"'};
} else {
filters = null;
if (audioNormalization.isNotEmpty) {
extras['lavfi-complex'] = '"[aid1] $audioNormalization [ao]"';
}
}
// if (kDebugMode) debugPrint(filters.toString());
late final String videoUri;
if (isFileSource) {
videoUri = path.join(
dirPath!,
typeTag!,
mediaType == 1
? PathUtils.videoNameType1
: onlyPlayAudio.value
? PathUtils.audioNameType2
: PathUtils.videoNameType2,
);
} else {
videoUri = dataSource.videoSource!;
}
await player.open(
Media(
videoUri,
httpHeaders: dataSource.httpHeaders,
video,
start: seekTo,
extras: filters,
extras: extras.isEmpty ? null : extras,
),
play: false,
);
return player;
}
Future<bool> refreshPlayer() async {
if (isFileSource) {
if (dataSource is FileSource) {
return true;
}
if (_videoPlayerController == null) {
@@ -902,23 +846,21 @@ class PlPlayerController with BlockConfigMixin {
SmartDialog.showToast('视频源为空,请重新进入本页面');
return false;
}
String? audioUri;
if (!isLive) {
if (dataSource.audioSource.isNullOrEmpty) {
SmartDialog.showToast('音频源为空');
} else {
await (_videoPlayerController!.platform!).setProperty(
'audio-files',
Platform.isWindows
? dataSource.audioSource!.replaceAll(';', '\\;')
: dataSource.audioSource!.replaceAll(':', '\\:'),
);
audioUri = Platform.isWindows
? dataSource.audioSource!.replaceAll(';', '\\;')
: dataSource.audioSource!.replaceAll(':', '\\:');
}
}
await _videoPlayerController!.open(
Media(
dataSource.videoSource!,
httpHeaders: dataSource.httpHeaders,
dataSource.videoSource,
start: position,
extras: audioUri == null ? null : {'audio-files': '"$audioUri"'},
),
play: true,
);
@@ -974,14 +916,14 @@ class PlPlayerController with BlockConfigMixin {
return null;
}
Set<StreamSubscription> subscriptions = {};
List<StreamSubscription> subscriptions = [];
final Set<Function(Duration position)> _positionListeners = {};
final Set<Function(PlayerStatus status)> _statusListeners = {};
/// 播放事件监听
void startListeners() {
final controllerStream = videoPlayerController!.stream;
subscriptions = {
subscriptions = [
controllerStream.playing.listen((event) {
WakelockPlus.toggle(enable: event);
if (event) {
@@ -1062,7 +1004,8 @@ class PlPlayerController with BlockConfigMixin {
}
})),
controllerStream.error.listen((String event) {
if (isFileSource && event.startsWith("Failed to open file")) {
if (dataSource is FileSource &&
event.startsWith("Failed to open file")) {
return;
}
if (isLive) {
@@ -1131,7 +1074,7 @@ class PlPlayerController with BlockConfigMixin {
videoPlayerServiceHandler!.onPositionChange(Duration(seconds: event));
}),
],
};
];
}
/// 移除事件监听
@@ -1139,6 +1082,13 @@ class PlPlayerController with BlockConfigMixin {
return Future.wait(subscriptions.map((e) => e.cancel()));
}
void _cancelSubForSeek() {
if (_subForSeek != null) {
_subForSeek!.cancel();
_subForSeek = null;
}
}
/// 跳转至指定位置
Future<void> seekTo(Duration position, {bool isSeek = true}) async {
// if (position >= duration.value) {
@@ -1153,7 +1103,8 @@ class PlPlayerController with BlockConfigMixin {
this.position = position;
updatePositionSecond();
_heartDuration = position.inSeconds;
if (duration.value.inSeconds != 0) {
Future<void> seek() async {
if (isSeek) {
/// 拖动进度条调节时,不等待第一帧,防止抖动
await _videoPlayerController?.stream.buffer.first;
@@ -1164,33 +1115,16 @@ class PlPlayerController with BlockConfigMixin {
} catch (e) {
if (kDebugMode) debugPrint('seek failed: $e');
}
// if (playerStatus.stopped) {
// play();
// }
}
if (duration.value != Duration.zero) {
seek();
} else {
// if (kDebugMode) debugPrint('seek duration else');
_timerForSeek?.cancel();
_timerForSeek = Timer.periodic(const Duration(milliseconds: 200), (
Timer t,
) async {
//_timerForSeek = null;
if (_playerCount == 0) {
_timerForSeek?.cancel();
_timerForSeek = null;
} else if (duration.value != Duration.zero) {
try {
await _videoPlayerController?.stream.buffer.first;
danmakuController?.clear();
await _videoPlayerController?.seek(position);
} catch (e) {
if (kDebugMode) debugPrint('seek failed: $e');
}
// if (playerStatus.isPaused) {
// play();
// }
t.cancel();
_timerForSeek = null;
}
_subForSeek?.cancel();
_subForSeek = duration.listen((_) {
seek();
_cancelSubForSeek();
});
}
}
@@ -1304,7 +1238,7 @@ class PlPlayerController with BlockConfigMixin {
final RxBool volumeIndicator = false.obs;
Timer? volumeTimer;
final RxBool volumeInterceptEventStream = false.obs;
bool volumeInterceptEventStream = false;
static final double maxVolume = PlatformUtils.isDesktop ? 2.0 : 1.0;
Future<void> setVolume(double volume) async {
@@ -1322,11 +1256,11 @@ class PlPlayerController with BlockConfigMixin {
}
}
volumeIndicator.value = true;
volumeInterceptEventStream.value = true;
volumeInterceptEventStream = true;
volumeTimer?.cancel();
volumeTimer = Timer(const Duration(milliseconds: 200), () {
volumeIndicator.value = false;
volumeInterceptEventStream.value = false;
volumeInterceptEventStream = false;
if (PlatformUtils.isDesktop) {
setting.put(SettingBoxKey.desktopVolume, volume.toPrecision(3));
}
@@ -1341,7 +1275,7 @@ class PlPlayerController with BlockConfigMixin {
/// 读取fit
int fitValue = Pref.cacheVideoFit;
Future<void> getVideoFit() async {
void getVideoFit() {
var attr = VideoFitType.values[fitValue];
// 由于none与scaleDown涉及视频原始尺寸需要等待视频加载后再设置否则尺寸会变为0出现错误;
if (attr == VideoFitType.none || attr == VideoFitType.scaleDown) {
@@ -1570,14 +1504,6 @@ class PlPlayerController with BlockConfigMixin {
void removeStatusLister(Function(PlayerStatus status) listener) =>
_statusListeners.remove(listener);
/// 截屏
Future<Uint8List?> screenshot() async {
final Uint8List? screenshot = await _videoPlayerController!.screenshot(
format: 'image/png',
);
return screenshot;
}
// 记录播放记录
Future<void>? makeHeartBeat(
int progress, {
@@ -1657,9 +1583,10 @@ class PlPlayerController with BlockConfigMixin {
}
bool isCloseAll = false;
Future<void> dispose() async {
void dispose() {
// 每次减1最后销毁
cancelLongPressTimer();
_cancelSubForSeek();
if (!isCloseAll && _playerCount > 1) {
_playerCount -= 1;
_heartDuration = 0;
@@ -1681,7 +1608,6 @@ class PlPlayerController with BlockConfigMixin {
}
Utils.channel.setMethodCallHandler(null);
_timer?.cancel();
_timerForSeek?.cancel();
// _position.close();
// _playerEventSubs?.cancel();
// _sliderPosition.close();
@@ -1699,7 +1625,7 @@ class PlPlayerController with BlockConfigMixin {
windowManager.setAlwaysOnTop(false);
}
await removeListeners();
removeListeners();
subscriptions.clear();
_positionListeners.clear();
_statusListeners.clear();
@@ -1781,7 +1707,7 @@ class PlPlayerController with BlockConfigMixin {
},
options: Options(
headers: {
'user-agent': UaType.pc.ua,
'user-agent': BrowserUa.pc,
'referer': 'https://www.bilibili.com/video/$bvid',
},
),
@@ -1802,7 +1728,7 @@ class PlPlayerController with BlockConfigMixin {
void takeScreenshot() {
SmartDialog.showToast('截图中');
videoPlayerController?.screenshot(format: 'image/png').then((value) {
videoPlayerController?.screenshot(format: .png).then((value) {
if (value != null) {
SmartDialog.showToast('点击弹窗保存截图');
showDialog(

View File

@@ -1,39 +1,39 @@
/// The way in which the video was originally loaded.
///
/// This has nothing to do with the video's file type. It's just the place
/// from which the video is fetched from.
enum DataSourceType {
/// The video was downloaded from the internet.
network,
import 'package:PiliPlus/utils/path_utils.dart';
import 'package:path/path.dart' as path;
/// The video was loaded off of the local filesystem.
file,
}
class DataSource {
String? videoSource;
String? audioSource;
DataSourceType type;
Map<String, String>? httpHeaders; // for headers
sealed class DataSource {
final String videoSource;
final String? audioSource;
DataSource({
this.videoSource,
this.audioSource,
required this.type,
this.httpHeaders,
required this.videoSource,
required this.audioSource,
});
DataSource copyWith({
String? videoSource,
String? audioSource,
DataSourceType? type,
Map<String, String>? httpHeaders,
}) {
return DataSource(
videoSource: videoSource ?? this.videoSource,
audioSource: audioSource ?? this.audioSource,
type: type ?? this.type,
httpHeaders: httpHeaders ?? this.httpHeaders,
);
}
}
class NetworkSource extends DataSource {
NetworkSource({
required super.videoSource,
required super.audioSource,
});
}
class FileSource extends DataSource {
final String dir;
final bool isMp4;
FileSource({
required this.dir,
required this.isMp4,
required String typeTag,
}) : super(
videoSource: path.join(
dir,
typeTag,
isMp4 ? PathUtils.videoNameType1 : PathUtils.videoNameType2,
),
audioSource: isMp4
? null
: path.join(dir, typeTag, PathUtils.audioNameType2),
);
}

View File

@@ -80,6 +80,8 @@ import 'package:media_kit_video/media_kit_video.dart';
import 'package:screen_brightness_platform_interface/screen_brightness_platform_interface.dart';
import 'package:window_manager/window_manager.dart';
part 'widgets.dart';
class PLVideoPlayer extends StatefulWidget {
const PLVideoPlayer({
required this.maxWidth,
@@ -226,8 +228,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
plPlayerController.volume.value =
(await FlutterVolumeController.getVolume())!;
FlutterVolumeController.addListener((double value) {
if (mounted &&
!plPlayerController.volumeInterceptEventStream.value) {
if (mounted && !plPlayerController.volumeInterceptEventStream) {
plPlayerController.volume.value = value;
if (Platform.isIOS && !FlutterVolumeController.showSystemUI) {
plPlayerController
@@ -240,7 +241,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
});
}
}
});
}, emitOnStart: false);
} catch (_) {}
});
@@ -293,7 +294,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (!plPlayerController.continuePlayInBackground.value) {
late final player = plPlayerController.videoController?.player;
late final player = plPlayerController.videoPlayerController;
if (const [
AppLifecycleState.paused,
AppLifecycleState.detached,
@@ -2436,505 +2437,3 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
);
}
}
Widget buildDmChart(
Color color,
List<double> dmTrend,
VideoDetailController videoDetailController, [
double offset = 0,
]) {
return IgnorePointer(
child: Container(
height: 12,
margin: EdgeInsets.only(
bottom:
videoDetailController.viewPointList.isNotEmpty &&
videoDetailController.showVP.value
? 19.25 + offset
: 4.25 + offset,
),
child: LineChart(
LineChartData(
titlesData: const FlTitlesData(show: false),
lineTouchData: const LineTouchData(enabled: false),
gridData: const FlGridData(show: false),
borderData: FlBorderData(show: false),
minX: 0,
maxX: (dmTrend.length - 1).toDouble(),
minY: 0,
maxY: dmTrend.max,
lineBarsData: [
LineChartBarData(
spots: List.generate(
dmTrend.length,
(index) => FlSpot(
index.toDouble(),
dmTrend[index],
),
),
isCurved: true,
barWidth: 1,
color: color,
dotData: const FlDotData(show: false),
belowBarData: BarAreaData(
show: true,
color: color.withValues(alpha: 0.4),
),
),
],
),
),
),
);
}
Widget buildSeekPreviewWidget(
PlPlayerController plPlayerController,
double maxWidth,
double maxHeight,
ValueGetter<bool> isMounted,
) {
return Obx(
() {
if (!plPlayerController.showPreview.value) {
return const SizedBox.shrink();
}
try {
final data = plPlayerController.videoShot!.data;
final double scale =
plPlayerController.isFullScreen.value &&
(PlatformUtils.isDesktop || !plPlayerController.isVertical)
? 4
: 3;
double height = 27 * scale;
final compatHeight = maxHeight - 140;
if (compatHeight > 50) {
height = math.min(height, compatHeight);
}
final int imgXLen = data.imgXLen;
final int imgYLen = data.imgYLen;
final int totalPerImage = data.totalPerImage;
double imgXSize = data.imgXSize;
double imgYSize = data.imgYSize;
return Align(
alignment: Alignment.center,
child: Obx(
() {
final index = plPlayerController.previewIndex.value!;
int pageIndex = (index ~/ totalPerImage).clamp(
0,
data.image.length - 1,
);
int align = index % totalPerImage;
int x = align % imgXLen;
int y = align ~/ imgYLen;
final url = data.image[pageIndex];
return ClipRRect(
borderRadius: StyleString.mdRadius,
child: VideoShotImage(
url: url,
x: x,
y: y,
imgXSize: imgXSize,
imgYSize: imgYSize,
height: height,
imageCache: plPlayerController.previewCache,
onSetSize: (xSize, ySize) => data
..imgXSize = imgXSize = xSize
..imgYSize = imgYSize = ySize,
isMounted: isMounted,
),
);
},
),
);
} catch (e) {
if (kDebugMode) rethrow;
return const SizedBox.shrink();
}
},
);
}
class VideoShotImage extends StatefulWidget {
const VideoShotImage({
super.key,
required this.imageCache,
required this.url,
required this.x,
required this.y,
required this.imgXSize,
required this.imgYSize,
required this.height,
required this.onSetSize,
required this.isMounted,
});
final Map<String, ui.Image?> imageCache;
final String url;
final int x;
final int y;
final double imgXSize;
final double imgYSize;
final double height;
final Function(double imgXSize, double imgYSize) onSetSize;
final ValueGetter<bool> isMounted;
@override
State<VideoShotImage> createState() => _VideoShotImageState();
}
Future<ui.Image?> _getImg(String url) async {
final cacheManager = DefaultCacheManager();
final cacheKey = Utils.getFileName(url, fileExt: false);
try {
final fileInfo = await cacheManager.getSingleFile(
ImageUtils.safeThumbnailUrl(url),
key: cacheKey,
headers: Constants.baseHeaders,
);
return _loadImg(fileInfo.path);
} catch (_) {
return null;
}
}
Future<ui.Image?> _loadImg(String path) async {
final codec = await ui.instantiateImageCodecFromBuffer(
await ImmutableBuffer.fromFilePath(path),
);
final frame = await codec.getNextFrame();
codec.dispose();
return frame.image;
}
class _VideoShotImageState extends State<VideoShotImage> {
late Size _size;
late Rect _srcRect;
late Rect _dstRect;
late RRect _rrect;
ui.Image? _image;
@override
void initState() {
super.initState();
_initSize();
_loadImg();
}
void _initSizeIfNeeded() {
if (_size.width.isNaN) {
_initSize();
}
}
void _initSize() {
if (widget.imgXSize == 0) {
if (_image != null) {
final imgXSize = _image!.width / 10;
final imgYSize = _image!.height / 10;
final height = widget.height;
final width = height * imgXSize / imgYSize;
_setRect(width, height);
_setSrcRect(imgXSize, imgYSize);
widget.onSetSize(imgXSize, imgYSize);
} else {
_setRect(double.nan, double.nan);
_setSrcRect(widget.imgXSize, widget.imgYSize);
}
} else {
final height = widget.height;
final width = height * widget.imgXSize / widget.imgYSize;
_setRect(width, height);
_setSrcRect(widget.imgXSize, widget.imgYSize);
}
}
void _setRect(double width, double height) {
_size = Size(width, height);
_dstRect = Rect.fromLTRB(0, 0, width, height);
_rrect = RRect.fromRectAndRadius(_dstRect, const Radius.circular(10));
}
void _setSrcRect(double imgXSize, double imgYSize) {
_srcRect = Rect.fromLTWH(
widget.x * imgXSize,
widget.y * imgYSize,
imgXSize,
imgYSize,
);
}
void _loadImg() {
final url = widget.url;
_image = widget.imageCache[url];
if (_image != null) {
_initSizeIfNeeded();
} else if (!widget.imageCache.containsKey(url)) {
widget.imageCache[url] = null;
_getImg(url).then((image) {
if (image != null) {
if (widget.isMounted()) {
widget.imageCache[url] = image;
}
if (mounted) {
_image = image;
_initSizeIfNeeded();
setState(() {});
}
} else {
widget.imageCache.remove(url);
}
});
}
}
@override
void didUpdateWidget(VideoShotImage oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.url != widget.url) {
_loadImg();
}
if (oldWidget.x != widget.x || oldWidget.y != widget.y) {
_setSrcRect(widget.imgXSize, widget.imgYSize);
}
}
late final _imgPaint = Paint()..filterQuality = FilterQuality.medium;
late final _borderPaint = Paint()
..color = Colors.white
..style = PaintingStyle.stroke
..strokeWidth = 1.5;
@override
Widget build(BuildContext context) {
if (_image != null) {
return CroppedImage(
size: _size,
image: _image!,
srcRect: _srcRect,
dstRect: _dstRect,
rrect: _rrect,
imgPaint: _imgPaint,
borderPaint: _borderPaint,
);
}
return const SizedBox.shrink();
}
}
const double _triangleHeight = 5.6;
class _DanmakuTip extends SingleChildRenderObjectWidget {
const _DanmakuTip({
this.offset = 0,
super.child,
});
final double offset;
@override
RenderObject createRenderObject(BuildContext context) {
return _RenderDanmakuTip(offset: offset);
}
@override
void updateRenderObject(
BuildContext context,
_RenderDanmakuTip renderObject,
) {
renderObject.offset = offset;
}
}
class _RenderDanmakuTip extends RenderProxyBox {
_RenderDanmakuTip({
required double offset,
}) : _offset = offset;
double _offset;
double get offset => _offset;
set offset(double value) {
if (_offset == value) return;
_offset = value;
markNeedsPaint();
}
@override
void paint(PaintingContext context, Offset offset) {
final paint = Paint()
..color = const Color(0xB3000000)
..style = .fill;
final radius = size.height / 2;
const triangleBase = _triangleHeight * 2 / 3;
final triangleCenterX = (size.width / 2 + _offset).clamp(
radius + triangleBase,
size.width - radius - triangleBase,
);
final path = Path()
// triangle (exceed)
..moveTo(triangleCenterX - triangleBase, 0)
..lineTo(triangleCenterX, -_triangleHeight)
..lineTo(triangleCenterX + triangleBase, 0)
// top
..lineTo(size.width - radius, 0)
// right
..arcToPoint(
Offset(size.width - radius, size.height),
radius: Radius.circular(radius),
)
// bottom
..lineTo(radius, size.height)
// left
..arcToPoint(
Offset(radius, 0),
radius: Radius.circular(radius),
)
..close();
context.canvas
..save()
..translate(offset.dx, offset.dy)
..drawPath(path, paint)
..drawPath(
path,
paint
..color = const Color(0x7EFFFFFF)
..style = PaintingStyle.stroke
..strokeWidth = 1.25,
)
..restore();
super.paint(context, offset);
}
}
class _VideoTime extends LeafRenderObjectWidget {
const _VideoTime({
required this.position,
required this.duration,
});
final String position;
final String duration;
@override
_RenderVideoTime createRenderObject(BuildContext context) => _RenderVideoTime(
position: position,
duration: duration,
);
@override
void updateRenderObject(
BuildContext context,
covariant _RenderVideoTime renderObject,
) {
renderObject
..position = position
..duration = duration;
}
}
class _RenderVideoTime extends RenderBox {
_RenderVideoTime({
required String position,
required String duration,
}) : _position = position,
_duration = duration;
String _duration;
set duration(String value) {
_duration = value;
final paragraph = _buildParagraph(const Color(0xFFD0D0D0), _duration);
if (paragraph.maxIntrinsicWidth != _cache?.maxIntrinsicWidth) {
markNeedsLayout();
}
_cache?.dispose();
_cache = paragraph;
markNeedsSemanticsUpdate();
}
String _position;
set position(String value) {
_position = value;
markNeedsPaint();
markNeedsSemanticsUpdate();
}
ui.Paragraph? _cache;
ui.Paragraph _buildParagraph(Color color, String time) {
final builder =
ui.ParagraphBuilder(
ui.ParagraphStyle(
fontSize: 10,
height: 1.4,
fontFamily: 'Monospace',
),
)
..pushStyle(
ui.TextStyle(
color: color,
fontSize: 10,
height: 1.4,
fontFamily: 'Monospace',
fontFeatures: const [FontFeature.tabularFigures()],
),
)
..addText(time);
return builder.build()
..layout(const ui.ParagraphConstraints(width: .infinity));
}
@override
ui.Size computeDryLayout(covariant BoxConstraints constraints) {
final paragraph = _cache ??= _buildParagraph(
const Color(0xFFD0D0D0),
_duration,
);
return Size(paragraph.maxIntrinsicWidth, paragraph.height * 2);
}
@override
void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
config.label = 'position:$_position\nduration:$_duration';
}
@override
void performLayout() {
size = computeDryLayout(constraints);
}
@override
void paint(PaintingContext context, ui.Offset offset) {
final para = _buildParagraph(Colors.white, _position);
context.canvas
..drawParagraph(
para,
Offset(
offset.dx + _cache!.maxIntrinsicWidth - para.maxIntrinsicWidth,
offset.dy,
),
)
..drawParagraph(_cache!, Offset(offset.dx, offset.dy + para.height));
para.dispose();
}
@override
void dispose() {
_cache?.dispose();
_cache = null;
super.dispose();
}
@override
bool get isRepaintBoundary => true;
}

View File

@@ -0,0 +1,503 @@
part of 'view.dart';
Widget buildDmChart(
Color color,
List<double> dmTrend,
VideoDetailController videoDetailController, [
double offset = 0,
]) {
return IgnorePointer(
child: Container(
height: 12,
margin: EdgeInsets.only(
bottom:
videoDetailController.viewPointList.isNotEmpty &&
videoDetailController.showVP.value
? 19.25 + offset
: 4.25 + offset,
),
child: LineChart(
LineChartData(
titlesData: const FlTitlesData(show: false),
lineTouchData: const LineTouchData(enabled: false),
gridData: const FlGridData(show: false),
borderData: FlBorderData(show: false),
minX: 0,
maxX: (dmTrend.length - 1).toDouble(),
minY: 0,
maxY: dmTrend.max,
lineBarsData: [
LineChartBarData(
spots: List.generate(
dmTrend.length,
(index) => FlSpot(
index.toDouble(),
dmTrend[index],
),
),
isCurved: true,
barWidth: 1,
color: color,
dotData: const FlDotData(show: false),
belowBarData: BarAreaData(
show: true,
color: color.withValues(alpha: 0.4),
),
),
],
),
),
),
);
}
Widget buildSeekPreviewWidget(
PlPlayerController plPlayerController,
double maxWidth,
double maxHeight,
ValueGetter<bool> isMounted,
) {
return Obx(
() {
if (!plPlayerController.showPreview.value) {
return const SizedBox.shrink();
}
try {
final data = plPlayerController.videoShot!.data;
final double scale =
plPlayerController.isFullScreen.value &&
(PlatformUtils.isDesktop || !plPlayerController.isVertical)
? 4
: 3;
double height = 27 * scale;
final compatHeight = maxHeight - 140;
if (compatHeight > 50) {
height = math.min(height, compatHeight);
}
final int imgXLen = data.imgXLen;
final int imgYLen = data.imgYLen;
final int totalPerImage = data.totalPerImage;
double imgXSize = data.imgXSize;
double imgYSize = data.imgYSize;
return Align(
alignment: Alignment.center,
child: Obx(
() {
final index = plPlayerController.previewIndex.value!;
int pageIndex = (index ~/ totalPerImage).clamp(
0,
data.image.length - 1,
);
int align = index % totalPerImage;
int x = align % imgXLen;
int y = align ~/ imgYLen;
final url = data.image[pageIndex];
return ClipRRect(
borderRadius: StyleString.mdRadius,
child: VideoShotImage(
url: url,
x: x,
y: y,
imgXSize: imgXSize,
imgYSize: imgYSize,
height: height,
imageCache: plPlayerController.previewCache,
onSetSize: (xSize, ySize) => data
..imgXSize = imgXSize = xSize
..imgYSize = imgYSize = ySize,
isMounted: isMounted,
),
);
},
),
);
} catch (e) {
if (kDebugMode) rethrow;
return const SizedBox.shrink();
}
},
);
}
class VideoShotImage extends StatefulWidget {
const VideoShotImage({
super.key,
required this.imageCache,
required this.url,
required this.x,
required this.y,
required this.imgXSize,
required this.imgYSize,
required this.height,
required this.onSetSize,
required this.isMounted,
});
final Map<String, ui.Image?> imageCache;
final String url;
final int x;
final int y;
final double imgXSize;
final double imgYSize;
final double height;
final Function(double imgXSize, double imgYSize) onSetSize;
final ValueGetter<bool> isMounted;
@override
State<VideoShotImage> createState() => _VideoShotImageState();
}
Future<ui.Image?> _getImg(String url) async {
final cacheManager = DefaultCacheManager();
final cacheKey = Utils.getFileName(url, fileExt: false);
try {
final fileInfo = await cacheManager.getSingleFile(
ImageUtils.safeThumbnailUrl(url),
key: cacheKey,
headers: Constants.baseHeaders,
);
return _loadImg(fileInfo.path);
} catch (_) {
return null;
}
}
Future<ui.Image?> _loadImg(String path) async {
final codec = await ui.instantiateImageCodecFromBuffer(
await ImmutableBuffer.fromFilePath(path),
);
final frame = await codec.getNextFrame();
codec.dispose();
return frame.image;
}
class _VideoShotImageState extends State<VideoShotImage> {
late Size _size;
late Rect _srcRect;
late Rect _dstRect;
late RRect _rrect;
ui.Image? _image;
@override
void initState() {
super.initState();
_initSize();
_loadImg();
}
void _initSizeIfNeeded() {
if (_size.width.isNaN) {
_initSize();
}
}
void _initSize() {
if (widget.imgXSize == 0) {
if (_image != null) {
final imgXSize = _image!.width / 10;
final imgYSize = _image!.height / 10;
final height = widget.height;
final width = height * imgXSize / imgYSize;
_setRect(width, height);
_setSrcRect(imgXSize, imgYSize);
widget.onSetSize(imgXSize, imgYSize);
} else {
_setRect(double.nan, double.nan);
_setSrcRect(widget.imgXSize, widget.imgYSize);
}
} else {
final height = widget.height;
final width = height * widget.imgXSize / widget.imgYSize;
_setRect(width, height);
_setSrcRect(widget.imgXSize, widget.imgYSize);
}
}
void _setRect(double width, double height) {
_size = Size(width, height);
_dstRect = Rect.fromLTRB(0, 0, width, height);
_rrect = RRect.fromRectAndRadius(_dstRect, const Radius.circular(10));
}
void _setSrcRect(double imgXSize, double imgYSize) {
_srcRect = Rect.fromLTWH(
widget.x * imgXSize,
widget.y * imgYSize,
imgXSize,
imgYSize,
);
}
void _loadImg() {
final url = widget.url;
_image = widget.imageCache[url];
if (_image != null) {
_initSizeIfNeeded();
} else if (!widget.imageCache.containsKey(url)) {
widget.imageCache[url] = null;
_getImg(url).then((image) {
if (image != null) {
if (widget.isMounted()) {
widget.imageCache[url] = image;
}
if (mounted) {
_image = image;
_initSizeIfNeeded();
setState(() {});
}
} else {
widget.imageCache.remove(url);
}
});
}
}
@override
void didUpdateWidget(VideoShotImage oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.url != widget.url) {
_loadImg();
}
if (oldWidget.x != widget.x || oldWidget.y != widget.y) {
_setSrcRect(widget.imgXSize, widget.imgYSize);
}
}
late final _imgPaint = Paint()..filterQuality = FilterQuality.medium;
late final _borderPaint = Paint()
..color = Colors.white
..style = PaintingStyle.stroke
..strokeWidth = 1.5;
@override
Widget build(BuildContext context) {
if (_image != null) {
return CroppedImage(
size: _size,
image: _image!,
srcRect: _srcRect,
dstRect: _dstRect,
rrect: _rrect,
imgPaint: _imgPaint,
borderPaint: _borderPaint,
);
}
return const SizedBox.shrink();
}
}
const double _triangleHeight = 5.6;
class _DanmakuTip extends SingleChildRenderObjectWidget {
const _DanmakuTip({
this.offset = 0,
super.child,
});
final double offset;
@override
RenderObject createRenderObject(BuildContext context) {
return _RenderDanmakuTip(offset: offset);
}
@override
void updateRenderObject(
BuildContext context,
_RenderDanmakuTip renderObject,
) {
renderObject.offset = offset;
}
}
class _RenderDanmakuTip extends RenderProxyBox {
_RenderDanmakuTip({
required double offset,
}) : _offset = offset;
double _offset;
double get offset => _offset;
set offset(double value) {
if (_offset == value) return;
_offset = value;
markNeedsPaint();
}
@override
void paint(PaintingContext context, Offset offset) {
final paint = Paint()
..color = const Color(0xB3000000)
..style = .fill;
final radius = size.height / 2;
const triangleBase = _triangleHeight * 2 / 3;
final triangleCenterX = (size.width / 2 + _offset).clamp(
radius + triangleBase,
size.width - radius - triangleBase,
);
final path = Path()
// triangle (exceed)
..moveTo(triangleCenterX - triangleBase, 0)
..lineTo(triangleCenterX, -_triangleHeight)
..lineTo(triangleCenterX + triangleBase, 0)
// top
..lineTo(size.width - radius, 0)
// right
..arcToPoint(
Offset(size.width - radius, size.height),
radius: Radius.circular(radius),
)
// bottom
..lineTo(radius, size.height)
// left
..arcToPoint(
Offset(radius, 0),
radius: Radius.circular(radius),
)
..close();
context.canvas
..save()
..translate(offset.dx, offset.dy)
..drawPath(path, paint)
..drawPath(
path,
paint
..color = const Color(0x7EFFFFFF)
..style = PaintingStyle.stroke
..strokeWidth = 1.25,
)
..restore();
super.paint(context, offset);
}
}
class _VideoTime extends LeafRenderObjectWidget {
const _VideoTime({
required this.position,
required this.duration,
});
final String position;
final String duration;
@override
_RenderVideoTime createRenderObject(BuildContext context) => _RenderVideoTime(
position: position,
duration: duration,
);
@override
void updateRenderObject(
BuildContext context,
covariant _RenderVideoTime renderObject,
) {
renderObject
..position = position
..duration = duration;
}
}
class _RenderVideoTime extends RenderBox {
_RenderVideoTime({
required String position,
required String duration,
}) : _position = position,
_duration = duration;
String _duration;
set duration(String value) {
_duration = value;
final paragraph = _buildParagraph(const Color(0xFFD0D0D0), _duration);
if (paragraph.maxIntrinsicWidth != _cache?.maxIntrinsicWidth) {
markNeedsLayout();
}
_cache?.dispose();
_cache = paragraph;
markNeedsSemanticsUpdate();
}
String _position;
set position(String value) {
_position = value;
markNeedsPaint();
markNeedsSemanticsUpdate();
}
ui.Paragraph? _cache;
ui.Paragraph _buildParagraph(Color color, String time) {
final builder =
ui.ParagraphBuilder(
ui.ParagraphStyle(
fontSize: 10,
height: 1.4,
fontFamily: 'Monospace',
),
)
..pushStyle(
ui.TextStyle(
color: color,
fontSize: 10,
height: 1.4,
fontFamily: 'Monospace',
fontFeatures: const [FontFeature.tabularFigures()],
),
)
..addText(time);
return builder.build()
..layout(const ui.ParagraphConstraints(width: .infinity));
}
@override
ui.Size computeDryLayout(covariant BoxConstraints constraints) {
final paragraph = _cache ??= _buildParagraph(
const Color(0xFFD0D0D0),
_duration,
);
return Size(paragraph.maxIntrinsicWidth, paragraph.height * 2);
}
@override
void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
config.label = 'position:$_position\nduration:$_duration';
}
@override
void performLayout() {
size = computeDryLayout(constraints);
}
@override
void paint(PaintingContext context, ui.Offset offset) {
final para = _buildParagraph(Colors.white, _position);
context.canvas
..drawParagraph(
para,
Offset(
offset.dx + _cache!.maxIntrinsicWidth - para.maxIntrinsicWidth,
offset.dy,
),
)
..drawParagraph(_cache!, Offset(offset.dx, offset.dy + para.height));
para.dispose();
}
@override
void dispose() {
_cache?.dispose();
_cache = null;
super.dispose();
}
@override
bool get isRepaintBoundary => true;
}

View File

@@ -2,7 +2,7 @@ import 'package:PiliPlus/common/widgets/progress_bar/audio_video_progress_bar.da
import 'package:PiliPlus/common/widgets/progress_bar/segment_progress_bar.dart';
import 'package:PiliPlus/pages/video/controller.dart';
import 'package:PiliPlus/plugin/pl_player/controller.dart';
import 'package:PiliPlus/plugin/pl_player/view.dart';
import 'package:PiliPlus/plugin/pl_player/view/view.dart';
import 'package:PiliPlus/utils/extension/theme_ext.dart';
import 'package:PiliPlus/utils/feed_back.dart';
import 'package:PiliPlus/utils/platform_utils.dart';

View File

@@ -3,6 +3,7 @@
import 'dart:async';
import 'dart:ffi';
import 'package:PiliPlus/http/browser_ua.dart';
import 'package:PiliPlus/http/constants.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:flutter/foundation.dart' show kDebugMode;
@@ -13,10 +14,9 @@ import 'package:media_kit/ffi/src/utf8.dart';
import 'package:media_kit/generated/libmpv/bindings.dart' as generated;
import 'package:media_kit/media_kit.dart';
import 'package:media_kit/src/player/native/core/initializer.dart';
import 'package:media_kit/src/player/native/core/native_library.dart';
class MpvConvertWebp {
final _mpv = generated.MPV(DynamicLibrary.open(NativeLibrary.path));
final _mpv = NativePlayer.mpv;
late final Pointer<generated.mpv_handle> _ctx;
final _completer = Completer<bool>();
@@ -41,7 +41,7 @@ class MpvConvertWebp {
Future<void> _init() async {
final enableHA = Pref.enableHA;
_ctx = await Initializer.create(
NativeLibrary.path,
_mpv,
_onEvent,
options: {
'o': outFile,
@@ -58,13 +58,10 @@ class MpvConvertWebp {
},
);
NativePlayer.setHeader(
const {
'user-agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
'referer': HttpString.baseUrl,
},
_mpv,
_ctx,
userAgent: BrowserUa.pc,
referer: HttpString.baseUrl,
);
if (progress != null) {
_observeProperty('time-pos');
@@ -86,7 +83,7 @@ class MpvConvertWebp {
return _completer.future;
}
Future<void> _onEvent(Pointer<generated.mpv_event> event) async {
Future<void>? _onEvent(Pointer<generated.mpv_event> event) {
switch (event.ref.event_id) {
case generated.mpv_event_id.MPV_EVENT_PROPERTY_CHANGE:
final prop = event.ref.data.cast<generated.mpv_event_property>().ref;
@@ -117,11 +114,12 @@ class MpvConvertWebp {
dispose();
break;
}
return null;
}
void _command(List<String> args) {
final pointers = args.map((e) => e.toNativeUtf8()).toList();
final arr = calloc<Pointer<Uint8>>(128);
final arr = calloc<Pointer<Uint8>>(pointers.length + 1);
for (int i = 0; i < args.length; i++) {
arr[i] = pointers[i];
}