Skip to content

Commit

Permalink
feat: 简单实现字幕功能
Browse files Browse the repository at this point in the history
  • Loading branch information
guozhigq committed Mar 24, 2024
1 parent 1f75a7e commit 955d8f5
Show file tree
Hide file tree
Showing 9 changed files with 318 additions and 62 deletions.
27 changes: 20 additions & 7 deletions lib/http/video.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import '../models/video/subTitile/result.dart';
import '../models/video_detail_res.dart';
import '../utils/recommend_filter.dart';
import '../utils/storage.dart';
import '../utils/subtitle.dart';
import '../utils/wbi_sign.dart';
import 'api.dart';
import 'init.dart';
Expand Down Expand Up @@ -482,13 +483,17 @@ class VideoHttp {
'cid': cid,
'bvid': bvid,
});
if (res.data['code'] == 0) {
return {
'status': true,
'data': SubTitlteModel.fromJson(res.data['data']),
};
} else {
return {'status': false, 'data': [], 'msg': res.data['msg']};
try {
if (res.data['code'] == 0) {
return {
'status': true,
'data': SubTitlteModel.fromJson(res.data['data']),
};
} else {
return {'status': false, 'data': [], 'msg': res.data['msg']};
}
} catch (err) {
print(err);
}
}

Expand All @@ -514,4 +519,12 @@ class VideoHttp {
return {'status': false, 'data': [], 'msg': err};
}
}

// 获取字幕内容
static Future<Map<String, dynamic>> getSubtitleContent(url) async {
var res = await Request().get('https:$url');
final String content = SubTitleUtils.convertToWebVTT(res.data['body']);
final List body = res.data['body'];
return {'content': content, 'body': body};
}
}
47 changes: 47 additions & 0 deletions lib/models/common/subtitle_type.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
enum SubtitleType {
// 中文(中国)
zhCN,
// 中文(自动翻译)
aizh,
// 英语(自动生成)
aien,
}

extension SubtitleTypeExtension on SubtitleType {
String get description {
switch (this) {
case SubtitleType.zhCN:
return '中文(中国)';
case SubtitleType.aizh:
return '中文(自动翻译)';
case SubtitleType.aien:
return '英语(自动生成)';
}
}
}

extension SubtitleIdExtension on SubtitleType {
String get id {
switch (this) {
case SubtitleType.zhCN:
return 'zh-CN';
case SubtitleType.aizh:
return 'ai-zh';
case SubtitleType.aien:
return 'ai-en';
}
}
}

extension SubtitleCodeExtension on SubtitleType {
int get code {
switch (this) {
case SubtitleType.zhCN:
return 1;
case SubtitleType.aizh:
return 2;
case SubtitleType.aien:
return 3;
}
}
}
21 changes: 20 additions & 1 deletion lib/models/video/subTitile/result.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import 'package:get/get.dart';
import '../../common/subtitle_type.dart';

class SubTitlteModel {
SubTitlteModel({
this.aid,
Expand Down Expand Up @@ -45,6 +48,10 @@ class SubTitlteItemModel {
this.type,
this.aiType,
this.aiStatus,
this.title,
this.code,
this.content,
this.body,
});

int? id;
Expand All @@ -55,16 +62,28 @@ class SubTitlteItemModel {
int? type;
int? aiType;
int? aiStatus;
String? title;
int? code;
String? content;
List? body;

factory SubTitlteItemModel.fromJson(Map<String, dynamic> json) =>
SubTitlteItemModel(
id: json["id"],
lan: json["lan"],
lan: json["lan"].replaceAll('-', ''),
lanDoc: json["lan_doc"],
isLock: json["is_lock"],
subtitleUrl: json["subtitle_url"],
type: json["type"],
aiType: json["ai_type"],
aiStatus: json["ai_status"],
title: json["lan_doc"],
code: SubtitleType.values
.firstWhereOrNull(
(element) => element.id.toString() == json["lan"])
?.index ??
-1,
content: '',
body: [],
);
}
42 changes: 26 additions & 16 deletions lib/pages/video/detail/controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import 'package:pilipala/utils/utils.dart';
import 'package:pilipala/utils/video_utils.dart';
import 'package:screen_brightness/screen_brightness.dart';

import '../../../http/index.dart';
import '../../../models/video/subTitile/content.dart';
import '../../../http/danmaku.dart';
import '../../../utils/id_utils.dart';
Expand Down Expand Up @@ -98,6 +97,7 @@ class VideoDetailController extends GetxController
RxList<SubTitileContentModel> subtitleContents =
<SubTitileContentModel>[].obs;
late bool enableRelatedVideo;
List subtitles = [];

@override
void onInit() {
Expand Down Expand Up @@ -256,6 +256,8 @@ class VideoDetailController extends GetxController

/// 开启自动全屏时,在player初始化完成后立即传入headerControl
plPlayerController.headerControl = headerControl;

plPlayerController.subtitles.value = subtitles;
}

// 视频链接
Expand Down Expand Up @@ -398,30 +400,38 @@ class VideoDetailController extends GetxController
var result = await VideoHttp.getSubtitle(bvid: bvid, cid: cid.value);
if (result['status']) {
if (result['data'].subtitles.isNotEmpty) {
SmartDialog.showToast('字幕加载中...');
var subtitle = result['data'].subtitles.first;
getSubtitleContent(subtitle.subtitleUrl);
subtitles = result['data'].subtitles;
if (subtitles.isNotEmpty) {
for (var i in subtitles) {
final Map<String, dynamic> res = await VideoHttp.getSubtitleContent(
i.subtitleUrl,
);
i.content = res['content'];
i.body = res['body'];
}
}
}
return result['data'];
} else {
SmartDialog.showToast(result['msg'].toString());
}
}

// 获取字幕内容
Future getSubtitleContent(String url) async {
var res = await Request().get('https:$url');
subtitleContents.value = res.data['body'].map<SubTitileContentModel>((e) {
return SubTitileContentModel.fromJson(e);
}).toList();
setSubtitleContent();
}
// Future getSubtitleContent(String url) async {
// var res = await Request().get('https:$url');
// subtitleContents.value = res.data['body'].map<SubTitileContentModel>((e) {
// return SubTitileContentModel.fromJson(e);
// }).toList();
// setSubtitleContent();
// }

setSubtitleContent() {
plPlayerController.subtitleContent.value = '';
if (subtitleContents.isNotEmpty) {
plPlayerController.subtitleContents = subtitleContents;
}
plPlayerController.subtitles.value = subtitles;
}

clearSubtitleContent() {
plPlayerController.subtitleContent.value = '';
plPlayerController.subtitles.value = [];
}

/// 发送弹幕
Expand Down
1 change: 1 addition & 0 deletions lib/pages/video/detail/view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
videoIntroController.isPaused = true;
plPlayerController!.removeStatusLister(playerListener);
plPlayerController!.pause();
vdCtr.clearSubtitleContent();
}
setState(() => isShowing = false);
super.didPushNext();
Expand Down
75 changes: 75 additions & 0 deletions lib/pages/video/detail/widgets/header_control.dart
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,56 @@ class _HeaderControlState extends State<HeaderControl> {
);
}

/// 选择字幕
void showSubtitleDialog() async {
int tempThemeValue = widget.controller!.subTitleCode.value;
int len = widget.videoDetailCtr!.subtitles.length;
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('选择字幕'),
contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 18),
content: StatefulBuilder(builder: (context, StateSetter setState) {
return len == 0
? const SizedBox(
height: 60,
child: Center(
child: Text('没有字幕'),
),
)
: Column(
mainAxisSize: MainAxisSize.min,
children: [
RadioListTile(
value: -1,
title: const Text('关闭弹幕'),
groupValue: tempThemeValue,
onChanged: (value) {
tempThemeValue = value!;
widget.controller?.toggleSubtitle(value);
Get.back();
},
),
...widget.videoDetailCtr!.subtitles
.map((e) => RadioListTile(
value: e.code,
title: Text(e.title),
groupValue: tempThemeValue,
onChanged: (value) {
tempThemeValue = value!;
widget.controller?.toggleSubtitle(value);
Get.back();
},
))
.toList(),
],
);
}),
);
});
}

/// 选择倍速
void showSetSpeedSheet() {
final double currentSpeed = widget.controller!.playbackSpeed;
Expand Down Expand Up @@ -1115,6 +1165,31 @@ class _HeaderControlState extends State<HeaderControl> {
),
SizedBox(width: buttonSpace),
],

/// 字幕
// SizedBox(
// width: 34,
// height: 34,
// child: IconButton(
// style: ButtonStyle(
// padding: MaterialStateProperty.all(EdgeInsets.zero),
// ),
// onPressed: () => showSubtitleDialog(),
// icon: const Icon(
// Icons.closed_caption_off,
// size: 22,
// ),
// ),
// ),
ComBtn(
icon: const Icon(
Icons.closed_caption_off,
size: 22,
color: Colors.white,
),
fuc: () => showSubtitleDialog(),
),
SizedBox(width: buttonSpace),
Obx(
() => SizedBox(
width: 45,
Expand Down
Loading

0 comments on commit 955d8f5

Please sign in to comment.