168 lines
6.2 KiB
Dart
168 lines
6.2 KiB
Dart
import 'package:hgdj/config/address.dart' as address;
|
|
import 'package:hgdj/hj_model/drama/drama_models.dart';
|
|
import 'package:hgdj/hj_model/video_model.dart';
|
|
import 'package:path/path.dart' as path;
|
|
|
|
/// 作品的「一集」:漫画一话 / 小说一章 / 有声一集 / 短剧一集 共用这一个模型,
|
|
/// 所以字段是几种业务的并集,取值时按自己的业务挑(如短剧只关心视频与解锁那几个)
|
|
class MediaContent {
|
|
// ===== 标识 =====
|
|
String? id; //本集 id
|
|
String? mediaId; //所属作品(剧/漫画/小说)的 id
|
|
int? episodeNumber; //第几集/第几话,从 1 开始
|
|
String? name; //集名,如「第1集」
|
|
String? cover; //本集封面,短剧多为空、回退用剧封面
|
|
|
|
// ===== 资源 =====
|
|
String? videoUrl; //视频相对路径(m3u8),要拼 baseApiPath 才能播
|
|
String? h265Url; //H.265 源,为空表示这一集没有 265
|
|
String? audioUrl; //有声书音频,见 realAudioUrl
|
|
List<String>? urlSet; //多清晰度地址集合,短剧下发空数组
|
|
String? text; //小说正文
|
|
String? md5;
|
|
int? playTime; //时长(秒)
|
|
int? mediaSize; //文件字节数
|
|
int? height; //视频高
|
|
int? weight; //视频宽(后端字段就叫 weight,不是重量)
|
|
double? ratio; //宽高比,0 表示后端没算
|
|
|
|
// ===== 权限 / 付费 =====
|
|
//0会员 1金币购买 2免费。**只用于展示**,能不能播看 canPlay
|
|
int? listenPermission;
|
|
int? price; //单集金币价,0 为免费
|
|
bool? hasBuy; //本集是否已单独购买过
|
|
bool? isFree; //本集是否免费,别再用整部的 freeEpisode 自己算
|
|
bool? canPlay; //能不能播只认这个字段
|
|
String? accessType; // free 免费 / bought 已购 / card 短剧卡 / coin 未解锁
|
|
DramaPaywall? paywall; //付费墙文案与价格,canPlay=false 时必有,能播时为 null
|
|
bool? isActive; //是否上架,下架的集不该出现在选集面板
|
|
|
|
// ===== 试看(feed / list / info 三个接口都下发)=====
|
|
bool? previewEnabled; //这一集配没配试看
|
|
int? previewStart; //试看起播秒数,0 = 从头
|
|
int? previewSeconds; //试看时长(秒),从 previewStart 起算,到点停下挂付费墙
|
|
String? previewVideoUrl; //试看片地址,与 videoUrl 同格式
|
|
String? previewH265Url; //试看片的 265 源,为空表示没有
|
|
|
|
// ===== 统计 =====
|
|
int? countBrowse;
|
|
int? countCollect;
|
|
int? countComment;
|
|
int? countDisLike;
|
|
int? countLike;
|
|
int? countPurchases;
|
|
VidStatus? mediaStatus; //本集的已购/已收藏/已点赞状态
|
|
|
|
// ===== 时间 =====
|
|
String? createdAt;
|
|
String? updateTime;
|
|
|
|
//只用来造空 model 占位(拉详情失败时页面据此走错误态),字段一律走 fromJson 赋值
|
|
MediaContent();
|
|
|
|
MediaContent.fromJson(dynamic json) {
|
|
json ??= {};
|
|
id = json['id'];
|
|
mediaId = json['mediaId'];
|
|
episodeNumber = json['episodeNumber'];
|
|
name = json['name'];
|
|
cover = json['cover'];
|
|
|
|
videoUrl = json['videoUrl'];
|
|
h265Url = json['h265Url'];
|
|
audioUrl = json['audioUrl'];
|
|
//用 is List 判而不是 as List:下发 null 或非数组时硬 cast 会抛,整条分集就解析不出来
|
|
final rawUrlSet = json['urlSet'];
|
|
urlSet =
|
|
rawUrlSet is List ? rawUrlSet.map((e) => e.toString()).toList() : [];
|
|
text = json['text'];
|
|
md5 = json['md5'];
|
|
playTime = json['playTime'];
|
|
mediaSize = json['mediaSize'];
|
|
height = json['height'];
|
|
weight = json['weight'];
|
|
//数字直接转;后端也可能下发字符串("1.5"),那时才走 parse,脏值落 null
|
|
final rawRatio = json['ratio'];
|
|
ratio = rawRatio is num
|
|
? rawRatio.toDouble()
|
|
: double.tryParse(rawRatio?.toString() ?? '');
|
|
|
|
listenPermission = json['listenPermission'];
|
|
price = json['price'];
|
|
hasBuy = json['hasBuy'];
|
|
isFree = json['isFree'];
|
|
canPlay = json['canPlay'];
|
|
accessType = json['accessType'];
|
|
paywall =
|
|
json['paywall'] != null ? DramaPaywall.fromJson(json['paywall']) : null;
|
|
isActive = json['isActive'];
|
|
|
|
previewEnabled = json['previewEnabled'];
|
|
previewStart = json['previewStart'];
|
|
previewSeconds = json['previewSeconds'];
|
|
previewVideoUrl = json['previewVideoUrl'];
|
|
previewH265Url = json['previewH265Url'];
|
|
|
|
countBrowse = json['countBrowse'];
|
|
countCollect = json['countCollect'];
|
|
countComment = json['countComment'];
|
|
countDisLike = json['countDisLike'];
|
|
countLike = json['countLike'];
|
|
countPurchases = json['countPurchases'];
|
|
mediaStatus = json['mediaStatus'] != null
|
|
? VidStatus.fromMap(json['mediaStatus'])
|
|
: null;
|
|
|
|
createdAt = json['createdAt'];
|
|
updateTime = json['updateTime'];
|
|
}
|
|
|
|
/// 只给浏览历史落库用(经 CartoonMediaInfo.toJson → HistoryUtil)。
|
|
/// paywall 和 preview* 都是这次请求的临时状态、下次进来要重新拉,故意不存
|
|
Map<String, dynamic> toJson() {
|
|
return {
|
|
'id': id,
|
|
'mediaId': mediaId,
|
|
'episodeNumber': episodeNumber,
|
|
'name': name,
|
|
'cover': cover,
|
|
'videoUrl': videoUrl,
|
|
'h265Url': h265Url,
|
|
'audioUrl': audioUrl,
|
|
'urlSet': urlSet,
|
|
'text': text,
|
|
'md5': md5,
|
|
'playTime': playTime,
|
|
'mediaSize': mediaSize,
|
|
'height': height,
|
|
'weight': weight,
|
|
'ratio': ratio,
|
|
'listenPermission': listenPermission,
|
|
'price': price,
|
|
'hasBuy': hasBuy,
|
|
'isFree': isFree,
|
|
'canPlay': canPlay,
|
|
'accessType': accessType,
|
|
'isActive': isActive,
|
|
'countBrowse': countBrowse,
|
|
'countCollect': countCollect,
|
|
'countComment': countComment,
|
|
'countDisLike': countDisLike,
|
|
'countLike': countLike,
|
|
'countPurchases': countPurchases,
|
|
if (mediaStatus != null) 'mediaStatus': mediaStatus?.toJson(),
|
|
'createdAt': createdAt,
|
|
'updateTime': updateTime,
|
|
};
|
|
}
|
|
|
|
/// 有声书音频完整地址:相对路径要拼音频 CDN,已是 http(s) 的直接用。
|
|
/// 目前有声书播的是 ComicChapterInfo 里的同名实现,这里留着与 audioUrl 字段配套
|
|
String get realAudioUrl {
|
|
final url = audioUrl;
|
|
if (url?.isNotEmpty != true) return '';
|
|
if (url!.startsWith('http')) return url;
|
|
return path.join(address.Address.audioCdnAddress ?? '', url);
|
|
}
|
|
}
|