mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-04-20 03:06:59 +08:00
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:
committed by
GitHub
parent
6782bee11a
commit
7276cde48a
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user