初始化

This commit is contained in:
谢宇宁
2026-09-15 15:44:13 +07:00
commit a23ba685e9
1065 changed files with 101122 additions and 0 deletions
+167
View File
@@ -0,0 +1,167 @@
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);
}
}