diff --git a/lib/common/widgets/video_card/video_card_h.dart b/lib/common/widgets/video_card/video_card_h.dart index 56a408da1..1c1c1c59a 100644 --- a/lib/common/widgets/video_card/video_card_h.dart +++ b/lib/common/widgets/video_card/video_card_h.dart @@ -43,6 +43,11 @@ class VideoCardH extends StatelessWidget { var typeOrNull = item.type; if (typeOrNull?.isNotEmpty == true) { type = typeOrNull!; + if (type == 'ketang') { + badge = '课堂'; + } else if (type == 'live_room') { + badge = '直播'; + } } if (item.isUnionVideo == 1) { badge = '合作'; @@ -71,7 +76,7 @@ class VideoCardH extends StatelessWidget { onTap ?? () async { if (type == 'ketang') { - SmartDialog.showToast('课堂视频暂不支持播放'); + PageUtils.viewPugv(seasonId: videoItem.aid); return; } else if (type == 'live_room') { if (videoItem case SearchVideoItemModel item) { @@ -173,13 +178,6 @@ class VideoCardH extends StatelessWidget { bottom: 6.0, type: PBadgeType.gray, ), - if (type != 'video') - PBadge( - text: type, - left: 6.0, - bottom: 6.0, - type: PBadgeType.primary, - ), ], ); }, diff --git a/lib/http/api.dart b/lib/http/api.dart index e0868f00e..0184a5c19 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -22,6 +22,8 @@ class Api { // https://api.bilibili.com/pgc/player/web/v2/playurl?cid=104236640&bvid=BV13t411n7ex static const String pgcUrl = '/pgc/player/web/v2/playurl'; + static const String pugvUrl = '/pugv/player/web/playurl'; + // 字幕 // aid, cid static const String playInfo = '/x/player/wbi/v2'; @@ -279,6 +281,8 @@ class Api { // 番剧/剧集明细 static const String pgcInfo = '/pgc/view/web/season'; + static const String pugvInfo = '/pugv/view/web/season'; + // https://api.bilibili.com/pgc/season/episode/web/info?ep_id=12345678 static const String episodeInfo = '/pgc/season/episode/web/info'; diff --git a/lib/http/search.dart b/lib/http/search.dart index a9c756b12..a49519e0c 100644 --- a/lib/http/search.dart +++ b/lib/http/search.dart @@ -193,6 +193,24 @@ class SearchHttp { } } + static Future> pugvInfo({ + dynamic seasonId, + dynamic epId, + }) async { + var res = await Request().get( + Api.pugvInfo, + queryParameters: { + 'season_id': ?seasonId, + 'ep_id': ?epId, + }, + ); + if (res.data['code'] == 0) { + return Success(PgcInfoModel.fromJson(res.data['data'])); + } else { + return Error(res.data['message']); + } + } + static Future episodeInfo({dynamic epId}) async { var res = await Request().get( Api.episodeInfo, diff --git a/lib/http/video.dart b/lib/http/video.dart index 333db8c06..ffcfc867a 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -7,6 +7,7 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/login.dart'; import 'package:PiliPlus/http/ua_type.dart'; import 'package:PiliPlus/models/common/account_type.dart'; +import 'package:PiliPlus/models/common/video/video_type.dart'; import 'package:PiliPlus/models/home/rcmd/result.dart'; import 'package:PiliPlus/models/model_hot_video_item.dart'; import 'package:PiliPlus/models/model_rec_video_item.dart'; @@ -33,7 +34,6 @@ import 'package:flutter/foundation.dart'; /// view层根据 status 判断渲染逻辑 class VideoHttp { - static bool p1080 = Pref.p1080; // static bool enableRcmdDynamic = Pref.enableRcmdDynamic; static RegExp zoneRegExp = RegExp(Pref.banWordForZone, caseSensitive: false); static bool enableFilter = zoneRegExp.pattern.isNotEmpty; @@ -193,7 +193,8 @@ class VideoHttp { int? qn, dynamic epid, dynamic seasonId, - bool? forcePgcApi, + required bool tryLook, + required VideoType videoType, }) async { final params = await WbiSign.makSign({ 'avid': ?avid, @@ -211,33 +212,28 @@ class VideoHttp { 'isGaiaAvoided': true, 'web_location': 1315873, // 免登录查看1080p - if (!Accounts.get(AccountType.video).isLogin && p1080) 'try_look': 1, + if (tryLook) 'try_look': 1, }); - late final usePgcApi = - forcePgcApi == true || Accounts.get(AccountType.video).isLogin; - try { var res = await Request().get( - epid != null && usePgcApi ? Api.pgcUrl : Api.ugcUrl, + videoType.api, queryParameters: params, ); if (res.data['code'] == 0) { late PlayUrlModel data; - if (epid != null && usePgcApi) { - data = PlayUrlModel.fromJson(res.data['result']['video_info']) - ..lastPlayTime = res - .data['result']['play_view_business_info']['user_status']['watch_progress']['current_watch_progress']; - } else { - data = PlayUrlModel.fromJson(res.data['data']); + switch (videoType) { + case VideoType.ugc || VideoType.pugv: + data = PlayUrlModel.fromJson(res.data['data']); + case VideoType.pgc: + data = PlayUrlModel.fromJson(res.data['result']['video_info']) + ..lastPlayTime = res + .data['result']?['play_view_business_info']?['user_status']?['watch_progress']?['current_watch_progress']; } - return { - 'status': true, - 'data': data, - }; + return {'status': true, 'data': data}; } else { - if (epid != null && !usePgcApi && forcePgcApi != true) { + if (epid != null && videoType == VideoType.ugc) { return videoUrl( avid: avid, bvid: bvid, @@ -245,7 +241,8 @@ class VideoHttp { qn: qn, epid: epid, seasonId: seasonId, - forcePgcApi: true, + tryLook: tryLook, + videoType: VideoType.pgc, ); } return { @@ -648,6 +645,7 @@ class VideoHttp { epid, seasonId, subType, + required VideoType videoType, }) async { await Request().post( Api.heartBeat, @@ -656,7 +654,7 @@ class VideoHttp { 'cid': cid, 'epid': ?epid, 'sid': ?seasonId, - if (epid != null) 'type': 4, + 'type': videoType.type, 'sub_type': ?subType, 'played_time': progress, 'csrf': Accounts.main.csrf, diff --git a/lib/models/common/episode_panel_type.dart b/lib/models/common/episode_panel_type.dart index 27375bd48..5b1ad381b 100644 --- a/lib/models/common/episode_panel_type.dart +++ b/lib/models/common/episode_panel_type.dart @@ -1,7 +1,7 @@ enum EpisodeType { part('分P'), season('合集'), - pgc('番剧'); + pgc('剧集'); final String title; const EpisodeType(this.title); diff --git a/lib/models/common/history_business_type.dart b/lib/models/common/history_business_type.dart index 3493d4adb..bcfa38d6b 100644 --- a/lib/models/common/history_business_type.dart +++ b/lib/models/common/history_business_type.dart @@ -10,11 +10,6 @@ enum HistoryBusinessType { // 文章 article('article'); - // 隐藏时长 - static const hiddenDurationType = {'live', 'article-list', 'article'}; - // 右上 - static const showBadge = {'pgc', 'article-list', 'article'}; - final String type; const HistoryBusinessType(this.type); } diff --git a/lib/models/common/video/video_type.dart b/lib/models/common/video/video_type.dart new file mode 100644 index 000000000..a85c03a89 --- /dev/null +++ b/lib/models/common/video/video_type.dart @@ -0,0 +1,26 @@ +import 'package:PiliPlus/http/api.dart'; + +enum VideoType { + ugc( + type: 3, + api: Api.ugcUrl, + ), + pgc( + type: 4, + api: Api.pgcUrl, + ), + pugv( + type: 10, + replyType: 33, + api: Api.pugvUrl, + ); + + final int type; + final String api; + final int replyType; + const VideoType({ + required this.api, + required this.type, + this.replyType = 1, + }); +} diff --git a/lib/models/video/play/url.dart b/lib/models/video/play/url.dart index 07b1b49a7..879bd5432 100644 --- a/lib/models/video/play/url.dart +++ b/lib/models/video/play/url.dart @@ -170,23 +170,25 @@ abstract class BaseItem { BaseItem.fromJson(Map json) { id = json['id']; - baseUrl = json['baseUrl']; - final backupUrls = (json['backupUrl'] as List?)?.cast() ?? []; + baseUrl = json['baseUrl'] ?? json['base_url']; + final backupUrls = + ((json['backupUrl'] ?? json['backup_url']) as List?)?.cast() ?? + []; backupUrl = backupUrls.isNotEmpty ? backupUrls.firstWhere( (i) => !_isMCDNorPCDN(i), orElse: () => backupUrls.first, ) : null; - bandWidth = json['bandWidth']; + bandWidth = json['bandWidth'] ?? json['bandwidth']; mimeType = json['mime_type']; codecs = json['codecs']; width = json['width']; height = json['height']; - frameRate = json['frameRate']; + frameRate = json['frameRate'] ?? json['frame_rate']; sar = json['sar']; - startWithSap = json['startWithSap']; - segmentBase = json['segmentBase']; + startWithSap = json['startWithSap'] ?? json['start_with_sap']; + segmentBase = json['segmentBase'] ?? json['segment_base']; codecid = json['codecid']; } } diff --git a/lib/models_new/pgc/pgc_info_model/episode.dart b/lib/models_new/pgc/pgc_info_model/episode.dart index e70f9be71..0a1ecab12 100644 --- a/lib/models_new/pgc/pgc_info_model/episode.dart +++ b/lib/models_new/pgc/pgc_info_model/episode.dart @@ -9,7 +9,7 @@ class EpisodeItem extends BaseEpisodeItem { BadgeInfo? badgeInfo; int? badgeType; Dimension? dimension; - int? duration; + int? duration; // pgc: millisec , pugv: sec bool? enableVt; String? from; bool? isViewHide; @@ -17,7 +17,7 @@ class EpisodeItem extends BaseEpisodeItem { String? longTitle; int? pubTime; int? pv; - String? releaseDate; + // String? releaseDate; Rights? rights; int? sectionType; String? shareCopy; @@ -29,6 +29,7 @@ class EpisodeItem extends BaseEpisodeItem { int? status; String? subtitle; String? vid; + int? play; EpisodeItem({ super.aid, @@ -49,7 +50,7 @@ class EpisodeItem extends BaseEpisodeItem { this.longTitle, this.pubTime, this.pv, - this.releaseDate, + // this.releaseDate, this.rights, this.sectionType, this.shareCopy, @@ -62,6 +63,7 @@ class EpisodeItem extends BaseEpisodeItem { this.subtitle, super.title, this.vid, + this.play, }); factory EpisodeItem.fromJson(Map json) => EpisodeItem( @@ -85,9 +87,9 @@ class EpisodeItem extends BaseEpisodeItem { isViewHide: json['is_view_hide'] as bool?, link: json['link'] as String?, longTitle: json['long_title'] as String?, - pubTime: json['pub_time'] as int?, + pubTime: json['pub_time'] ?? json['release_date'], pv: json['pv'] as int?, - releaseDate: json['release_date'] as String?, + // releaseDate: json['release_date'] as String?, rights: json['rights'] == null ? null : Rights.fromJson(json['rights'] as Map), @@ -104,5 +106,6 @@ class EpisodeItem extends BaseEpisodeItem { subtitle: json['subtitle'] as String?, title: json['title'] as String?, vid: json['vid'] as String?, + play: json['play'] as int?, ); } diff --git a/lib/pages/article/controller.dart b/lib/pages/article/controller.dart index 6caac6d28..997dd8fde 100644 --- a/lib/pages/article/controller.dart +++ b/lib/pages/article/controller.dart @@ -177,15 +177,13 @@ class ArticleController extends CommonDynController { } @override - Future> customGetData() { - return ReplyGrpc.mainList( - type: commentType, - oid: commentId, - mode: mode.value, - cursorNext: cursorNext, - offset: paginationReply?.nextOffset, - ); - } + Future> customGetData() => ReplyGrpc.mainList( + type: commentType, + oid: commentId, + mode: mode.value, + cursorNext: cursorNext, + offset: paginationReply?.nextOffset, + ); Future onFav() async { final favorite = stats.value?.favorite; diff --git a/lib/pages/episode_panel/view.dart b/lib/pages/episode_panel/view.dart index c0d581ea2..88ba8de94 100644 --- a/lib/pages/episode_panel/view.dart +++ b/lib/pages/episode_panel/view.dart @@ -367,7 +367,12 @@ class _EpisodePanelState extends CommonSlidePageState { bvid = item.bvid; title = item.showTitle ?? item.title!; cover = item.cover; - duration = item.duration == null ? null : item.duration! ~/ 1000; + if (item.from == 'pugv') { + duration = item.duration; + view = item.play; + } else { + duration = item.duration == null ? null : item.duration! ~/ 1000; + } pubdate = item.pubTime; break; } diff --git a/lib/pages/history/widgets/item.dart b/lib/pages/history/widgets/item.dart index 50e3c2e44..13516037f 100644 --- a/lib/pages/history/widgets/item.dart +++ b/lib/pages/history/widgets/item.dart @@ -38,6 +38,7 @@ class HistoryItem extends StatelessWidget { @override Widget build(BuildContext context) { final theme = Theme.of(context); + final hasDuration = item.duration != null && item.duration != 0; int aid = item.history.oid!; String bvid = item.history.bvid ?? IdUtils.av2bv(aid); return Material( @@ -68,6 +69,10 @@ class HistoryItem extends StatelessWidget { } } else if (item.history.business == 'pgc') { PageUtils.viewPgc(epId: item.history.epid); + } else if (item.history.business == 'cheese') { + if (item.uri?.isNotEmpty == true) { + PageUtils.viewPgcFromUri(item.uri!, isPgc: false); + } } else { int? cid = item.history.cid ?? @@ -128,8 +133,7 @@ class HistoryItem extends StatelessWidget { width: maxWidth, height: maxHeight, ), - if (!HistoryBusinessType.hiddenDurationType - .contains(item.history.business)) + if (hasDuration) PBadge( text: item.progress == -1 ? '已看完' @@ -139,11 +143,7 @@ class HistoryItem extends StatelessWidget { type: PBadgeType.gray, ), // 右上角 - if (HistoryBusinessType.showBadge.contains( - item.history.business, - ) || - item.history.business == - HistoryBusinessType.live.type) + if (item.badge?.isNotEmpty == true) PBadge( text: item.badge, top: 6.0, @@ -155,8 +155,7 @@ class HistoryItem extends StatelessWidget { ? PBadgeType.gray : PBadgeType.primary, ), - if (item.duration != null && - item.duration != 0 && + if (hasDuration && item.progress != null && item.progress != 0) Positioned( diff --git a/lib/pages/later/widgets/video_card_h_later.dart b/lib/pages/later/widgets/video_card_h_later.dart index 4450928fd..020329f33 100644 --- a/lib/pages/later/widgets/video_card_h_later.dart +++ b/lib/pages/later/widgets/video_card_h_later.dart @@ -51,7 +51,7 @@ class VideoCardHLater extends StatelessWidget { onTap ?? () async { if (type == 'ketang') { - SmartDialog.showToast('课堂视频暂不支持播放'); + PageUtils.viewPugv(seasonId: videoItem.aid); return; } if (videoItem.isPgc == true) { @@ -117,6 +117,12 @@ class VideoCardHLater extends StatelessWidget { text: videoItem.pgcLabel, top: 6.0, right: 6.0, + ) + else if (type == 'ketang') + const PBadge( + text: '课堂', + top: 6.0, + right: 6.0, ), if (progress != null && progress != 0) ...[ PBadge( @@ -146,13 +152,6 @@ class VideoCardHLater extends StatelessWidget { bottom: 6.0, type: PBadgeType.gray, ), - if (type != 'video') - PBadge( - text: type, - left: 6.0, - bottom: 6.0, - type: PBadgeType.primary, - ), ], ); }, diff --git a/lib/pages/save_panel/view.dart b/lib/pages/save_panel/view.dart index 11a5d332c..8859e4012 100644 --- a/lib/pages/save_panel/view.dart +++ b/lib/pages/save_panel/view.dart @@ -5,7 +5,7 @@ import 'package:PiliPlus/common/widgets/button/icon_button.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart' show ReplyInfo; -import 'package:PiliPlus/models/common/search_type.dart'; +import 'package:PiliPlus/models/common/video/video_type.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/pages/dynamics/widgets/dynamic_panel.dart'; import 'package:PiliPlus/pages/video/introduction/pgc/controller.dart'; @@ -90,7 +90,8 @@ class _SavePanelState extends State { if (currentRoute.startsWith('/video')) { try { final heroTag = Get.arguments['heroTag']; - if (Get.arguments['videoType'] == SearchType.media_bangumi) { + final videoType = Get.arguments['videoType']; + if (videoType == VideoType.pgc || videoType == VideoType.pugv) { final ctr = Get.find(tag: heroTag); final pgcItem = ctr.pgcItem; final episode = pgcItem.episodes!.firstWhere( @@ -99,7 +100,7 @@ class _SavePanelState extends State { cover = episode.cover; title = episode.shareCopy ?? - '${pgcItem.title} ${episode.showTitle ?? episode.title}'; + '${pgcItem.title} ${episode.showTitle ?? episode.longTitle ?? ''}'; pubdate = episode.pubTime; uname = pgcItem.upInfo?.uname; } else { diff --git a/lib/pages/setting/models/video_settings.dart b/lib/pages/setting/models/video_settings.dart index d441e8148..0e61d8a4a 100644 --- a/lib/pages/setting/models/video_settings.dart +++ b/lib/pages/setting/models/video_settings.dart @@ -1,6 +1,5 @@ import 'dart:io'; -import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/models/common/settings_type.dart'; import 'package:PiliPlus/models/common/video/audio_quality.dart'; import 'package:PiliPlus/models/common/video/cdn_type.dart'; @@ -33,9 +32,6 @@ List get videoSettings => [ leading: const Icon(Icons.hd_outlined), setKey: SettingBoxKey.p1080, defaultVal: true, - onChanged: (value) { - VideoHttp.p1080 = value; - }, ), SettingsModel( settingsType: SettingsType.normal, diff --git a/lib/pages/setting/widgets/select_dialog.dart b/lib/pages/setting/widgets/select_dialog.dart index fb74c8a62..20abfcb19 100644 --- a/lib/pages/setting/widgets/select_dialog.dart +++ b/lib/pages/setting/widgets/select_dialog.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:PiliPlus/http/constants.dart'; import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/models/common/video/cdn_type.dart'; +import 'package:PiliPlus/models/common/video/video_type.dart'; import 'package:PiliPlus/models/video/play/url.dart'; import 'package:PiliPlus/utils/storage_pref.dart'; import 'package:PiliPlus/utils/video_utils.dart'; @@ -102,6 +103,8 @@ class _CdnSelectDialogState extends State { final result = await VideoHttp.videoUrl( cid: 196018899, bvid: 'BV1fK4y1t7hj', + tryLook: false, + videoType: VideoType.ugc, ); if (!result['status']) throw Exception('无法获取视频流'); return result['data'].dash.video.first; diff --git a/lib/pages/subscription_detail/widget/sub_video_card.dart b/lib/pages/subscription_detail/widget/sub_video_card.dart index 1360f73f5..a68301e08 100644 --- a/lib/pages/subscription_detail/widget/sub_video_card.dart +++ b/lib/pages/subscription_detail/widget/sub_video_card.dart @@ -5,8 +5,8 @@ import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/stat/stat.dart'; import 'package:PiliPlus/http/search.dart'; import 'package:PiliPlus/models/common/badge_type.dart'; -import 'package:PiliPlus/models/common/search_type.dart'; import 'package:PiliPlus/models/common/stat_type.dart'; +import 'package:PiliPlus/models/common/video/video_type.dart'; import 'package:PiliPlus/models_new/sub/sub_detail/media.dart'; import 'package:PiliPlus/utils/date_util.dart'; import 'package:PiliPlus/utils/duration_util.dart'; @@ -38,7 +38,7 @@ class SubVideoCardH extends StatelessWidget { arguments: { 'videoItem': videoItem, 'heroTag': Utils.makeHeroTag(videoItem.id), - 'videoType': SearchType.video, + 'videoType': VideoType.ugc, }, ); } diff --git a/lib/pages/video/controller.dart b/lib/pages/video/controller.dart index cf20fe4c9..e6253f2e6 100644 --- a/lib/pages/video/controller.dart +++ b/lib/pages/video/controller.dart @@ -11,7 +11,7 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/user.dart'; import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/main.dart'; -import 'package:PiliPlus/models/common/search_type.dart'; +import 'package:PiliPlus/models/common/account_type.dart'; import 'package:PiliPlus/models/common/sponsor_block/action_type.dart'; import 'package:PiliPlus/models/common/sponsor_block/post_segment_model.dart'; import 'package:PiliPlus/models/common/sponsor_block/segment_model.dart'; @@ -22,6 +22,7 @@ import 'package:PiliPlus/models/common/video/source_type.dart'; import 'package:PiliPlus/models/common/video/subtitle_pref_type.dart'; import 'package:PiliPlus/models/common/video/video_decode_type.dart'; import 'package:PiliPlus/models/common/video/video_quality.dart'; +import 'package:PiliPlus/models/common/video/video_type.dart'; import 'package:PiliPlus/models/video/play/url.dart'; import 'package:PiliPlus/models_new/media_list/data.dart'; import 'package:PiliPlus/models_new/media_list/media_list.dart'; @@ -42,6 +43,7 @@ import 'package:PiliPlus/plugin/pl_player/controller.dart'; import 'package:PiliPlus/plugin/pl_player/models/data_source.dart'; import 'package:PiliPlus/plugin/pl_player/models/heart_beat_type.dart'; import 'package:PiliPlus/plugin/pl_player/models/play_status.dart'; +import 'package:PiliPlus/utils/accounts.dart'; import 'package:PiliPlus/utils/duration_util.dart'; import 'package:PiliPlus/utils/id_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; @@ -67,12 +69,24 @@ class VideoDetailController extends GetxController with GetTickerProviderStateMixin { /// 路由传参 String bvid = Get.parameters['bvid']!; + late int aid = IdUtils.bv2av(bvid); final RxInt cid = int.parse(Get.parameters['cid']!).obs; final heroTag = Get.arguments['heroTag']; final RxString cover = ''.obs; // 视频类型 默认投稿视频 - final isUgc = - (Get.arguments['videoType'] ?? SearchType.video) == SearchType.video; + final pgcApi = Get.arguments['pgcApi']; + final VideoType videoType = Get.arguments['videoType'] ?? VideoType.ugc; + late final isUgc = videoType == VideoType.ugc; + VideoType get _actualVideoType { + if (videoType == VideoType.pgc) { + if (!Accounts.get(AccountType.video).isLogin) { + return VideoType.ugc; + } + } else if (pgcApi == true) { + return VideoType.pgc; + } + return videoType; + } /// tabs相关配置 late TabController tabCtr; @@ -90,8 +104,6 @@ class VideoDetailController extends GetxController // 封面图的展示 final RxBool isShowCover = true.obs; - final RxInt oid = 0.obs; - final scaffoldKey = GlobalKey(); final childKey = GlobalKey(); @@ -287,7 +299,6 @@ class VideoDetailController extends GetxController // 预设的解码格式 cacheDecode = Pref.defaultDecode; cacheSecondDecode = Pref.secondDecode; - oid.value = IdUtils.bv2av(Get.parameters['bvid']!); } Future getMediaList({ @@ -1125,6 +1136,7 @@ class VideoDetailController extends GetxController epid: isUgc ? null : epId, seasonId: isUgc ? null : seasonId, subType: isUgc ? null : subType, + videoType: videoType, callback: () { if (videoState.value is! Success) { videoState.value = const Success(null); @@ -1169,12 +1181,14 @@ class VideoDetailController extends GetxController : Pref.defaultAudioQaCellular; }); } + var result = await VideoHttp.videoUrl( cid: cid.value, bvid: bvid, epid: epId, seasonId: seasonId, - forcePgcApi: Get.arguments?['pgcApi'] ?? false, + tryLook: plPlayerController.tryLook, + videoType: _actualVideoType, ); if (result['status']) { @@ -1330,7 +1344,10 @@ class VideoDetailController extends GetxController Get.arguments['progress'] = null; } else { this.defaultST = - defaultST ?? Duration(milliseconds: data.lastPlayTime!); + defaultST ?? + (data.lastPlayTime == null + ? Duration.zero + : Duration(milliseconds: data.lastPlayTime!)); } if (autoPlay.value) { isShowCover.value = false; @@ -1345,13 +1362,13 @@ class VideoDetailController extends GetxController if (plPlayerController.isFullScreen.value) { plPlayerController.toggleFullScreen(false); } - if (result['code'] == -404) { + final code = result['code']; + if (code == -404) { SmartDialog.showToast('视频不存在或已被删除'); - } - if (result['code'] == 87008) { + } else if (code == 87008) { SmartDialog.showToast("当前视频可能是专属视频,可能需包月充电观看(${result['msg']})"); } else { - SmartDialog.showToast("错误(${result['code']}):${result['msg']}"); + SmartDialog.showToast("错误($code): ${result['msg']}"); } } isQuerying = false; @@ -1587,6 +1604,7 @@ class VideoDetailController extends GetxController epid: isUgc ? null : epId, seasonId: isUgc ? null : seasonId, subType: isUgc ? null : subType, + videoType: videoType, ); } catch (_) {} } @@ -1680,7 +1698,7 @@ class VideoDetailController extends GetxController ? Theme( data: MyApp.darkThemeData!, child: NoteListPage( - oid: oid.value, + oid: aid, enableSlide: false, heroTag: heroTag, isStein: graphVersion != null, @@ -1688,7 +1706,7 @@ class VideoDetailController extends GetxController ), ) : NoteListPage( - oid: oid.value, + oid: aid, enableSlide: false, heroTag: heroTag, isStein: graphVersion != null, @@ -1700,7 +1718,7 @@ class VideoDetailController extends GetxController childKey.currentState?.showBottomSheet( backgroundColor: Colors.transparent, (context) => NoteListPage( - oid: oid.value, + oid: aid, heroTag: heroTag, isStein: graphVersion != null, title: title, diff --git a/lib/pages/video/introduction/pgc/controller.dart b/lib/pages/video/introduction/pgc/controller.dart index 51d0e28e6..b735091e3 100644 --- a/lib/pages/video/introduction/pgc/controller.dart +++ b/lib/pages/video/introduction/pgc/controller.dart @@ -7,6 +7,7 @@ import 'package:PiliPlus/grpc/view.dart'; import 'package:PiliPlus/http/constants.dart'; import 'package:PiliPlus/http/search.dart'; import 'package:PiliPlus/http/video.dart'; +import 'package:PiliPlus/models/common/video/video_type.dart'; import 'package:PiliPlus/models/pgc_lcf.dart'; import 'package:PiliPlus/models_new/pgc/pgc_info_model/episode.dart'; import 'package:PiliPlus/models_new/pgc/pgc_info_model/result.dart'; @@ -43,20 +44,23 @@ class PgcIntroController extends CommonIntroController { ? '追番' : '追剧'; + final isPgc = Get.arguments['videoType'] == VideoType.pgc; final PgcInfoModel pgcItem = Get.arguments['pgcItem']; @override void onInit() { super.onInit(); - if (accountService.isLogin.value) { - if (seasonId != null) { - queryIsFollowed(); - } - if (epId != null) { - queryPgcLikeCoinFav(); + if (isPgc) { + if (accountService.isLogin.value) { + if (seasonId != null) { + queryIsFollowed(); + } + if (epId != null) { + queryPgcLikeCoinFav(); + } } + queryVideoTags(); } - queryVideoTags(); } // 获取点赞/投币/收藏状态 @@ -226,7 +230,9 @@ class PgcIntroController extends CommonIntroController { EpisodeItem item = pgcItem.episodes!.firstWhere( (item) => item.epId == epId, ); - final title = '${item.title!} ${item.showTitle}'; + final title = + item.shareCopy ?? + '${pgcItem.title} ${item.showTitle ?? item.longTitle}'; PageUtils.pmShare( context, content: { @@ -263,7 +269,7 @@ class PgcIntroController extends CommonIntroController { // 修改分P或番剧分集 Future onChangeEpisode(BaseEpisodeItem episode) async { try { - final int epId = episode.epId!; + final int epId = episode.epId ?? episode.id!; final String bvid = episode.bvid ?? this.bvid; final int aid = episode.aid ?? IdUtils.bv2av(bvid); final int? cid = @@ -284,6 +290,7 @@ class PgcIntroController extends CommonIntroController { ..onReset() ..epId = epId ..bvid = bvid + ..aid = aid ..cid.value = cid ..queryVideoUrl(); if (cover != null && cover.isNotEmpty) { @@ -299,7 +306,7 @@ class PgcIntroController extends CommonIntroController { } catch (_) {} } - if (accountService.isLogin.value) { + if (isPgc && accountService.isLogin.value) { queryPgcLikeCoinFav(); } diff --git a/lib/pages/video/introduction/pgc/view.dart b/lib/pages/video/introduction/pgc/view.dart index f9d53b79f..892e1de07 100644 --- a/lib/pages/video/introduction/pgc/view.dart +++ b/lib/pages/video/introduction/pgc/view.dart @@ -122,130 +122,158 @@ class _PgcIntroPageState extends State ], ), Expanded( - child: GestureDetector( - onTap: () => widget.showIntroDetail( - item, - pgcIntroController.videoTags.value, - ), - behavior: HitTestBehavior.opaque, - child: SizedBox( - height: isLandscape ? 115 : 115 / 0.75, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 20, - children: [ - Expanded( - child: Text( - item.title!, - style: const TextStyle( - fontSize: 16, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, + child: !pgcIntroController.isPgc + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.title!, + style: const TextStyle(fontSize: 16), + ), + const SizedBox(height: 5), + if (item.subtitle?.isNotEmpty == true) + Text( + item.subtitle!, + style: TextStyle( + fontSize: 13, + color: theme.colorScheme.onSurfaceVariant, ), ), - Obx( - () { - final isFollowed = - pgcIntroController.isFollowed.value; - final followStatus = - pgcIntroController.followStatus.value; - return FilledButton.tonal( - style: FilledButton.styleFrom( - tapTargetSize: - MaterialTapTargetSize.shrinkWrap, - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 10, + ], + ) + : GestureDetector( + onTap: () => widget.showIntroDetail( + item, + pgcIntroController.videoTags.value, + ), + behavior: HitTestBehavior.opaque, + child: SizedBox( + height: isLandscape ? 115 : 115 / 0.75, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 20, + children: [ + Expanded( + child: Text( + item.title!, + style: const TextStyle( + fontSize: 16, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - visualDensity: VisualDensity.compact, - foregroundColor: isFollowed - ? theme.colorScheme.outline - : null, - backgroundColor: isFollowed - ? theme.colorScheme.onInverseSurface - : null, ), - onPressed: followStatus == -1 - ? null - : () { - if (isFollowed) { - showPgcFollowDialog( - context: context, - type: - pgcIntroController.pgcType, - followStatus: followStatus, - onUpdateStatus: (followStatus) { - if (followStatus == -1) { - pgcIntroController.pgcDel(); - } else { - pgcIntroController - .pgcUpdate( + Obx( + () { + final isFollowed = + pgcIntroController.isFollowed.value; + final followStatus = pgcIntroController + .followStatus + .value; + return FilledButton.tonal( + style: FilledButton.styleFrom( + tapTargetSize: MaterialTapTargetSize + .shrinkWrap, + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 10, + ), + visualDensity: + VisualDensity.compact, + foregroundColor: isFollowed + ? theme.colorScheme.outline + : null, + backgroundColor: isFollowed + ? theme + .colorScheme + .onInverseSurface + : null, + ), + onPressed: followStatus == -1 + ? null + : () { + if (isFollowed) { + showPgcFollowDialog( + context: context, + type: pgcIntroController + .pgcType, + followStatus: followStatus, - ); + onUpdateStatus: + (followStatus) { + if (followStatus == + -1) { + pgcIntroController + .pgcDel(); + } else { + pgcIntroController + .pgcUpdate( + followStatus, + ); + } + }, + ); + } else { + pgcIntroController.pgcAdd(); } }, - ); - } else { - pgcIntroController.pgcAdd(); - } - }, - child: Text( - isFollowed - ? '已${pgcIntroController.pgcType}' - : pgcIntroController.pgcType, + child: Text( + isFollowed + ? '已${pgcIntroController.pgcType}' + : pgcIntroController.pgcType, + ), + ); + }, ), - ); - }, - ), - ], - ), - Row( - spacing: 6, - children: [ - StatWidget( - type: StatType.play, - value: item.stat!.view, - ), - StatWidget( - type: StatType.danmaku, - value: item.stat!.danmaku, - ), - if (isLandscape) ...[ - areasAndPubTime(theme, item), - newEpDesc(theme, item), + ], + ), + Row( + spacing: 6, + children: [ + StatWidget( + type: StatType.play, + value: item.stat!.view, + ), + StatWidget( + type: StatType.danmaku, + value: item.stat!.danmaku, + ), + if (isLandscape) ...[ + areasAndPubTime(theme, item), + newEpDesc(theme, item), + ], + ], + ), + SizedBox(height: isLandscape ? 2 : 6), + if (!isLandscape) ...[ + areasAndPubTime(theme, item), + newEpDesc(theme, item), + ], + const Spacer(), + Text( + '简介:${item.evaluate}', + maxLines: isLandscape ? 2 : 3, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 13, + color: theme.colorScheme.outline, + ), + ), ], - ], - ), - SizedBox(height: isLandscape ? 2 : 6), - if (!isLandscape) ...[ - areasAndPubTime(theme, item), - newEpDesc(theme, item), - ], - const Spacer(), - Text( - '简介:${item.evaluate!}', - maxLines: isLandscape ? 2 : 3, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 13, - color: theme.colorScheme.outline, ), ), - ], - ), - ), - ), + ), ), ], ), const SizedBox(height: 6), // 点赞收藏转发 布局样式2 - actionGrid(theme, item, pgcIntroController), + if (pgcIntroController.isPgc) + actionGrid(theme, item, pgcIntroController), // 番剧分p if (item.episodes!.isNotEmpty) ...[ PgcPanel( diff --git a/lib/pages/video/introduction/pgc/widgets/pgc_panel.dart b/lib/pages/video/introduction/pgc/widgets/pgc_panel.dart index e8bb529fd..c1077020f 100644 --- a/lib/pages/video/introduction/pgc/widgets/pgc_panel.dart +++ b/lib/pages/video/introduction/pgc/widgets/pgc_panel.dart @@ -87,6 +87,8 @@ class _PgcPanelState extends State { @override Widget build(BuildContext context) { final theme = Theme.of(context); + final currEpisode = widget.pages[currentIndex]; + final isPugv = currEpisode.from == 'pugv'; return Column( children: [ Padding( @@ -97,7 +99,7 @@ class _PgcPanelState extends State { const Text('合集 '), Expanded( child: Text( - ' 正在播放:${widget.pages[currentIndex].longTitle}', + ' 正在播放:${currEpisode.longTitle ?? currEpisode.title}', overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 12, @@ -116,14 +118,14 @@ class _PgcPanelState extends State { null, null, widget.pages, - widget.pages[currentIndex].bvid, - widget.pages[currentIndex].aid, + videoDetailCtr.bvid, + videoDetailCtr.aid, cid, ), child: Text( widget.newEp?.desc?.contains('连载') == true ? '连载中,更新至${Utils.isStringNumeric(widget.newEp!.title!) ? '第${widget.newEp!.title}话' : '${widget.newEp!.title}'}' - : widget.newEp?.desc ?? '', + : widget.newEp?.desc ?? '查看全部', style: const TextStyle(fontSize: 13), ), ), @@ -139,103 +141,108 @@ class _PgcPanelState extends State { itemCount: widget.pages.length, itemExtent: 150, itemBuilder: (BuildContext context, int index) { - final item = widget.pages[index]; - return Container( - width: 150, - margin: EdgeInsets.only( - right: index == widget.pages.length - 1 ? 0 : 10, - ), - child: Material( - color: theme.colorScheme.onInverseSurface, - borderRadius: const BorderRadius.all(Radius.circular(6)), - child: InkWell( - borderRadius: const BorderRadius.all(Radius.circular(6)), - onTap: () { - if (item.badge == '会员' && vipStatus != 1) { - SmartDialog.showToast('需要大会员'); - } - widget.onChangeEpisode(item); - }, - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 8, - horizontal: 10, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - if (index == currentIndex) ...[ - Image.asset( - 'assets/images/live.png', - color: theme.colorScheme.primary, - height: 12, - semanticLabel: "正在播放:", - ), - const SizedBox(width: 6), - ], - Expanded( - child: Text( - item.title ?? '第${index + 1}话', - maxLines: - (item.longTitle != null && - item.longTitle != '') - ? 1 - : 2, - style: TextStyle( - fontSize: 13, - color: index == currentIndex - ? theme.colorScheme.primary - : theme.colorScheme.onSurface, - ), - ), - ), - const SizedBox(width: 2), - if (item.badge != null) ...[ - const Spacer(), - if (item.badge == '会员') - Image.asset( - 'assets/images/big-vip.png', - height: 16, - semanticLabel: "大会员", - ) - else - Text( - item.badge!, - style: TextStyle( - fontSize: 11, - color: theme.colorScheme.primary, - ), - ), - ], - ], - ), - if (item.longTitle != null && - item.longTitle != '') ...[ - const SizedBox(height: 3), - Text( - item.longTitle!, - maxLines: 1, - style: TextStyle( - fontSize: 13, - color: index == currentIndex - ? theme.colorScheme.primary - : theme.colorScheme.onSurface, - ), - overflow: TextOverflow.ellipsis, - ), - ], - ], - ), - ), - ), - ), - ); + return _buildItem(theme, isPugv, index); }, ), ), ], ); } + + Widget _buildItem(ThemeData theme, bool isPugv, int index) { + final item = widget.pages[index]; + final hasLongTitle = item.longTitle?.isNotEmpty == true; + final color = index == currentIndex + ? theme.colorScheme.primary + : theme.colorScheme.onSurface; + return Container( + width: 150, + height: 60, + margin: EdgeInsets.only( + right: index == widget.pages.length - 1 ? 0 : 10, + ), + child: Material( + color: theme.colorScheme.onInverseSurface, + borderRadius: const BorderRadius.all(Radius.circular(6)), + child: InkWell( + borderRadius: const BorderRadius.all(Radius.circular(6)), + onTap: () { + if (item.badge == '会员' && vipStatus != 1) { + SmartDialog.showToast('需要大会员'); + } + widget.onChangeEpisode(item); + }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 10), + child: Column( + spacing: 3, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text.rich( + maxLines: hasLongTitle ? 1 : 2, + TextSpan( + children: [ + if (index == currentIndex) + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Padding( + padding: const EdgeInsets.only(right: 6), + child: Image.asset( + 'assets/images/live.png', + color: theme.colorScheme.primary, + height: 12, + semanticLabel: "正在播放:", + ), + ), + ), + TextSpan( + text: item.title ?? '第${index + 1}话', + style: TextStyle( + fontSize: 13, + color: color, + ), + ), + ], + ), + ), + ), + if (item.badge?.isNotEmpty == true) ...[ + const SizedBox(width: 2), + if (item.badge == '会员') + Image.asset( + 'assets/images/big-vip.png', + height: 16, + semanticLabel: "大会员", + ) + else + Text( + item.badge!, + style: TextStyle( + fontSize: 11, + color: theme.colorScheme.primary, + ), + ), + ], + ], + ), + if (hasLongTitle) + Text( + isPugv ? item.title! : item.longTitle!, + maxLines: 1, + style: TextStyle( + fontSize: 13, + color: color, + ), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ), + ), + ); + } } diff --git a/lib/pages/video/introduction/ugc/controller.dart b/lib/pages/video/introduction/ugc/controller.dart index 5ab1c719c..3b074f7c4 100644 --- a/lib/pages/video/introduction/ugc/controller.dart +++ b/lib/pages/video/introduction/ugc/controller.dart @@ -499,7 +499,7 @@ class UgcIntroController extends CommonIntroController with ReloadMixin { ..updateMediaListHistory(aid) ..onReset(isStein) ..bvid = bvid - ..oid.value = aid + ..aid = aid ..cid.value = cid ..queryVideoUrl(); diff --git a/lib/pages/video/introduction/ugc/view.dart b/lib/pages/video/introduction/ugc/view.dart index b20852bc7..672b67a5e 100644 --- a/lib/pages/video/introduction/ugc/view.dart +++ b/lib/pages/video/introduction/ugc/view.dart @@ -133,7 +133,7 @@ class _UgcIntroPanelState extends State widget.onShowMemberPage(mid); } else { Get.toNamed( - '/member?mid=$mid&from_view_aid=${videoDetailCtr.oid.value}', + '/member?mid=$mid&from_view_aid=${videoDetailCtr.aid}', ); } } @@ -722,7 +722,7 @@ class _UgcIntroPanelState extends State widget.onShowMemberPage(ownerMid); } else { Get.toNamed( - '/member?mid=${item.mid}&from_view_aid=${videoDetailCtr.oid.value}', + '/member?mid=${item.mid}&from_view_aid=${videoDetailCtr.aid}', ); } }, diff --git a/lib/pages/video/member/view.dart b/lib/pages/video/member/view.dart index 49090ef7b..6b8971729 100644 --- a/lib/pages/video/member/view.dart +++ b/lib/pages/video/member/view.dart @@ -52,7 +52,7 @@ class _HorizontalMemberPageState extends State { _controller = Get.put( HorizontalMemberPageController( mid: widget.mid, - currAid: widget.videoDetailController.oid.value.toString(), + currAid: widget.videoDetailController.aid.toString(), ), tag: widget.videoDetailController.heroTag, ); @@ -142,8 +142,7 @@ class _HorizontalMemberPageState extends State { height: 35, child: TextButton.icon( onPressed: () => _controller - ..lastAid = widget.videoDetailController.oid.value - .toString() + ..lastAid = widget.videoDetailController.aid.toString() ..queryBySort(), icon: Icon( Icons.sort, diff --git a/lib/pages/video/reply/controller.dart b/lib/pages/video/reply/controller.dart index e7a3f27c9..b0a334749 100644 --- a/lib/pages/video/reply/controller.dart +++ b/lib/pages/video/reply/controller.dart @@ -2,16 +2,26 @@ import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart' show MainListReply, ReplyInfo; import 'package:PiliPlus/grpc/reply.dart'; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/common/video/video_type.dart'; import 'package:PiliPlus/pages/common/reply_controller.dart'; +import 'package:PiliPlus/pages/video/controller.dart'; import 'package:PiliPlus/utils/id_utils.dart'; import 'package:flutter/material.dart'; -import 'package:get/get_state_manager/src/rx_flutter/rx_ticket_provider_mixin.dart'; +import 'package:get/get.dart'; class VideoReplyController extends ReplyController with GetSingleTickerProviderStateMixin { - VideoReplyController({required this.aid}); - // 视频aid 请求时使用的oid + VideoReplyController({ + required this.aid, + required this.videoType, + required this.heroTag, + }); int aid; + final VideoType videoType; + late final isPugv = videoType == VideoType.pugv; + + final String heroTag; + late final videoCtr = Get.find(tag: heroTag); @override dynamic get sourceId => IdUtils.av2bv(aid); @@ -52,9 +62,18 @@ class VideoReplyController extends ReplyController return response.replies; } + int get _epId { + return switch (videoCtr.epId) { + int e => e, + String e => int.parse(e), + _ => throw UnsupportedError(''), + }; + } + @override Future> customGetData() => ReplyGrpc.mainList( - oid: aid, + oid: isPugv ? _epId : aid, + type: videoType.replyType, mode: mode.value, cursorNext: cursorNext, offset: paginationReply?.nextOffset, diff --git a/lib/pages/video/reply/view.dart b/lib/pages/video/reply/view.dart index 4bae4ba0b..2b46e6ab1 100644 --- a/lib/pages/video/reply/view.dart +++ b/lib/pages/video/reply/view.dart @@ -13,22 +13,8 @@ import 'package:flutter/rendering.dart'; import 'package:get/get.dart'; class VideoReplyPanel extends StatefulWidget { - final String? bvid; - final int oid; - final int rpid; - final int replyLevel; - final String heroTag; - final Function(ReplyInfo replyItem, int? rpid) replyReply; - final VoidCallback? onViewImage; - final ValueChanged? onDismissed; - final Function(List, int)? callback; - final bool? needController; - const VideoReplyPanel({ super.key, - this.bvid, - required this.oid, - this.rpid = 0, this.replyLevel = 1, required this.heroTag, required this.replyReply, @@ -38,6 +24,14 @@ class VideoReplyPanel extends StatefulWidget { this.needController, }); + final int replyLevel; + final String heroTag; + final Function(ReplyInfo replyItem, int? rpid) replyReply; + final VoidCallback? onViewImage; + final ValueChanged? onDismissed; + final Function(List, int)? callback; + final bool? needController; + @override State createState() => _VideoReplyPanelState(); } @@ -179,7 +173,7 @@ class _VideoReplyPanelState extends State _videoReplyController.onReply( context, oid: _videoReplyController.aid, - replyType: 1, + replyType: _videoReplyController.videoType.replyType, ); }, tooltip: '发表评论', @@ -240,7 +234,7 @@ class _VideoReplyPanelState extends State item, index, _videoReplyController.aid, - 1, + _videoReplyController.videoType.replyType, ), ); } diff --git a/lib/pages/video/view.dart b/lib/pages/video/view.dart index 374675606..6857efecf 100644 --- a/lib/pages/video/view.dart +++ b/lib/pages/video/view.dart @@ -44,7 +44,6 @@ import 'package:PiliPlus/services/service_locator.dart'; import 'package:PiliPlus/services/shutdown_timer_service.dart'; import 'package:PiliPlus/utils/accounts.dart'; import 'package:PiliPlus/utils/extension.dart'; -import 'package:PiliPlus/utils/id_utils.dart'; import 'package:PiliPlus/utils/image_util.dart'; import 'package:PiliPlus/utils/num_util.dart'; import 'package:PiliPlus/utils/page_utils.dart'; @@ -138,7 +137,11 @@ class _VideoDetailPageVState extends State if (videoDetailController.showReply) { _videoReplyController = Get.put( - VideoReplyController(aid: videoDetailController.oid.value), + VideoReplyController( + aid: videoDetailController.aid, + videoType: videoDetailController.videoType, + heroTag: heroTag, + ), tag: heroTag, ); } @@ -317,8 +320,9 @@ class _VideoDetailPageVState extends State return; } plPlayerController = videoDetailController.plPlayerController; - videoDetailController.isShowCover.value = false; - videoDetailController.autoPlay.value = true; + videoDetailController + ..isShowCover.value = false + ..autoPlay.value = true; if (videoDetailController.plPlayerController.preInitPlayer) { await plPlayerController!.play(); } else { @@ -798,9 +802,7 @@ class _VideoDetailPageVState extends State ); } else { PageUtils.reportVideo( - videoDetailController - .oid - .value, + videoDetailController.aid, ); } break; @@ -1256,10 +1258,9 @@ class _VideoDetailPageVState extends State ), ); - Widget get manualPlayerWidget => Obx( - () => Visibility( - visible: videoDetailController.isShowCover.value, - child: Stack( + Widget get manualPlayerWidget => Obx(() { + if (videoDetailController.isShowCover.value) { + return Stack( clipBehavior: Clip.none, children: [ Positioned( @@ -1341,9 +1342,7 @@ class _VideoDetailPageVState extends State if (!Accounts.main.isLogin) { SmartDialog.showToast('账号未登录'); } else { - PageUtils.reportVideo( - videoDetailController.oid.value, - ); + PageUtils.reportVideo(videoDetailController.aid); } break; case 'note': @@ -1396,9 +1395,10 @@ class _VideoDetailPageVState extends State ), ), ], - ), - ), - ); + ); + } + return const SizedBox.shrink(); + }); Widget get plPlayer => Obx( key: videoPlayerKey, @@ -1641,37 +1641,35 @@ class _VideoDetailPageVState extends State if (isShowing) plPlayer, - if (!videoDetailController.autoPlay.value) ...[ - Obx( - () => videoDetailController.isShowCover.value - ? Positioned( - top: 0, - left: 0, - right: 0, - bottom: 0, - child: GestureDetector( - onTap: handlePlay, - child: Obx( - () => CachedNetworkImage( - imageUrl: - videoDetailController.cover.value.http2https, - width: videoWidth, - height: videoHeight, - fit: BoxFit.cover, - fadeOutDuration: const Duration(milliseconds: 120), - fadeInDuration: const Duration(milliseconds: 120), - memCacheWidth: videoWidth.cacheSize(context), - placeholder: (context, url) => Center( - child: Image.asset('assets/images/loading.png'), - ), - ), - ), + Obx(() { + if (videoDetailController.isShowCover.value) { + return Positioned( + top: 0, + left: 0, + right: 0, + bottom: 0, + child: GestureDetector( + onTap: handlePlay, + child: Obx( + () => CachedNetworkImage( + imageUrl: videoDetailController.cover.value.http2https, + width: videoWidth, + height: videoHeight, + fit: BoxFit.cover, + fadeOutDuration: const Duration(milliseconds: 120), + fadeInDuration: const Duration(milliseconds: 120), + memCacheWidth: videoWidth.cacheSize(context), + placeholder: (context, url) => Center( + child: Image.asset('assets/images/loading.png'), ), - ) - : const SizedBox.shrink(), - ), - manualPlayerWidget, - ], + ), + ), + ), + ); + } + return const SizedBox.shrink(); + }), + manualPlayerWidget, if (videoDetailController.plPlayerController.enableSponsorBlock || videoDetailController.continuePlayingPart) @@ -1933,7 +1931,7 @@ class _VideoDetailPageVState extends State list: [videoDetail.pages!], cover: videoDetailController.cover.value, bvid: videoDetailController.bvid, - aid: IdUtils.bv2av(videoDetailController.bvid), + aid: videoDetailController.aid, cid: videoDetailController.cid.value, isReversed: videoDetail.isPageReversed, onChangeEpisode: videoDetailController.isUgc @@ -1943,7 +1941,7 @@ class _VideoDetailPageVState extends State isSupportReverse: videoDetailController.isUgc, onReverse: () => onReversePlay( bvid: videoDetailController.bvid, - aid: IdUtils.bv2av(videoDetailController.bvid), + aid: videoDetailController.aid, isSeason: false, ), ), @@ -1980,7 +1978,7 @@ class _VideoDetailPageVState extends State seasonId: videoDetail.ugcSeason!.id, list: videoDetail.ugcSeason!.sections!, bvid: videoDetailController.bvid, - aid: IdUtils.bv2av(videoDetailController.bvid), + aid: videoDetailController.aid, cid: videoDetailController.seasonCid ?? 0, isReversed: ugcIntroController .videoDetail @@ -1995,7 +1993,7 @@ class _VideoDetailPageVState extends State isSupportReverse: videoDetailController.isUgc, onReverse: () => onReversePlay( bvid: videoDetailController.bvid, - aid: IdUtils.bv2av(videoDetailController.bvid), + aid: videoDetailController.aid, isSeason: true, ), ), @@ -2006,25 +2004,21 @@ class _VideoDetailPageVState extends State ); } - Widget videoReplyPanel([bool needCtr = true]) => Obx( - () => VideoReplyPanel( - key: videoReplyPanelKey, - needController: needCtr, - bvid: videoDetailController.bvid, - oid: videoDetailController.oid.value, - heroTag: heroTag, - replyReply: replyReply, - onViewImage: videoDetailController.onViewImage, - onDismissed: videoDetailController.onDismissed, - callback: _horizontalPreview - ? (imgList, index) => PageUtils.onHorizontalPreview( - videoDetailController.childKey, - this, - imgList, - index, - ) - : null, - ), + Widget videoReplyPanel([bool needCtr = true]) => VideoReplyPanel( + key: videoReplyPanelKey, + needController: needCtr, + heroTag: heroTag, + replyReply: replyReply, + onViewImage: videoDetailController.onViewImage, + onDismissed: videoDetailController.onDismissed, + callback: _horizontalPreview + ? (imgList, index) => PageUtils.onHorizontalPreview( + videoDetailController.childKey, + this, + imgList, + index, + ) + : null, ); // 展示二级回复 @@ -2039,7 +2033,7 @@ class _VideoDetailPageVState extends State oid: oid, rpid: rpid, firstFloor: replyItem, - replyType: 1, + replyType: _videoReplyController.videoType.replyType, isVideoDetail: true, onViewImage: videoDetailController.onViewImage, onDismissed: videoDetailController.onDismissed, diff --git a/lib/pages/video/widgets/header_control.dart b/lib/pages/video/widgets/header_control.dart index 17ea8121c..8a77fbc03 100644 --- a/lib/pages/video/widgets/header_control.dart +++ b/lib/pages/video/widgets/header_control.dart @@ -561,7 +561,7 @@ class HeaderControlState extends State { return; } Get.back(); - PageUtils.reportVideo(videoDetailCtr.oid.value); + PageUtils.reportVideo(videoDetailCtr.aid); }, leading: const Icon(Icons.error_outline, size: 20), title: const Text('举报', style: titleStyle), diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index 601d75db9..787c3760f 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -9,6 +9,7 @@ import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/models/common/account_type.dart'; import 'package:PiliPlus/models/common/audio_normalization.dart'; import 'package:PiliPlus/models/common/sponsor_block/skip_type.dart'; +import 'package:PiliPlus/models/common/video/video_type.dart'; import 'package:PiliPlus/models/user/danmaku_rule.dart'; import 'package:PiliPlus/models_new/video/video_shot/data.dart'; import 'package:PiliPlus/pages/mine/controller.dart'; @@ -122,10 +123,13 @@ class PlPlayerController { dynamic _epid; dynamic _seasonId; dynamic _subType; + VideoType _videoType = VideoType.ugc; int _heartDuration = 0; int? width; int? height; + late final tryLook = !Accounts.get(AccountType.video).isLogin && Pref.p1080; + late DataSource dataSource; Timer? _timer; @@ -529,9 +533,11 @@ class PlPlayerController { dynamic epid, dynamic seasonId, dynamic subType, + VideoType? videoType, VoidCallback? callback, }) async { try { + _videoType = videoType ?? VideoType.ugc; this.width = width; this.height = height; this.dataSource = dataSource; @@ -1439,6 +1445,7 @@ class PlPlayerController { dynamic epid, dynamic seasonId, dynamic subType, + VideoType? videoType, }) async { if (!enableHeart || MineController.anonymity.value || progress == 0) { return; @@ -1466,6 +1473,7 @@ class PlPlayerController { epid: epid ?? _epid, seasonId: seasonId ?? _seasonId, subType: subType ?? _subType, + videoType: videoType ?? _videoType, ); return; } @@ -1479,6 +1487,7 @@ class PlPlayerController { epid: epid ?? _epid, seasonId: seasonId ?? _seasonId, subType: subType ?? _subType, + videoType: videoType ?? _videoType, ); } } diff --git a/lib/services/audio_handler.dart b/lib/services/audio_handler.dart index 31df49c5e..67686e50f 100644 --- a/lib/services/audio_handler.dart +++ b/lib/services/audio_handler.dart @@ -153,9 +153,11 @@ class VideoPlayerServiceHandler extends BaseAudioHandler with SeekHandler { } else if (data is EpisodeItem) { mediaItem = MediaItem( id: id, - title: data.showTitle ?? data.title ?? '', + title: data.showTitle ?? data.longTitle ?? data.title ?? '', artist: artist, - duration: Duration(milliseconds: data.duration ?? 0), + duration: data.from == 'pugv' + ? Duration(seconds: data.duration ?? 0) + : Duration(milliseconds: data.duration ?? 0), artUri: Uri.parse(data.cover ?? ''), ); } else if (data is RoomInfoH5Data) { diff --git a/lib/utils/accounts/account_manager/account_mgr.dart b/lib/utils/accounts/account_manager/account_mgr.dart index 48040c8a9..2dd59c2bc 100644 --- a/lib/utils/accounts/account_manager/account_mgr.dart +++ b/lib/utils/accounts/account_manager/account_mgr.dart @@ -53,7 +53,11 @@ class AccountManager extends Interceptor { Api.searchTrending, Api.searchRecommend, }, - AccountType.video: {Api.ugcUrl, Api.pgcUrl}, + AccountType.video: { + Api.ugcUrl, + Api.pgcUrl, + Api.pugvUrl, + }, }; static const loginApi = { diff --git a/lib/utils/app_scheme.dart b/lib/utils/app_scheme.dart index 0f73c143e..50b4a62d2 100644 --- a/lib/utils/app_scheme.dart +++ b/lib/utils/app_scheme.dart @@ -817,6 +817,13 @@ class PiliScheme { } launchURL(); return false; + case 'cheese': + bool hasMatch = PageUtils.viewPgcFromUri(path, isPgc: false); + if (hasMatch) { + return true; + } + launchURL(); + return false; default: Map map = IdUtils.matchAvorBv(input: area?.split('?').first); if (map.isNotEmpty) { diff --git a/lib/utils/page_utils.dart b/lib/utils/page_utils.dart index 5b381929f..b8ee00ffd 100644 --- a/lib/utils/page_utils.dart +++ b/lib/utils/page_utils.dart @@ -5,7 +5,7 @@ import 'package:PiliPlus/grpc/im.dart'; import 'package:PiliPlus/http/dynamics.dart'; import 'package:PiliPlus/http/search.dart'; import 'package:PiliPlus/models/common/image_preview_type.dart'; -import 'package:PiliPlus/models/common/search_type.dart'; +import 'package:PiliPlus/models/common/video/video_type.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/models_new/pgc/pgc_info_model/episode.dart'; import 'package:PiliPlus/models_new/pgc/pgc_info_model/result.dart'; @@ -695,16 +695,27 @@ class PageUtils { } static final _pgcRegex = RegExp(r'(ep|ss)(\d+)'); - static bool viewPgcFromUri(String uri, {String? progress}) { + static bool viewPgcFromUri( + String uri, { + bool isPgc = true, + String? progress, + }) { RegExpMatch? match = _pgcRegex.firstMatch(uri); if (match != null) { bool isSeason = match.group(1) == 'ss'; String id = match.group(2)!; - viewPgc( - seasonId: isSeason ? id : null, - epId: isSeason ? null : id, - progress: progress, - ); + if (isPgc) { + viewPgc( + seasonId: isSeason ? id : null, + epId: isSeason ? null : id, + progress: progress, + ); + } else { + viewPugv( + seasonId: isSeason ? id : null, + epId: isSeason ? null : id, + ); + } return true; } return false; @@ -745,7 +756,7 @@ class PageUtils { 'pgcApi': true, 'pic': item.cover, 'heroTag': Utils.makeHeroTag(item.cid), - 'videoType': SearchType.video, + 'videoType': VideoType.ugc, if (progress != null) 'progress': int.tryParse(progress), }, @@ -775,7 +786,7 @@ class PageUtils { arguments: { 'pic': episode.cover, 'heroTag': Utils.makeHeroTag(episode.cid), - 'videoType': SearchType.media_bangumi, + 'videoType': VideoType.pgc, 'pgcItem': data, if (progress != null) 'progress': int.tryParse(progress), }, @@ -791,6 +802,37 @@ class PageUtils { } } + static Future viewPugv({dynamic seasonId, dynamic epId}) async { + try { + SmartDialog.showLoading(msg: '资源获取中'); + var res = await SearchHttp.pugvInfo(seasonId: seasonId, epId: epId); + SmartDialog.dismiss(); + if (res.isSuccess) { + PgcInfoModel data = res.data; + if (data.episodes?.isNotEmpty == true) { + final item = data.episodes!.first; + toVideoPage( + 'bvid=${IdUtils.av2bv(item.aid!)}&cid=${item.cid}&seasonId=${data.seasonId}&epId=${item.id}', + arguments: { + 'pic': item.cover, + 'heroTag': Utils.makeHeroTag(item.cid), + 'videoType': VideoType.pugv, + 'pgcItem': data, + }, + preventDuplicates: false, + ); + } else { + SmartDialog.showToast('NULL'); + } + } else { + res.toast(); + } + } catch (e) { + SmartDialog.dismiss(); + SmartDialog.showToast(e.toString()); + } + } + static void toDupNamed( String page, { dynamic arguments,