Compare commits
15 Commits
fix
...
v1.0.24.06
Author | SHA1 | Date | |
---|---|---|---|
fb26d35bb0 | |||
92b9e4f155 | |||
d40be085fe | |||
041552cc9a | |||
c65baa4337 | |||
56e8c5bf5f | |||
18960c1d02 | |||
f1bb21981a | |||
f598c81b53 | |||
ec7762644b | |||
5500a58c32 | |||
2ba72e3792 | |||
25c416f9bf | |||
9ec9fc2830 | |||
c0f3b4f3a2 |
2
.github/workflows/beta_ci.yml
vendored
2
.github/workflows/beta_ci.yml
vendored
@ -4,7 +4,7 @@ on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
- "x-main"
|
||||
paths-ignore:
|
||||
- "**.md"
|
||||
- "**.txt"
|
||||
|
73
README.md
73
README.md
@ -2,8 +2,6 @@
|
||||
<img width="200" height="200" src="https://github.com/guozhigq/pilipala/blob/main/assets/images/logo/logo_android.png">
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div align="center">
|
||||
<h1>PiliPala</h1>
|
||||
<div align="center">
|
||||
@ -11,8 +9,9 @@
|
||||

|
||||

|
||||

|
||||
|
||||
</div>
|
||||
<p>使用Flutter开发的BiliBili第三方客户端</p>
|
||||
<p>使用 Flutter 开发的 BiliBili 第三方客户端</p>
|
||||
|
||||
<img src="https://github.com/guozhigq/pilipala/blob/main/assets/screenshots/510shots_so.png" width="32%" alt="home" />
|
||||
<img src="https://github.com/guozhigq/pilipala/blob/main/assets/screenshots/174shots_so.png" width="32%" alt="home" />
|
||||
@ -23,46 +22,41 @@
|
||||
</div>
|
||||
|
||||
## 开发环境
|
||||
Xcode 13.4 不支持**auto_orientation**,请注释相关代码
|
||||
|
||||
Xcode 13.4 不支持 ```auto_orientation```,请注释相关代码
|
||||
|
||||
```bash
|
||||
[✓] Flutter (Channel stable, 3.16.4, on macOS 14.1.2 23B92 darwin-arm64, locale
|
||||
[✓] Flutter (Channel stable, 3.16.5, on macOS 14.1.2 23B92 darwin-arm64, locale
|
||||
zh-Hans-CN)
|
||||
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
|
||||
[✓] Xcode - develop for iOS and macOS (Xcode 15.1)
|
||||
[✓] Chrome - develop for the web
|
||||
[✓] Android Studio (version 2022.3)
|
||||
[✓] VS Code (version 1.85.1)
|
||||
[✓] VS Code (version 1.87.2)
|
||||
[✓] Connected device (3 available)
|
||||
[✓] Network resources
|
||||
|
||||
```
|
||||
|
||||
<br/>
|
||||
|
||||
|
||||
## 技术交流
|
||||
|
||||
Telegram: https://t.me/+lm_oOVmF0RJiODk1
|
||||
QQ频道: https://pd.qq.com/s/365esodk3
|
||||
|
||||
Telegram Beta 版本:@PiliPala_Beta
|
||||
|
||||
<br/>
|
||||
QQ 频道: https://pd.qq.com/s/365esodk3
|
||||
|
||||
## 功能
|
||||
|
||||
目前着重移动端(Android、iOS),暂时没有适配桌面端、Pad端、手表端等
|
||||
|
||||
<br/>
|
||||
目前着重移动端 (Android、iOS),暂时没有适配桌面端、Pad 端、手表端等
|
||||
|
||||
现有功能及[开发计划](https://github.com/users/guozhigq/projects/5)
|
||||
|
||||
|
||||
- [x] 推荐视频列表(app端)
|
||||
- [x] 推荐视频列表 (app 端)
|
||||
- [x] 最热视频列表
|
||||
- [x] 热门直播
|
||||
- [x] 番剧列表
|
||||
- [x] 屏蔽黑名单内用户视频
|
||||
- [x] 排行榜
|
||||
|
||||
- [x] 用户相关
|
||||
- [x] 粉丝、关注用户、拉黑用户查看
|
||||
@ -72,11 +66,13 @@ QQ频道: https://pd.qq.com/s/365esodk3
|
||||
- [x] 稍后再看
|
||||
- [x] 观看记录
|
||||
- [x] 我的收藏
|
||||
- [x] 黑名单管理
|
||||
|
||||
- [x] 动态相关
|
||||
- [x] 全部、投稿、番剧分类查看
|
||||
- [x] 动态评论查看
|
||||
- [x] 动态评论回复功能
|
||||
- [x] 动态未读标记
|
||||
|
||||
- [x] 视频播放相关
|
||||
- [x] 双击快进/快退
|
||||
@ -85,13 +81,13 @@ QQ频道: https://pd.qq.com/s/365esodk3
|
||||
- [x] 垂直方向上滑全屏、下滑退出全屏
|
||||
- [x] 水平方向手势快进/快退
|
||||
- [x] 全屏方向设置
|
||||
- [x] 倍速选择/长按2倍速
|
||||
- [x] 硬件加速(视机型而定)
|
||||
- [x] 画质选择(高清画质未解锁)
|
||||
- [x] 音质选择(视视频而定)
|
||||
- [x] 解码格式选择(视视频而定)
|
||||
- [x] 倍速选择/长按 2 倍速
|
||||
- [x] 硬件加速 (视机型而定)
|
||||
- [x] 画质选择 (高清画质未解锁)
|
||||
- [x] 音质选择 (视视频而定)
|
||||
- [x] 解码格式选择 (视视频而定)
|
||||
- [x] 弹幕
|
||||
- [ ] 字幕
|
||||
- [x] 字幕
|
||||
- [x] 记忆播放
|
||||
- [x] 视频比例:高度/宽度适应、填充、包含等
|
||||
|
||||
@ -103,12 +99,12 @@ QQ频道: https://pd.qq.com/s/365esodk3
|
||||
- [x] 视频搜索排序、按时长筛选
|
||||
|
||||
- [x] 视频详情页相关
|
||||
- [x] 视频选集(分p)切换
|
||||
- [x] 视频选集 (分 p) 切换
|
||||
- [x] 点赞、投币、收藏/取消收藏
|
||||
- [x] 相关视频查看
|
||||
- [x] 评论用户身份标识
|
||||
- [x] 评论(排序)查看、二楼评论查看
|
||||
- [x] 主楼、二楼评论回复功能
|
||||
- [x] 评论 (排序) 查看、二楼评论查看
|
||||
- [x] 主楼、二楼评论/表情回复功能
|
||||
- [x] 评论点赞
|
||||
- [x] 评论笔记图片查看、保存
|
||||
|
||||
@ -116,29 +112,30 @@ QQ频道: https://pd.qq.com/s/365esodk3
|
||||
- [x] 画质、音质、解码方式预设
|
||||
- [x] 图片质量设定
|
||||
- [x] 主题模式:亮色/暗色/跟随系统
|
||||
- [x] 震动反馈(可选)
|
||||
- [x] 震动反馈 (可选)
|
||||
- [x] 高帧率
|
||||
- [x] 自动全屏
|
||||
- [ ] 等等
|
||||
|
||||
<br/>
|
||||
|
||||
## 下载
|
||||
|
||||
可以通过右侧release进行下载或拉取代码到本地进行编译
|
||||
可以通过右侧 Releases 进行下载或拉取代码到本地进行编译
|
||||
|
||||
<br/>
|
||||
### 从 F-Droid 安装
|
||||
|
||||
<a href="https://f-droid.org/packages/com.guozhigq.pilipala">
|
||||
<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on-zh-cn.png"
|
||||
alt="Get it on F-Droid"
|
||||
height="80">
|
||||
</a>
|
||||
|
||||
## 声明
|
||||
|
||||
此项目(PiliPala)是个人为了兴趣而开发, 仅用于学习和测试。
|
||||
所用API皆从官方网站收集, 不提供任何破解内容。
|
||||
此项目 (PiliPala) 是个人为了兴趣而开发, 仅用于学习和测试。
|
||||
所用 API 皆从官方网站收集, 不提供任何破解内容。
|
||||
|
||||
感谢使用
|
||||
|
||||
|
||||
<br/>
|
||||
|
||||
## 致谢
|
||||
|
||||
- [bilibili-API-collect](https://github.com/SocialSisterYi/bilibili-API-collect)
|
||||
@ -146,7 +143,3 @@ QQ频道: https://pd.qq.com/s/365esodk3
|
||||
- [media-kit](https://github.com/media-kit/media-kit)
|
||||
- [dio](https://pub.dev/packages/dio)
|
||||
- 等等
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
|
29
change_log/1.0.22.0430.md
Normal file
29
change_log/1.0.22.0430.md
Normal file
@ -0,0 +1,29 @@
|
||||
## 1.0.22
|
||||
|
||||
明天晚上或者后天晚上应该会更新
|
||||
|
||||
### 功能
|
||||
+ 字幕
|
||||
+ 全屏时选集
|
||||
+ 动态转发
|
||||
+ 评论视频并转发
|
||||
+ 收藏夹删除
|
||||
+ 合集显示封面
|
||||
+ 底部导航栏编辑、排序功能
|
||||
+ 历史记录进度条展示
|
||||
+ 直播画质切换
|
||||
+ 排行榜功能
|
||||
+ 视频详情页推荐视频开关
|
||||
+ 显示联合投稿up
|
||||
|
||||
### 修复
|
||||
+ 收藏夹个数错误
|
||||
+ 封面保存权限问题
|
||||
+ 合集最后1p未展示
|
||||
+ up主页关注按钮触发灰屏
|
||||
|
||||
### 优化
|
||||
+ 视频简介查看逻辑
|
||||
|
||||
更多更新日志可在Github上查看
|
||||
问题反馈、功能建议请查看「关于」页面。
|
15
change_log/1.0.23.0505.md
Normal file
15
change_log/1.0.23.0505.md
Normal file
@ -0,0 +1,15 @@
|
||||
## 1.0.23
|
||||
|
||||
### 功能
|
||||
+ 封面下载
|
||||
|
||||
|
||||
### 修复
|
||||
+ 全屏问题
|
||||
+ 视频播放器灰屏问题
|
||||
+ 评论区点击区域问题
|
||||
+ 动态详情跳转异常问题
|
||||
|
||||
|
||||
更多更新日志可在Github上查看
|
||||
问题反馈、功能建议请查看「关于」页面。
|
25
change_log/1.0.24.0626.md
Normal file
25
change_log/1.0.24.0626.md
Normal file
@ -0,0 +1,25 @@
|
||||
## 1.0.24
|
||||
|
||||
该版本代码未开源,酌情使用。
|
||||
|
||||
### 功能
|
||||
+ 私信功能
|
||||
+ 回复我的、收到的赞查看
|
||||
+ 新的登录方式
|
||||
+ 全屏选集
|
||||
+ 一键三连
|
||||
+ 按分区搜索
|
||||
|
||||
### 优化
|
||||
+ 页面跳转动画
|
||||
+ 评论区跳转
|
||||
|
||||
### 修复
|
||||
+ 音画不同步问题
|
||||
+ 分集字幕未同步
|
||||
+ 多语言字幕
|
||||
+ 弹幕设置未生效
|
||||
|
||||
|
||||
更多更新日志可在Github上查看
|
||||
问题反馈、功能建议请查看「关于」页面。
|
@ -44,6 +44,7 @@ class _SelectDialogState<T> extends State<SelectDialog<T>> {
|
||||
setState(() {
|
||||
_tempValue = value as T;
|
||||
});
|
||||
Navigator.pop(context, _tempValue);
|
||||
},
|
||||
),
|
||||
]
|
||||
@ -51,19 +52,6 @@ class _SelectDialogState<T> extends State<SelectDialog<T>> {
|
||||
),
|
||||
);
|
||||
}),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, _tempValue),
|
||||
child: const Text('确定'),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ class VideoDetailController extends GetxController
|
||||
late bool enableCDN;
|
||||
late int? cacheVideoQa;
|
||||
late String cacheDecode;
|
||||
late int cacheAudioQa;
|
||||
late int defaultAudioQa;
|
||||
|
||||
PersistentBottomSheetController? replyReplyBottomSheetCtr;
|
||||
RxList<SubTitileContentModel> subtitleContents =
|
||||
@ -146,7 +146,7 @@ class VideoDetailController extends GetxController
|
||||
// 预设的解码格式
|
||||
cacheDecode = setting.get(SettingBoxKey.defaultDecode,
|
||||
defaultValue: VideoDecodeFormats.values.last.code);
|
||||
cacheAudioQa = setting.get(SettingBoxKey.defaultAudioQa,
|
||||
defaultAudioQa = setting.get(SettingBoxKey.defaultAudioQa,
|
||||
defaultValue: AudioQuality.hiRes.code);
|
||||
oid.value = IdUtils.bv2av(Get.parameters['bvid']!);
|
||||
getSubtitle();
|
||||
@ -353,9 +353,9 @@ class VideoDetailController extends GetxController
|
||||
|
||||
if (audiosList.isNotEmpty) {
|
||||
final List<int> numbers = audiosList.map((map) => map.id!).toList();
|
||||
int closestNumber = Utils.findClosestNumber(cacheAudioQa, numbers);
|
||||
if (!numbers.contains(cacheAudioQa) &&
|
||||
numbers.any((e) => e > cacheAudioQa)) {
|
||||
int closestNumber = Utils.findClosestNumber(defaultAudioQa, numbers);
|
||||
if (!numbers.contains(defaultAudioQa) &&
|
||||
numbers.any((e) => e > defaultAudioQa)) {
|
||||
closestNumber = 30280;
|
||||
}
|
||||
firstAudio = audiosList.firstWhere((e) => e.id == closestNumber);
|
||||
|
@ -17,6 +17,7 @@ import 'package:pilipala/plugin/pl_player/index.dart';
|
||||
import 'package:pilipala/plugin/pl_player/models/play_repeat.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
import 'package:pilipala/services/shutdown_timer_service.dart';
|
||||
import '../../../../http/danmaku.dart';
|
||||
import '../../../../models/common/search_type.dart';
|
||||
import '../../../../models/video_detail_res.dart';
|
||||
import '../introduction/index.dart';
|
||||
@ -52,7 +53,7 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
final Box<dynamic> videoStorage = GStrorage.video;
|
||||
late List<double> speedsList;
|
||||
double buttonSpace = 8;
|
||||
bool showTitle = false;
|
||||
RxBool isFullScreen = false.obs;
|
||||
late String heroTag;
|
||||
late VideoIntroController videoIntroController;
|
||||
late VideoDetailData videoDetail;
|
||||
@ -69,13 +70,8 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
}
|
||||
|
||||
void fullScreenStatusListener() {
|
||||
widget.videoDetailCtr!.plPlayerController.isFullScreen
|
||||
.listen((bool isFullScreen) {
|
||||
if (isFullScreen) {
|
||||
showTitle = true;
|
||||
} else {
|
||||
showTitle = false;
|
||||
}
|
||||
widget.videoDetailCtr!.plPlayerController.isFullScreen.listen((bool val) {
|
||||
isFullScreen.value = val;
|
||||
|
||||
/// TODO setState() called after dispose()
|
||||
if (mounted) {
|
||||
@ -218,6 +214,87 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
);
|
||||
}
|
||||
|
||||
/// 发送弹幕
|
||||
void showShootDanmakuSheet() {
|
||||
final TextEditingController textController = TextEditingController();
|
||||
bool isSending = false; // 追踪是否正在发送
|
||||
showDialog(
|
||||
context: Get.context!,
|
||||
builder: (BuildContext context) {
|
||||
// TODO: 支持更多类型和颜色的弹幕
|
||||
return AlertDialog(
|
||||
title: const Text('发送弹幕(测试)'),
|
||||
content: StatefulBuilder(
|
||||
builder: (BuildContext context, StateSetter setState) {
|
||||
return TextField(
|
||||
controller: textController,
|
||||
);
|
||||
}),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Get.back(),
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||
),
|
||||
),
|
||||
StatefulBuilder(
|
||||
builder: (BuildContext context, StateSetter setState) {
|
||||
return TextButton(
|
||||
onPressed: isSending
|
||||
? null
|
||||
: () async {
|
||||
final String msg = textController.text;
|
||||
if (msg.isEmpty) {
|
||||
SmartDialog.showToast('弹幕内容不能为空');
|
||||
return;
|
||||
} else if (msg.length > 100) {
|
||||
SmartDialog.showToast('弹幕内容不能超过100个字符');
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
isSending = true; // 开始发送,更新状态
|
||||
});
|
||||
//修改按钮文字
|
||||
final dynamic res = await DanmakaHttp.shootDanmaku(
|
||||
oid: widget.videoDetailCtr!.cid.value,
|
||||
msg: textController.text,
|
||||
bvid: widget.videoDetailCtr!.bvid,
|
||||
progress:
|
||||
widget.controller!.position.value.inMilliseconds,
|
||||
type: 1,
|
||||
);
|
||||
setState(() {
|
||||
isSending = false; // 发送结束,更新状态
|
||||
});
|
||||
if (res['status']) {
|
||||
SmartDialog.showToast('发送成功');
|
||||
// 发送成功,自动预览该弹幕,避免重新请求
|
||||
// TODO: 暂停状态下预览弹幕仍会移动与计时,可考虑添加到dmSegList或其他方式实现
|
||||
widget.controller!.danmakuController!.addItems([
|
||||
DanmakuItem(
|
||||
msg,
|
||||
color: Colors.white,
|
||||
time: widget
|
||||
.controller!.position.value.inMilliseconds,
|
||||
type: DanmakuItemType.scroll,
|
||||
isSend: true,
|
||||
)
|
||||
]);
|
||||
Get.back();
|
||||
} else {
|
||||
SmartDialog.showToast('发送失败,错误信息为${res['msg']}');
|
||||
}
|
||||
},
|
||||
child: Text(isSending ? '发送中...' : '发送'),
|
||||
);
|
||||
})
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 定时关闭
|
||||
void scheduleExit() async {
|
||||
const List<int> scheduleTimeChoices = [
|
||||
@ -1079,7 +1156,7 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
},
|
||||
),
|
||||
SizedBox(width: buttonSpace),
|
||||
if (showTitle &&
|
||||
if (isFullScreen.value &&
|
||||
isLandscape &&
|
||||
widget.videoType == SearchType.video) ...[
|
||||
Column(
|
||||
@ -1131,6 +1208,43 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
// ),
|
||||
// fuc: () => _.screenshot(),
|
||||
// ),
|
||||
if (isFullScreen.value) ...[
|
||||
SizedBox(
|
||||
width: 56,
|
||||
height: 34,
|
||||
child: TextButton(
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
onPressed: () => showShootDanmakuSheet(),
|
||||
child: const Text(
|
||||
'发弹幕',
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 34,
|
||||
height: 34,
|
||||
child: Obx(
|
||||
() => IconButton(
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
onPressed: () {
|
||||
_.isOpenDanmu.value = !_.isOpenDanmu.value;
|
||||
},
|
||||
icon: Icon(
|
||||
_.isOpenDanmu.value
|
||||
? Icons.subtitles_outlined
|
||||
: Icons.subtitles_off_outlined,
|
||||
size: 19,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
SizedBox(width: buttonSpace),
|
||||
if (Platform.isAndroid) ...<Widget>[
|
||||
SizedBox(
|
||||
|
@ -209,6 +209,8 @@ class Utils {
|
||||
static int findClosestNumber(int target, List<int> numbers) {
|
||||
int minDiff = 127;
|
||||
int closestNumber = 0; // 初始化为0,表示没有找到比目标值小的整数
|
||||
|
||||
// 向下查找
|
||||
try {
|
||||
for (int number in numbers) {
|
||||
if (number < target) {
|
||||
@ -221,6 +223,20 @@ class Utils {
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
// 向上查找
|
||||
if (closestNumber == 0) {
|
||||
try {
|
||||
for (int number in numbers) {
|
||||
int diff = (number - target).abs();
|
||||
|
||||
if (diff < minDiff) {
|
||||
minDiff = diff;
|
||||
closestNumber = number;
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
return closestNumber;
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 1.0.21+1021
|
||||
version: 1.0.24+1024
|
||||
|
||||
environment:
|
||||
sdk: ">=3.0.0 <4.0.0"
|
||||
|
Reference in New Issue
Block a user