Skip to content

Commit

Permalink
feat: 新增字幕功能,倍速移至底部
Browse files Browse the repository at this point in the history
  • Loading branch information
orz12 committed Feb 27, 2024
1 parent 11c2a69 commit 81da31c
Show file tree
Hide file tree
Showing 4 changed files with 306 additions and 31 deletions.
4 changes: 4 additions & 0 deletions lib/http/api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ class Api {
// https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/video/videostream_url.md
static const String videoUrl = '/x/player/wbi/playurl';

// 字幕
// aid, cid
static const String subtitleUrl = '/x/player/wbi/v2';

// 视频详情
// 竖屏 https://api.bilibili.com/x/web-interface/view?aid=527403921
// https://api.bilibili.com/x/web-interface/view/detail 获取视频超详细信息(web端)
Expand Down
110 changes: 109 additions & 1 deletion lib/http/video.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'dart:developer';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:hive/hive.dart';
import '../common/constants.dart';
import '../models/common/reply_type.dart';
Expand Down Expand Up @@ -155,7 +156,8 @@ class VideoHttp {
}

// 免登录查看1080p
if ((userInfoCache.get('userInfoCache') == null || MineController.anonymity) &&
if ((userInfoCache.get('userInfoCache') == null ||
MineController.anonymity) &&
setting.get(SettingBoxKey.p1080, defaultValue: true)) {
data['try_look'] = 1;
}
Expand Down Expand Up @@ -476,4 +478,110 @@ class VideoHttp {
return {'status': false, 'data': []};
}
}

static Future subtitlesJson(
{String? aid, String? bvid, required int cid}) async {
assert(aid != null || bvid != null);
var res = await Request().get(Api.subtitleUrl, data: {
if (aid != null) 'aid': aid,
if (bvid != null) 'bvid': bvid,
'cid': cid,
});
if (res.data['code'] == 0) {
dynamic data = res.data['data'];
List subtitlesJson = data['subtitle']['subtitles'];
/*
[
{
"id": 1430455228267894300,
"lan": "ai-zh",
"lan_doc": "中文(自动生成)",
"is_lock": false,
"subtitle_url": "//aisubtitle.hdslb.com/bfs/ai_subtitle/prod/15508958271448462983dacf99a49f40ccdf91a4df8d925e2b58?auth_key=1708941835-aaa0e44844594386ad356795733983a2-0-89af73c6aad5a1fca43b02113fa9d485",
"type": 1,
"id_str": "1430455228267894272",
"ai_type": 0,
"ai_status": 2
}
]
*/
return {
'status': true,
'data': subtitlesJson,
};
} else {
return {'status': false, 'data': [], 'msg': res.data['message']};
}
}

static Future vttSubtitles(List subtitlesJson) async {
if (subtitlesJson.isEmpty) {
return [];
}
List<Map<String, String>> subtitlesVtt = [];

String subtitleTimecode(double seconds) {
int h = (seconds / 3600).floor();
int m = ((seconds % 3600) / 60).floor();
int s = (seconds % 60).floor();
int ms = ((seconds * 1000) % 1000).floor();
if (h == 0) {
return "${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}.${ms.toString().padLeft(3, '0')}";
}
return "${h.toString().padLeft(2, '0')}:${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}.${ms.toString().padLeft(3, '0')}";
}

for (var i in subtitlesJson) {
var res =
await Request().get("https://${i['subtitle_url'].split('//')[1]}");
/*
{
"font_size": 0.4,
"font_color": "#FFFFFF",
"background_alpha": 0.5,
"background_color": "#9C27B0",
"Stroke": "none",
"type": "AIsubtitle",
"lang": "zh",
"version": "v1.6.0.4",
"body": [
{
"from": 0.5,
"to": 1.58,
"sid": 1,
"location": 2,
"content": "很多人可能不知道",
"music": 0.0
},
……,
{
"from": 558.629,
"to": 560.22,
"sid": 280,
"location": 2,
"content": "我们下期再见",
"music": 0.0
}
]
}
*/
if (res.data != null) {
String vttData = "WEBVTT\n\n";
for (var item in res.data['body']) {
vttData += "${item['sid']}\n";
vttData +=
"${subtitleTimecode(item['from'])} --> ${subtitleTimecode(item['to'])}\n";
vttData += "${item['content']}\n\n";
}
subtitlesVtt.add({
'language': i['lan'],
'title': i['lan_doc'],
'text': vttData,
});
} else {
SmartDialog.showToast("字幕${i['lan_doc']}加载失败, ${res.data['message']}");
}
}
return subtitlesVtt;
}
}
86 changes: 85 additions & 1 deletion lib/plugin/pl_player/controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'dart:typed_data';

import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:flutter_volume_controller/flutter_volume_controller.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
Expand Down Expand Up @@ -95,6 +96,7 @@ class PlPlayerController {
int _heartDuration = 0;
bool _enableHeart = true;
bool _isFirstTime = true;
final RxList<Map<String,String>> _vttSubtitles = <Map<String,String>>[].obs;

Timer? _timer;
Timer? _timerForSeek;
Expand Down Expand Up @@ -147,7 +149,10 @@ class PlPlayerController {
Rx<bool> get mute => _mute;
Stream<bool> get onMuteChanged => _mute.stream;

/// [videoPlayerController] instace of Player
// 视频字幕
RxList<dynamic> get vttSubtitles => _vttSubtitles;

/// [videoPlayerController] instance of Player
Player? get videoPlayerController => _videoPlayerController;

/// [videoController] instace of Player
Expand Down Expand Up @@ -372,6 +377,7 @@ class PlPlayerController {
// 获取视频时长 00:00
_duration.value = duration ?? _videoPlayerController!.state.duration;
updateDurationSecond();
refreshSubtitles();
// 数据加载完成
dataStatus.status.value = DataStatus.loaded;

Expand Down Expand Up @@ -1077,4 +1083,82 @@ class PlPlayerController {
print(err);
}
}

void refreshSubtitles() async {
_vttSubtitles.clear();
Map res = await VideoHttp.subtitlesJson(
bvid: _bvid, cid: _cid);
if (!res["status"]) {
SmartDialog.showToast('查询字幕错误,${res["msg"]}');
}
if (res["data"].length == 0) {
return;
}
_vttSubtitles.value = await VideoHttp.vttSubtitles(res["data"]);
if (_vttSubtitles.isEmpty) {
// SmartDialog.showToast('字幕均加载失败');
return;
}
}

/// 选择字幕
void showSetSubtitleSheet() async {
showDialog(
context: Get.context!,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('选择字幕(测试)'),
content: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Wrap(
spacing: 8,
runSpacing: 2,
children: [
FilledButton.tonal(
onPressed: () async {
await removeSubtitle();
Get.back();
},
child: const Text("关闭字幕"),
),
for (final Map<String, String> i in _vttSubtitles) ...<Widget>[
FilledButton.tonal(
onPressed: () async {
await setSubtitle(i);
Get.back();
},
child: Text(i["title"]!),
),
]
],
);
}),
actions: <Widget>[
TextButton(
onPressed: () => Get.back(),
child: Text(
'取消',
style: TextStyle(color: Theme.of(context).colorScheme.outline),
),
),
],
);
},
);
}

removeSubtitle() {
_videoPlayerController?.setSubtitleTrack(SubtitleTrack.no());
}

// 设定字幕轨道
setSubtitle(Map<String,String> s) {
_videoPlayerController?.setSubtitleTrack(
SubtitleTrack.data(
s['text']!,
title: s['title']!,
language: s['language']!,
)
);
}
}
Loading

0 comments on commit 81da31c

Please sign in to comment.