import 'dart:convert'; import 'package:PiliPlus/grpc/bilibili/community/service/dm/v1.pb.dart'; import 'package:PiliPlus/pages/danmaku/controller.dart'; import 'package:PiliPlus/pages/danmaku/danmaku_model.dart'; import 'package:PiliPlus/plugin/pl_player/controller.dart'; import 'package:PiliPlus/plugin/pl_player/models/play_status.dart'; import 'package:PiliPlus/plugin/pl_player/utils/danmaku_options.dart'; import 'package:PiliPlus/utils/danmaku_utils.dart'; import 'package:canvas_danmaku/canvas_danmaku.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; /// 传入播放器控制器,监听播放进度,加载对应弹幕 class PlDanmaku extends StatefulWidget { final int cid; final PlPlayerController playerController; final bool isPipMode; final bool isFullScreen; final bool isFileSource; final Size size; const PlDanmaku({ super.key, required this.cid, required this.playerController, this.isPipMode = false, required this.isFullScreen, required this.isFileSource, required this.size, }); @override State createState() => _PlDanmakuState(); bool get notFullscreen => !isFullScreen || isPipMode; } class _PlDanmakuState extends State { PlPlayerController get playerController => widget.playerController; late final PlDanmakuController _plDanmakuController; DanmakuController? _controller; int latestAddedPosition = -1; @override void initState() { super.initState(); _plDanmakuController = PlDanmakuController( widget.cid, playerController, widget.isFileSource, ); if (playerController.enableShowDanmaku.value) { if (widget.isFileSource) { _plDanmakuController.initFileDmIfNeeded(); } else { _plDanmakuController.queryDanmaku( PlDanmakuController.calcSegment( playerController.position.value.inMilliseconds, ), ); } } playerController ..addStatusLister(playerListener) ..addPositionListener(videoPositionListen); } @override void didUpdateWidget(PlDanmaku oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.notFullscreen != widget.notFullscreen && !DanmakuOptions.sameFontScale) { _controller?.updateOption( DanmakuOptions.get(notFullscreen: widget.notFullscreen), ); } } // 播放器状态监听 void playerListener(PlayerStatus? status) { if (status == PlayerStatus.playing) { _controller?.resume(); } else { _controller?.pause(); } } @pragma('vm:notify-debugger-on-exception') void videoPositionListen(Duration position) { if (_controller == null || !playerController.enableShowDanmaku.value) { return; } if (!playerController.showDanmaku && !widget.isPipMode) { return; } if (!playerController.playerStatus.playing) { return; } int currentPosition = position.inMilliseconds; currentPosition -= currentPosition % 100; //取整百的毫秒数 if (currentPosition == latestAddedPosition) { return; } latestAddedPosition = currentPosition; List? currentDanmakuList = _plDanmakuController .getCurrentDanmaku(currentPosition); if (currentDanmakuList != null) { final blockColorful = DanmakuOptions.blockColorful; for (DanmakuElem e in currentDanmakuList) { if (e.mode == 7) { try { _controller!.addDanmaku( SpecialDanmakuContentItem.fromList( DmUtils.decimalToColor(e.color), e.fontsize.toDouble(), jsonDecode(e.content.replaceAll('\n', '\\n')), extra: VideoDanmaku( id: e.id.toInt(), mid: e.midHash, like: e.like.toInt(), ), ), ); } catch (_) {} } else { _controller!.addDanmaku( DanmakuContentItem( e.content, color: blockColorful ? Colors.white : DmUtils.decimalToColor(e.color), type: DmUtils.getPosition(e.mode), isColorful: playerController.showVipDanmaku && e.colorful == DmColorfulType.VipGradualColor, count: e.count > 1 ? e.count : null, selfSend: e.isSelf, extra: VideoDanmaku( id: e.id.toInt(), mid: e.midHash, like: e.like.toInt(), ), ), ); } } } } @override void dispose() { playerController ..removePositionListener(videoPositionListen) ..removeStatusLister(playerListener); _plDanmakuController.dispose(); _controller = null; super.dispose(); } @override Widget build(BuildContext context) { return Obx( () => AnimatedOpacity( opacity: playerController.enableShowDanmaku.value ? playerController.danmakuOpacity.value : 0, duration: const Duration(milliseconds: 100), child: DanmakuScreen( createdController: (e) { playerController.danmakuController = _controller = e; }, option: DanmakuOptions.get( notFullscreen: widget.notFullscreen, speed: playerController.playbackSpeed, ), size: widget.size, ), ), ); } }