256 lines
6.4 KiB
Go
256 lines
6.4 KiB
Go
package playletser
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"videoweb/app/service/upstream"
|
|
"videoweb/pub/common/log"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
const (
|
|
chapterDetailPath = "/api/app/playlet/chapter/detail"
|
|
chapterListPath = "/api/app/playlet/chapter/list"
|
|
)
|
|
|
|
// PlayQuery 播放页参数。
|
|
type PlayQuery struct {
|
|
PlayletID int64
|
|
PlayletSlug string
|
|
ChapterID int64
|
|
}
|
|
|
|
// ParsePlayQuery 解析播放页的路径参数。
|
|
func ParsePlayQuery(playletKey, chapterIDRaw string) PlayQuery {
|
|
q := PlayQuery{PlayletSlug: strings.TrimSpace(playletKey)}
|
|
if n, err := strconv.ParseInt(strings.TrimSpace(playletKey), 10, 64); err == nil && n > 0 {
|
|
q.PlayletID = n
|
|
q.PlayletSlug = ""
|
|
}
|
|
if n, err := strconv.ParseInt(strings.TrimSpace(chapterIDRaw), 10, 64); err == nil && n > 0 {
|
|
q.ChapterID = n
|
|
}
|
|
return q
|
|
}
|
|
|
|
// FetchChapterDetail 查询剧集播放详情。
|
|
func FetchChapterDetail(c *gin.Context, chapterID, playletID int64, playletSlug string) (*PlayletChapterInfoResp, error) {
|
|
if chapterID <= 0 {
|
|
return nil, fmt.Errorf("chapter detail invalid chapterId")
|
|
}
|
|
params := url.Values{}
|
|
params.Set("chapterId", strconv.FormatInt(chapterID, 10))
|
|
if playletSlug = strings.TrimSpace(playletSlug); playletSlug != "" {
|
|
params.Set("playletSlug", playletSlug)
|
|
} else if playletID > 0 {
|
|
params.Set("playletId", strconv.FormatInt(playletID, 10))
|
|
}
|
|
var env APIEnvelope[PlayletChapterInfoResp]
|
|
if err := upstream.GetJSON(c, chapterDetailPath+"?"+params.Encode(), &env); err != nil {
|
|
return nil, err
|
|
}
|
|
if env.Code != 200 {
|
|
return nil, fmt.Errorf("chapter detail code=%d msg=%s tip=%s", env.Code, env.Msg, env.Tip)
|
|
}
|
|
env.Data.SeoInfo.Normalize()
|
|
return &env.Data, nil
|
|
}
|
|
|
|
// FetchChapterDetailOrNil 失败返回 nil。
|
|
func FetchChapterDetailOrNil(c *gin.Context, chapterID, playletID int64, playletSlug string) *PlayletChapterInfoResp {
|
|
data, err := FetchChapterDetail(c, chapterID, playletID, playletSlug)
|
|
if err != nil {
|
|
log.Error("fetch chapter detail failed", log.E(err))
|
|
return nil
|
|
}
|
|
if data == nil || data.ID <= 0 {
|
|
return nil
|
|
}
|
|
return data
|
|
}
|
|
|
|
// FetchChapterList 根据短剧 slug 或 ID 查询选集列表,slug 优先。
|
|
func FetchChapterList(c *gin.Context, playletID int64, playletSlug string) (*PlayletChapterListResp, error) {
|
|
params := url.Values{}
|
|
if playletSlug = strings.TrimSpace(playletSlug); playletSlug != "" {
|
|
params.Set("playletSlug", playletSlug)
|
|
} else if playletID > 0 {
|
|
params.Set("playletId", strconv.FormatInt(playletID, 10))
|
|
} else {
|
|
return nil, fmt.Errorf("chapter list missing playletSlug or playletId")
|
|
}
|
|
var env APIEnvelope[PlayletChapterListResp]
|
|
if err := upstream.GetJSON(c, chapterListPath+"?"+params.Encode(), &env); err != nil {
|
|
return nil, err
|
|
}
|
|
if env.Code != 200 {
|
|
return nil, fmt.Errorf("playlet chapter list code=%d msg=%s tip=%s", env.Code, env.Msg, env.Tip)
|
|
}
|
|
return &env.Data, nil
|
|
}
|
|
|
|
// ResolvePlayChapterID 无 chapterId 时取第 1 集(currentEpisode=1),否则取列表首条。
|
|
func ResolvePlayChapterID(playlet *PlayletInfoResp, chapterID int64) int64 {
|
|
if chapterID > 0 {
|
|
return chapterID
|
|
}
|
|
if playlet == nil {
|
|
return 0
|
|
}
|
|
var firstID int64
|
|
for _, ch := range playlet.PlayletChapterInfo {
|
|
if ch.ID <= 0 {
|
|
continue
|
|
}
|
|
if firstID == 0 {
|
|
firstID = ch.ID
|
|
}
|
|
if ch.CurrentEpisode == 1 {
|
|
return ch.ID
|
|
}
|
|
}
|
|
return firstID
|
|
}
|
|
|
|
// FindChapter 在详情选集中按 chapterId 查找。
|
|
func FindChapter(playlet *PlayletInfoResp, chapterID int64) *PlayletChapterData {
|
|
if playlet == nil || chapterID <= 0 {
|
|
return nil
|
|
}
|
|
for i := range playlet.PlayletChapterInfo {
|
|
if playlet.PlayletChapterInfo[i].ID == chapterID {
|
|
return &playlet.PlayletChapterInfo[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NextChapterID 返回下一集 chapterId;没有则 0。
|
|
func NextChapterID(playlet *PlayletInfoResp, chapterID int64) int64 {
|
|
if playlet == nil || chapterID <= 0 {
|
|
return 0
|
|
}
|
|
found := false
|
|
for _, ch := range playlet.PlayletChapterInfo {
|
|
if found && ch.ID > 0 {
|
|
return ch.ID
|
|
}
|
|
if ch.ID == chapterID {
|
|
found = true
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// FirstUnlockCost 取第一个需金币解锁的单集价格。
|
|
func FirstUnlockCost(chapters []PlayletChapterData) int {
|
|
for _, ch := range chapters {
|
|
if ch.NeedCoinGate() {
|
|
return ch.UnlockCost()
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// ChapterFromList 将详情选集条目转成章节详情结构(chapter/detail 失败时的 UI 兜底)。
|
|
func ChapterFromList(ch PlayletChapterData) *PlayletChapterInfoResp {
|
|
return &PlayletChapterInfoResp{
|
|
ID: ch.ID,
|
|
PlayletID: ch.PlayletID,
|
|
CurrentEpisode: ch.CurrentEpisode,
|
|
Title: ch.Title,
|
|
MD5: ch.MD5,
|
|
Hash: ch.Hash,
|
|
Height: ch.Height,
|
|
Width: ch.Width,
|
|
Ratio: ch.Ratio,
|
|
VideoURL: ch.VideoURL,
|
|
MediaSize: ch.MediaSize,
|
|
MediaTime: ch.MediaTime,
|
|
PayType: ch.PayType,
|
|
SingleCoins: ch.SingleCoins,
|
|
CostCoins: ch.CostCoins,
|
|
Balance: ch.Balance,
|
|
CanWatch: ch.CanWatch,
|
|
IsUnlocked: ch.IsUnlocked,
|
|
NeedUnlock: ch.NeedUnlock,
|
|
NeedVip: ch.NeedVip,
|
|
VipValid: ch.VipValid,
|
|
}
|
|
}
|
|
|
|
// AllowWatch 是否可播放(优先 canWatch;兼容仅返回 videoUrl 的旧数据)。
|
|
func (c *PlayletChapterInfoResp) AllowWatch() bool {
|
|
if c == nil {
|
|
return false
|
|
}
|
|
if c.CanWatch {
|
|
return true
|
|
}
|
|
if c.NeedUnlock || c.NeedVip {
|
|
return false
|
|
}
|
|
return strings.TrimSpace(c.VideoURL) != "" && c.PayType == 0
|
|
}
|
|
|
|
// UnlockCost 金币解锁消耗:优先 costCoins。
|
|
func (c *PlayletChapterInfoResp) UnlockCost() int64 {
|
|
if c == nil {
|
|
return 0
|
|
}
|
|
if c.CostCoins > 0 {
|
|
return c.CostCoins
|
|
}
|
|
if c.SingleCoins > 0 {
|
|
return int64(c.SingleCoins)
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// AllowWatch 详情选集是否可直达播放(优先 canWatch)。
|
|
func (c PlayletChapterData) AllowWatch() bool {
|
|
if c.CanWatch {
|
|
return true
|
|
}
|
|
if c.NeedUnlock || c.NeedVip {
|
|
return false
|
|
}
|
|
if c.IsUnlocked {
|
|
return true
|
|
}
|
|
return c.PayType == 0
|
|
}
|
|
|
|
// NeedCoinGate 是否弹出金币解锁。
|
|
func (c PlayletChapterData) NeedCoinGate() bool {
|
|
if c.AllowWatch() {
|
|
return false
|
|
}
|
|
if c.NeedUnlock {
|
|
return true
|
|
}
|
|
return c.PayType == 1
|
|
}
|
|
|
|
// NeedVipGate 是否弹出 VIP 开通。
|
|
func (c PlayletChapterData) NeedVipGate() bool {
|
|
if c.AllowWatch() || c.NeedCoinGate() {
|
|
return false
|
|
}
|
|
if c.NeedVip {
|
|
return true
|
|
}
|
|
return c.PayType == 2
|
|
}
|
|
|
|
// UnlockCost 金币解锁消耗:优先 costCoins。
|
|
func (c PlayletChapterData) UnlockCost() int {
|
|
if c.CostCoins > 0 {
|
|
return int(c.CostCoins)
|
|
}
|
|
return c.SingleCoins
|
|
}
|