Closed MCDFsteve closed 2 weeks ago
我不知道这是什么原因造成的,这部分代码看上去没有什么问题。
这是您的项目fnipaplayer的一部分吗,如果是的话,我明天把那个项目拉到本地看一看,今天有些太晚了。
我是各种排除之后,发现移除DanmakuScreen()
部分以后,字幕就不会继续闪烁了。我的项目是https://www.github.com/MCDFsteve/FnipaPlay
有测试用的视频和字幕文件吗
我使用的视频是
magnet:?xt=urn:btih:083e893cc60a5f049ec8210bb2ec2d905809b98b&tr=http://t.nyaatracker.com/announce&tr=http://tracker.kamigami.org:2710/announce&tr=http://share.camoe.cn:8080/announce&tr=http://opentracker.acgnx.se/announce&tr=http://anidex.moe:6969/announce&tr=http://t.acg.rip:6699/announce&tr=https://tr.bangumi.moe:9696/announce&tr=udp://tr.bangumi.moe:6969/announce&tr=http://open.acgtracker.com:1096/announce&tr=udp://tracker.opentrackr.org:1337/announce
这是磁力链接,需要种子下载器下载源文件。其内嵌了srt字幕文件
同一个视频,我注释掉DanmakuScreen()
部分以后,就会恢复正常,不闪烁字幕了。就像这样:
// 弹幕组件
/*DanmakuScreen(
createdController: (DanmakuController e) {
_controllerdanmaku = e;
},
option: DanmakuOption(
fontSize: 30,
),
),*/
然后,我将canvas_danmaku
的代码放置到了本地文件夹,不过只是因为我修改了弹幕描边粗细,以及添加了“将黑色弹幕的描边处理为白色”的功能。修改仅限于utils.dart
我无法使用 fnipaplay 仓库中的代码进行测试,代码中包含错误,danmakuController 没有被初始化。
我尝试使用 fvp 的 example 和 canvas_danmaku 以及您提供的视频文件制作了一个简单的 sample 。没有复现这一问题。
你可以更新一下 fnipaplay 仓库中的代码吗。
此外,fnipaplay 中的代码在我看来似乎有一些结构上的问题。除了抽象和状态管理不是很恰当之外,这似乎是一个 flutter 库的结构,而不是一个 flutter 工程的结构。这对我的分析造成了一定的困难。不过这应该并不影响其正常运行。
您制作的sample可以让我参考一下吗?
这个错误我并不清楚是什么原因,本地正常运行的代码我进行了简单的推送(略过了build文件夹)
sample 如下,视频文件放置在 assets/boku.mkv,点按弹幕测试按钮加载弹幕。
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
import 'package:canvas_danmaku/canvas_danmaku.dart';
import 'package:fvp/fvp.dart' as fvp;
void main() {
fvp.registerWith();
runApp(
MaterialApp(
home: _App(),
),
);
}
class _App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
key: const ValueKey<String>('home_page'),
body: _BumbleBeeRemoteVideo()
),
);
}
}
class _BumbleBeeRemoteVideo extends StatefulWidget {
@override
_BumbleBeeRemoteVideoState createState() => _BumbleBeeRemoteVideoState();
}
class _BumbleBeeRemoteVideoState extends State<_BumbleBeeRemoteVideo> {
late VideoPlayerController _controller;
late DanmakuController _danmakuController;
@override
void initState() {
super.initState();
_controller = VideoPlayerController.asset(
'assets/boku.mkv',
videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true),
);
_controller.addListener(() {
setState(() {});
});
_controller.setLooping(true);
_controller.initialize();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
children: <Widget>[
Container(
padding: const EdgeInsets.all(20),
child: AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: Stack(
alignment: Alignment.bottomCenter,
children: <Widget>[
VideoPlayer(_controller),
ClosedCaption(text: _controller.value.caption.text),
_ControlsOverlay(controller: _controller),
VideoProgressIndicator(_controller, allowScrubbing: true),
TextButton(onPressed: () {
_danmakuController.addDanmaku(DanmakuContentItem('这是一条很长很长很长的弹幕'));
}, child: const Text('弹幕测试')),
DanmakuScreen(
createdController: (e) {
_danmakuController = e;
},
option: DanmakuOption(
fontSize: 30,
),
),
],
),
),
),
],
),
);
}
}
class _ControlsOverlay extends StatelessWidget {
const _ControlsOverlay({required this.controller});
static const List<Duration> _exampleCaptionOffsets = <Duration>[
Duration(seconds: -10),
Duration(seconds: -3),
Duration(seconds: -1, milliseconds: -500),
Duration(milliseconds: -250),
Duration.zero,
Duration(milliseconds: 250),
Duration(seconds: 1, milliseconds: 500),
Duration(seconds: 3),
Duration(seconds: 10),
];
static const List<double> _examplePlaybackRates = <double>[
0.25,
0.5,
1.0,
1.5,
2.0,
3.0,
5.0,
10.0,
];
final VideoPlayerController controller;
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
AnimatedSwitcher(
duration: const Duration(milliseconds: 50),
reverseDuration: const Duration(milliseconds: 200),
child: controller.value.isPlaying
? const SizedBox.shrink()
: Container(
color: Colors.black26,
child: const Center(
child: Icon(
Icons.play_arrow,
color: Colors.white,
size: 100.0,
semanticLabel: 'Play',
),
),
),
),
GestureDetector(
onTap: () {
controller.value.isPlaying ? controller.pause() : controller.play();
},
),
Align(
alignment: Alignment.topLeft,
child: PopupMenuButton<Duration>(
initialValue: controller.value.captionOffset,
tooltip: 'Caption Offset',
onSelected: (Duration delay) {
controller.setCaptionOffset(delay);
},
itemBuilder: (BuildContext context) {
return <PopupMenuItem<Duration>>[
for (final Duration offsetDuration in _exampleCaptionOffsets)
PopupMenuItem<Duration>(
value: offsetDuration,
child: Text('${offsetDuration.inMilliseconds}ms'),
)
];
},
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 12,
horizontal: 16,
),
child: Text('${controller.value.captionOffset.inMilliseconds}ms'),
),
),
),
Align(
alignment: Alignment.topRight,
child: PopupMenuButton<double>(
initialValue: controller.value.playbackSpeed,
tooltip: 'Playback speed',
onSelected: (double speed) {
controller.setPlaybackSpeed(speed);
},
itemBuilder: (BuildContext context) {
return <PopupMenuItem<double>>[
for (final double speed in _examplePlaybackRates)
PopupMenuItem<double>(
value: speed,
child: Text('${speed}x'),
)
];
},
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 12,
horizontal: 16,
),
child: Text('${controller.value.playbackSpeed}x'),
),
),
),
],
);
}
}
试过对接dandanplay的弹幕加载吗(在我的代码里是计算hash以后get请求拉取json文件)
和那个没有什么关系吧,无论如何最后都是调用 addDanmaku 方法。
我的 smaple 在您那边运行正常吗。
如果 fnipaplay 仓库中的代码不是损坏的,我可能可以做一些尝试,但是现在 fnipaplay 仓库中的代码存在错误。
fnipaplay 的页面布局我觉得有些怪怪的,canvas_danmaku 例子中的 Container() 是留给播放器的,而不是现在这样 Stack 里嵌套 Stack。以及闪烁问题会让人首先想到重绘,可能是页面里有什么地方在频繁重绘。
我将sample代码放进了一个项目的main.dart进行了替换并flutter pub get获取了所有库,那个视频我也改名字为boku.mkv放进了项目根目录的assets文件夹以后。打开的窗口没有出现画面,按 播放 按钮也没啥反应。
pubspec.yaml 没有设置 assets 的访问权限?
我仓库的代码没有问题啊。我从云端拉取到我本地的下载文件夹并在flutter pub get后执行flutter run -d macos,程序正常的工作。
另外我给予assets权限以后视频正常播放了,但是字幕一样存在闪烁。
https://github.com/user-attachments/assets/0cc21902-96b0-4658-ab74-63c16350570b
所以似乎是一个只存在于macOS的问题
看上去这是一个特定于 MacOS 的问题,因为这个问题无法在 windows 上复现。
canvas_danmaku 是一个纯 dart 库,也就是 canvas_danmaku 中不包含特定于平台的代码。
这可能是一个 video_player 的问题,但我没有办法测试,你可以试试移除 fvp 能否复现,video_player 包有 MacOS 支持,不需要 fvp 的补充。这样可以确认问题来自 video_player 还是 fvp。
如果问题来自 video_player 我的推测是弹幕绘制和字幕绘制都用了相对底层的 CustomPainter ,在一个 Stack 中绘制时触发了 flutter 在 MacOS 上实现的一些奇怪问题。
作为临时的解决方案,可以尝试使用 Overlay 而不是 Stack 。
但是video_player本身并不支持h265的mkv视频,我也不确定是否支持内嵌srt,移除fvp的话大概率视频本身就无法播放。关于Overlay,我后面尝试一下
理论上 video_player 在 macOS 的底层是 AVPlayer 是支持 h265 的。
fvp 的字幕功能似乎是自己实现的,而不是依赖于 video_player 。所以我觉得应该确认一下问题来自 fvp 还是 video_player。
移除fvp以后,播放h265的视频现在会一直转圈圈加载
可能的解决方案还有
media_kit之前使用的时候发现字幕渲染使用的自己的一套规则从而无视ass字幕内部定义的那些样式和特效
我没有办法向 fvp 或是 video_player 报告这一问题,因为我并没有 MacOS 设备。使用 CustomPainter 叠加在 Player 上方应该就能复现这一问题,如果你有兴趣的话,可以向他们报告,错误报告中不应包含第三方库(也就是 canvas_danmaku),你可能需要自己加一个简易的 CustomPainer。
看上去应该做出一些取舍,弃用 canvas_danmaku 使用 ns_danmaku。或者弃用 fvp 使用 media_kit 。media_kit 可以自定义字幕渲染样式。
此外,我前面提到的使用 Overlay 取代 Stack 或许能解决问题。
我注意到了一个有趣的新出现的视频播放器 av_media_player
它有着优秀的性能,其实现方案和 fvp 类似,因此它取得了和 fvp 一样优秀的构建体积。
它完全开源,而不是像 fvp 那样使用了大量恼人的私有代码。
字幕方面,它基于 webATT 实现了字幕支持。
我认为对于你的使用场合,这个播放器库似乎是最优解。
可惜对我来说,它还缺乏一些串流网络媒体所需的必要支持。(例如自定义HTTP标头)
我注意到了一个有趣的新出现的视频播放器 av_media_player
它有着优秀的性能,其实现方案和 fvp 类似,因此它取得了和 fvp 一样优秀的构建体积。
它完全开源,而不是像 fvp 那样使用了大量恼人的私有代码。
字幕方面,它基于 webATT 实现了字幕支持。
我认为对于你的使用场合,这个播放器库似乎是最优解。
可惜对我来说,它还缺乏一些串流网络媒体所需的必要支持。(例如自定义HTTP标头)
谢谢 我找机会看一下 不过我做的本地看番播放器 很多字幕组会压制ass字幕(包含字体)进mkv文件 所以基本上得需要libass或类似功能的才能正常加载这些字幕 webATT听起来是web技术?但是web到现在不支持ass
我的表述问题,准确来说,它支持显示 webVTT 格式
的字幕。
我不知道其是否支持 ass 。
我的表述问题,准确来说,它支持显示 webVTT
格式
的字幕。我不知道其是否支持 ass 。
我后面试试看
我尝试media_kit以后发现mkv格式的视频无法播放(h265编码和h264编码都试过,一直加载的圈圈),不知道是不是我配置问题(media_kit收到的报错是flutter: Error: PlatformException(VideoError, Failed to load video: Cannot Open, null, null))。另外我也试过canvas_danmaku的绘制从Stack换成Overlay,依旧闪烁。也试过把canvas_danmaku换成ns_danmaku,问题依旧
media_kit 基于 mpv,理论上可以解码 mkv 格式,我不知道为什么会这样。我找到了打开的 Issue media_kit/media_kit#809 这确实是 media_kit 的问题,或许它可以通过升级 mpv 库解决,但是 media_kit 现在缺乏维护。
如果 ns_danmaku 也有问题,那么闪烁问题应该和 CustomPaint 的使用无关。fvp 的字幕功能是自己实现的,这可能是一个垂直同步问题,你可以向 fvp 报告该问题。
作为一个可能的临时缓解措施,尝试在 macOS 上启用实验性的 Impeller 渲染引擎,这或许有用。
media_kit/media_kit#809
我已经报告给了fvp,我去看看实验性的 Impeller 渲染引擎
这样开启?
<dict>
<key>FlutterEnableImpeller</key>
<true/>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
运行没有报错,但是依旧闪烁。之前发生问题的flutter版本是3.22,我更新为3.24以后还是一样
是这样的没错,注意一下 macOS 的调试模式和发布模式的配置文件在不同位置就可以了。
其实可以直接用 flutter run --enable-impeller
来尝试 impeller 而不用修改配置文件。
依旧闪烁的话就没办法了,等 fvp 的作者解决吧。
确实是实验性,因为我使用flutter run -d macos --enable-impeller
以后播放视频是黑屏状态。只能等待fvp那边的issue结果了
此问题已由 fvp 版本更新解决。
我将
DanmakuScreen()
放置在build
的body
内的任何位置,装载弹幕时字幕都会不间断闪烁(实际上只放置DanmakuScreen()
而不调用装载弹幕相关也是如此)。不知道是因为什么原因导致的这个。https://github.com/user-attachments/assets/98474194-ebd1-4128-88c6-b52a3e592ca6
部分代码:
FFprobe扫描的视频信息: