feat: improve fvp player

This commit is contained in:
22
2025-09-13 20:18:53 +08:00
parent 0cd9fd74a6
commit c8fcb7fde8
2 changed files with 165 additions and 160 deletions

View File

@@ -56,127 +56,103 @@ FvpPlayer useFvpPlayer(BuildContext context) {
final List<Subtitle> externalSubtitles = useMemoized(
() => currentPlay?.file.subtitles ?? [], [currentPlay?.file.subtitles]);
final initValue = useState(false);
final isInitializing = useState(false);
Future<void> init() async => initValue.value = true;
MediaStream mediaStream = MediaStream();
final streamUrl = mediaStream.url;
final controllerFuture = useMemoized(() async {
if (file == null) return null;
isInitializing.value = true;
final storage = useStorageStore().findById(file.storageId);
final auth = storage?.getAuth();
logger('Open file: $file');
switch (checkDataSourceType(file)) {
case DataSourceType.file:
return VideoPlayerController.file(
File(file.uri),
httpHeaders: auth != null ? {'authorization': auth} : {},
);
case DataSourceType.contentUri:
final isExists = await SafUtil().exists(file.uri, false);
return VideoPlayerController.contentUri(
isExists ? Uri.parse(file.uri) : Uri.parse(''),
);
default:
return VideoPlayerController.networkUrl(
Uri.parse(file.storageType == StorageType.ftp
? '$streamUrl/${file.uri}'
: file.uri),
httpHeaders: auth != null ? {'authorization': auth} : {},
);
}
}, [file, initValue.value]);
final controller = useState(VideoPlayerController.networkUrl(Uri.parse('')));
final controller = useFuture(controllerFuture).data ??
VideoPlayerController.networkUrl(Uri.parse(''));
Future<void> init() async {
isInitializing.value = true;
try {
await controller.value.initialize();
await controller.value.setLooping(repeat == Repeat.one ? true : false);
await controller.value.setPlaybackSpeed(rate);
await controller.value.setVolume(isMuted ? 0 : volume / 100);
} catch (e) {
logger('Error initializing player: $e');
}
isInitializing.value = false;
}
useEffect(() {
() async {
if (controller.dataSource.isEmpty) return;
try {
await controller.initialize();
await controller.setLooping(repeat == Repeat.one ? true : false);
await controller.setPlaybackSpeed(rate);
await controller.setVolume(isMuted ? 0 : volume / 100);
} catch (e) {
logger('Error initializing player: $e');
if (controller.value.value.isInitialized) {
logger('Dispose player');
controller.value.dispose();
}
isInitializing.value = false;
if (file == null || file.uri.isEmpty) {
controller.value = VideoPlayerController.networkUrl(Uri.parse(''));
} else {
final storage = useStorageStore().findById(file.storageId);
final auth = storage?.getAuth();
logger('Open file: $file');
switch (checkDataSourceType(file)) {
case DataSourceType.file:
controller.value = VideoPlayerController.file(
File(file.uri),
httpHeaders: auth != null ? {'authorization': auth} : {},
);
case DataSourceType.contentUri:
final isExists = await SafUtil().exists(file.uri, false);
controller.value = VideoPlayerController.contentUri(
isExists ? Uri.parse(file.uri) : Uri.parse(''),
);
default:
controller.value = VideoPlayerController.networkUrl(
Uri.parse(file.storageType == StorageType.ftp
? '$streamUrl/${file.uri}'
: file.uri),
httpHeaders: auth != null ? {'authorization': auth} : {},
);
}
await init();
}
}();
return () {
if (controller.dataSource.isEmpty) return;
controller.dispose();
externalSubtitle.value = null;
};
}, [controller, initValue.value]);
return;
}, [file?.uri]);
useEffect(() {
return () {
if (controller.dataSource.isEmpty) return;
controller.dispose();
if (controller.value.value.isInitialized) {
controller.value.dispose();
}
};
}, []);
final isPlaying =
useListenableSelector(controller, () => controller.value.isPlaying);
final duration =
useListenableSelector(controller, () => controller.value.duration);
final position =
useListenableSelector(controller, () => controller.value.position);
final buffered =
useListenableSelector(controller, () => controller.value.buffered);
final size = useListenableSelector(controller, () => controller.value.size);
final isCompleted =
useListenableSelector(controller, () => controller.value.isCompleted);
final double aspect = useMemoized(() {
if (file?.type != ContentType.video) {
return 0;
}
final double aspect = useMemoized(
() => size.width != 0 && size.height != 0 ? size.width / size.height : 0,
[size.width, size.height]);
final width = controller.value.value.size.width;
final height = controller.value.value.size.height;
if (width != 0 && height != 0) {
return width / height;
} else {
return 0;
}
}, [
file?.type,
controller.value.value.size.width,
controller.value.value.size.height
]);
final seeking = useState(false);
useEffect(() {
() async {
if (duration != Duration.zero &&
currentPlay != null &&
currentPlay.file.type == ContentType.video) {
Progress? progress = history[currentPlay.file.getID()];
if (progress != null) {
if (!alwaysPlayFromBeginning &&
(progress.duration.inMilliseconds -
progress.position.inMilliseconds) >
5000) {
logger(
'Resume progress: ${currentPlay.file.name} position: ${progress.position} duration: ${progress.duration}');
await controller.seekTo(progress.position);
}
}
}
if (autoPlay) {
controller.play();
}
if (externalSubtitles.isNotEmpty) {
externalSubtitle.value = 0;
}
}();
return;
}, [duration]);
useEffect(() {
() async {
final currentExternalSubtitle = externalSubtitle.value;
if (currentExternalSubtitle == null || externalSubtitles.isEmpty) {
controller.setExternalSubtitle('');
controller.value.setExternalSubtitle('');
} else if (externalSubtitle.value! < externalSubtitles.length) {
bool isExists = true;
@@ -194,7 +170,7 @@ FvpPlayer useFvpPlayer(BuildContext context) {
}
if (isExists) {
controller.setExternalSubtitle(uri);
controller.value.setExternalSubtitle(uri);
} else {
externalSubtitle.value = null;
}
@@ -207,9 +183,9 @@ FvpPlayer useFvpPlayer(BuildContext context) {
useEffect(() {
() async {
if (currentPlay != null &&
isCompleted &&
controller.value.position != Duration.zero &&
controller.value.duration != Duration.zero) {
controller.value.value.isCompleted &&
controller.value.value.position != Duration.zero &&
controller.value.value.duration != Duration.zero) {
logger('Completed: ${currentPlay.file.name}');
if (repeat == Repeat.one) return;
if (currentPlayIndex == playQueue.length - 1) {
@@ -223,30 +199,59 @@ FvpPlayer useFvpPlayer(BuildContext context) {
}
}();
return;
}, [isCompleted]);
}, [controller.value.value.isCompleted]);
useEffect(() {
if (controller.value.isInitialized) {
controller.setPlaybackSpeed(rate);
if (controller.value.value.isInitialized) {
controller.value.setPlaybackSpeed(rate);
}
return;
}, [rate]);
useEffect(() {
if (controller.value.isInitialized) {
controller.setVolume(isMuted ? 0 : volume / 100);
if (controller.value.value.isInitialized) {
controller.value.setVolume(isMuted ? 0 : volume / 100);
}
return;
}, [volume, isMuted]);
useEffect(() {
if (controller.value.isInitialized) {
if (controller.value.value.isInitialized) {
logger('Set looping: $looping');
controller.setLooping(repeat == Repeat.one ? true : false);
controller.value.setLooping(repeat == Repeat.one ? true : false);
}
return;
}, [looping]);
useEffect(() {
() async {
if (controller.value.value.duration != Duration.zero &&
file != null &&
file.type == ContentType.video) {
Progress? progress = history[file.getID()];
if (progress != null) {
if (!alwaysPlayFromBeginning &&
(progress.duration.inMilliseconds -
progress.position.inMilliseconds) >
5000) {
logger(
'Resume progress: ${file.name} position: ${progress.position} duration: ${progress.duration}');
await controller.value.seekTo(progress.position);
}
}
}
if (autoPlay) {
controller.value.play();
}
if (externalSubtitles.isNotEmpty) {
externalSubtitle.value = 0;
}
}();
return;
}, [controller.value.value.duration]);
useEffect(() {
return () {
if (isAndroid &&
@@ -257,14 +262,14 @@ FvpPlayer useFvpPlayer(BuildContext context) {
}
if (file != null &&
controller.value.isInitialized &&
controller.value.duration.inSeconds != 0) {
controller.value.value.isInitialized &&
controller.value.value.duration.inSeconds != 0) {
logger(
'Save progress: ${file.name}, position: ${controller.value.position}, duration: ${controller.value.duration}');
'Save progress: ${file.name}, position: ${controller.value.value.position}, duration: ${controller.value.value.duration}');
useHistoryStore().add(Progress(
dateTime: DateTime.now().toUtc(),
position: controller.value.position,
duration: controller.value.duration,
position: controller.value.value.position,
duration: controller.value.value.duration,
file: file,
));
}
@@ -272,7 +277,7 @@ FvpPlayer useFvpPlayer(BuildContext context) {
}, [currentPlay?.file]);
useEffect(() {
if (isPlaying) {
if (controller.value.value.isPlaying) {
logger('Enable wakelock');
WakelockPlus.enable();
} else {
@@ -280,39 +285,39 @@ FvpPlayer useFvpPlayer(BuildContext context) {
WakelockPlus.disable();
}
return;
}, [isPlaying]);
}, [controller.value.value.isPlaying]);
Future<void> play() async {
if (!controller.value.isInitialized && !isInitializing.value) {
if (!controller.value.value.isInitialized && !isInitializing.value) {
init();
}
controller.play();
controller.value.play();
}
Future<void> pause() async {
controller.pause();
controller.value.pause();
}
Future<void> seekTo(Duration newPosition) async {
logger('Seek to: $newPosition');
if (duration == Duration.zero) return;
if (controller.value.value.duration == Duration.zero) return;
newPosition.inSeconds < 0
? await controller.seekTo(Duration.zero)
: newPosition.inSeconds > duration.inSeconds
? await controller.seekTo(duration)
: await controller.seekTo(newPosition);
? await controller.value.seekTo(Duration.zero)
: newPosition.inSeconds > controller.value.value.duration.inSeconds
? await controller.value.seekTo(controller.value.value.duration)
: await controller.value.seekTo(newPosition);
}
Future<void> stepBackward() async {
if (file?.type == ContentType.video) {
await controller.step(frames: -1);
await controller.value.step(frames: -1);
logger('Step backward');
}
}
Future<void> stepForward() async {
if (file?.type == ContentType.video) {
await controller.step(frames: 1);
await controller.value.step(frames: 1);
logger('Step forward');
}
}
@@ -325,13 +330,13 @@ FvpPlayer useFvpPlayer(BuildContext context) {
return;
}
if (file != null && duration != Duration.zero) {
if (file != null && controller.value.value.duration != Duration.zero) {
logger(
'Save progress: ${file.name}, position: $position, duration: $duration');
'Save progress: ${file.name}, position: ${controller.value.value.position}, duration: ${controller.value.value.duration}');
useHistoryStore().add(Progress(
dateTime: DateTime.now().toUtc(),
position: position,
duration: duration,
position: controller.value.value.position,
duration: controller.value.value.duration,
file: file,
));
}
@@ -340,25 +345,30 @@ FvpPlayer useFvpPlayer(BuildContext context) {
useEffect(() => saveProgress, []);
return FvpPlayer(
controller: controller,
controller: controller.value,
isInitializing: isInitializing.value,
isPlaying: isPlaying,
isPlaying: controller.value.value.isPlaying,
externalSubtitle: externalSubtitle,
externalSubtitles: externalSubtitles,
position: duration == Duration.zero ? Duration.zero : position,
duration: duration,
buffer: buffered.isEmpty || duration == Duration.zero
position: controller.value.value.duration == Duration.zero
? Duration.zero
: buffered.reduce((max, curr) => curr.end > max.end ? curr : max).end,
: controller.value.value.position,
duration: controller.value.value.duration,
buffer: controller.value.value.buffered.isEmpty ||
controller.value.value.duration == Duration.zero
? Duration.zero
: controller.value.value.buffered
.reduce((max, curr) => curr.end > max.end ? curr : max)
.end,
aspect: aspect,
width: size.width,
height: size.height,
width: controller.value.value.size.width,
height: controller.value.value.size.height,
play: play,
pause: pause,
backward: (seconds) =>
seekTo(Duration(seconds: position.inSeconds - seconds)),
forward: (seconds) =>
seekTo(Duration(seconds: position.inSeconds + seconds)),
backward: (seconds) => seekTo(
Duration(seconds: controller.value.value.position.inSeconds - seconds)),
forward: (seconds) => seekTo(
Duration(seconds: controller.value.value.position.inSeconds + seconds)),
stepBackward: stepBackward,
stepForward: stepForward,
seekTo: seekTo,

View File

@@ -377,33 +377,28 @@ class IrisPlayer extends HookWidget {
),
),
Positioned(
left: videoViewOffset.dx,
top: videoViewOffset.dy,
width: videoViewSize.width,
height: videoViewSize.height,
child: player is FvpPlayer
? FittedBox(
left: videoViewOffset.dx,
top: videoViewOffset.dy,
width: videoViewSize.width,
height: videoViewSize.height,
child: switch (player) {
MediaKitPlayer player => Video(
key: ValueKey(currentPlay?.file.uri),
controller: player.controller,
controls: NoVideoControls,
fit:
fit == BoxFit.none ? BoxFit.contain : fit,
),
FvpPlayer player => FittedBox(
fit: fit,
child: SizedBox(
width: player.width,
height: player.height,
child: VideoPlayer(
(player as FvpPlayer).controller),
child: VideoPlayer(player.controller),
),
)
: player is MediaKitPlayer
? Video(
key: ValueKey(currentPlay?.file.uri),
controller:
(player as MediaKitPlayer).controller,
controls: NoVideoControls,
fit: fit == BoxFit.none
? BoxFit.contain
: fit,
// wakelock: mediaType == 'video',
)
: Container(),
),
),
_ => Container(),
}),
// Audio
if (currentPlay?.file.type == ContentType.audio)
Positioned(