Skip to content

Commit b33f6e1

Browse files
committed
feat(channel): 支持配置频道URL是否优先使用组播地址(当存在多个URL地址)。
1 parent ed64547 commit b33f6e1

File tree

5 files changed

+75
-44
lines changed

5 files changed

+75
-44
lines changed

README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,15 +93,17 @@ Key后面的即是)。
9393
* m3u格式直播源在线接口
9494

9595
```
96-
http://IP:PORT/channel/m3u?csFormat={format}
96+
http://IP:PORT/channel/m3u?csFormat={format}&multiFirst={multiFirst}
9797
```
9898

99-
参数csFormat可指定回看catchup-source的请求格式,非必填。可选值如下:
99+
1. 参数csFormat可指定回看catchup-source的请求格式,非必填。可选值如下:
100100

101-
|| 是否缺省 | 说明 |
102-
|---|------|-------------------------------------------------------|
103-
| 0 || `?playseek=${(b)yyyyMMddHHmmss}-${(e)yyyyMMddHHmmss}` |
104-
| 1 || `?playseek={utc:YmdHMS}-{utcend:YmdHMS}` |
101+
|| 是否缺省 | 说明 |
102+
|---|------|-------------------------------------------------------|
103+
| 0 || `?playseek=${(b)yyyyMMddHHmmss}-${(e)yyyyMMddHHmmss}` |
104+
| 1 || `?playseek={utc:YmdHMS}-{utcend:YmdHMS}` |
105+
106+
2. 参数multiFirst:当频道存在多个URL地址时,是否优先使用组播地址。可选值:`true``false`。非必填,缺省为`true`
105107

106108
* txt格式直播源在线接口
107109

cmd/iptv/cmds/channel.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ var (
2525
udpxyURL string
2626
format string
2727
catchupSource string
28+
multicastFirst bool
2829
)
2930

3031
func NewChannelCLI() *cobra.Command {
@@ -82,13 +83,13 @@ func NewChannelCLI() *cobra.Command {
8283
switch format {
8384
case "txt":
8485
// 将获取到的频道列表转换为TXT格式
85-
content, err = iptv.ToTxtFormat(channels, udpxyURL)
86+
content, err = iptv.ToTxtFormat(channels, udpxyURL, multicastFirst)
8687
if err != nil {
8788
return err
8889
}
8990
case "m3u":
9091
// 将获取到的频道列表转换为M3U格式
91-
content, err = iptv.ToM3UFormat(channels, udpxyURL, catchupSource)
92+
content, err = iptv.ToM3UFormat(channels, udpxyURL, catchupSource, multicastFirst)
9293
if err != nil {
9394
return err
9495
}
@@ -109,6 +110,7 @@ func NewChannelCLI() *cobra.Command {
109110
channelCmd.Flags().StringVarP(&udpxyURL, "udpxy", "u", "", "如果有安装udpxy进行组播转单播,请配置HTTP地址,e.g `http://192.168.1.1:4022`。")
110111
channelCmd.Flags().StringVarP(&format, "format", "f", "m3u", "生成的直播源文件格式,e.g `m3u或txt`。")
111112
channelCmd.Flags().StringVarP(&catchupSource, "catchup-source", "s", "?playseek=${(b)yyyyMMddHHmmss}-${(e)yyyyMMddHHmmss}", "回看的请求格式字符串,会追加在时移地址后面。")
113+
channelCmd.Flags().BoolVarP(&multicastFirst, "multicast-first", "m", false, "当频道存在多个URL地址时,是否优先使用组播地址。")
112114

113115
return channelCmd
114116
}

internal/app/iptv/channel.go

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ type Channel struct {
1515
ChannelID string `json:"channelID"` // 频道ID
1616
ChannelName string `json:"channelName"` // 频道名称
1717
UserChannelID string `json:"userChannelID"` // 频道号
18-
ChannelURL *url.URL `json:"channelURL"` // 频道URL
18+
ChannelURLs []url.URL `json:"channelURLs"` // 频道URL列表
1919
TimeShift string `json:"timeShift"` // 时移类型
2020
TimeShiftLength time.Duration `json:"timeShiftLength"` // 支持的时移长度
2121
TimeShiftURL *url.URL `json:"timeShiftURL"` // 时移地址(回放地址)
@@ -24,40 +24,36 @@ type Channel struct {
2424
}
2525

2626
// ToM3UFormat 转换为M3U格式内容
27-
func ToM3UFormat(channels []Channel, udpxyURL, catchupSource string) (string, error) {
27+
func ToM3UFormat(channels []Channel, udpxyURL, catchupSource string, multicastFirst bool) (string, error) {
2828
if len(channels) == 0 {
2929
return "", errors.New("no channels found")
3030
}
3131

3232
var sb strings.Builder
3333
sb.WriteString("#EXTM3U\n")
3434
for _, channel := range channels {
35-
var err error
36-
var channelURL string
37-
if udpxyURL != "" && channel.ChannelURL.Scheme == SCHEME_IGMP {
38-
channelURL, err = url.JoinPath(udpxyURL, fmt.Sprintf("/rtp/%s", channel.ChannelURL.Host))
39-
if err != nil {
40-
return "", err
41-
}
42-
} else {
43-
channelURL = channel.ChannelURL.String()
35+
// 根据指定条件,获取频道URL地址
36+
channelURLStr, err := getChannelURLStr(channel.ChannelURLs, udpxyURL, multicastFirst)
37+
if err != nil {
38+
return "", err
4439
}
40+
4541
var m3uLine string
4642
if channel.TimeShift == "1" && channel.TimeShiftLength > 0 {
4743
m3uLine = fmt.Sprintf("#EXTINF:-1 tvg-id=\"%s\" tvg-chno=\"%s\" catchup=\"%s\" catchup-source=\"%s\" catchup-days=\"%d\" group-title=\"%s\",%s\n%s\n",
4844
channel.ChannelID, channel.UserChannelID, "default", channel.TimeShiftURL.String()+catchupSource,
49-
int64(channel.TimeShiftLength.Hours()/24), channel.GroupName, channel.ChannelName, channelURL)
45+
int64(channel.TimeShiftLength.Hours()/24), channel.GroupName, channel.ChannelName, channelURLStr)
5046
} else {
5147
m3uLine = fmt.Sprintf("#EXTINF:-1 tvg-id=\"%s\" tvg-chno=\"%s\" group-title=\"%s\",%s\n%s\n",
52-
channel.ChannelID, channel.UserChannelID, channel.GroupName, channel.ChannelName, channelURL)
48+
channel.ChannelID, channel.UserChannelID, channel.GroupName, channel.ChannelName, channelURLStr)
5349
}
5450
sb.WriteString(m3uLine)
5551
}
5652
return sb.String(), nil
5753
}
5854

5955
// ToTxtFormat 转换为txt格式内容
60-
func ToTxtFormat(channels []Channel, udpxyURL string) (string, error) {
56+
func ToTxtFormat(channels []Channel, udpxyURL string, multicastFirst bool) (string, error) {
6157
if len(channels) == 0 {
6258
return "", errors.New("no channels found")
6359
}
@@ -88,21 +84,41 @@ func ToTxtFormat(channels []Channel, udpxyURL string) (string, error) {
8884

8985
// 输出频道信息
9086
for _, channel := range groupChannels {
91-
var err error
92-
var channelURL string
93-
if udpxyURL != "" && channel.ChannelURL.Scheme == SCHEME_IGMP {
94-
channelURL, err = url.JoinPath(udpxyURL, fmt.Sprintf("/rtp/%s", channel.ChannelURL.Host))
95-
if err != nil {
96-
return "", err
97-
}
98-
} else {
99-
channelURL = channel.ChannelURL.String()
87+
// 根据指定条件,获取频道URL地址
88+
channelURLStr, err := getChannelURLStr(channel.ChannelURLs, udpxyURL, multicastFirst)
89+
if err != nil {
90+
return "", err
10091
}
10192

10293
txtLine := fmt.Sprintf("%s,%s\n",
103-
channel.ChannelName, channelURL)
94+
channel.ChannelName, channelURLStr)
10495
sb.WriteString(txtLine)
10596
}
10697
}
10798
return sb.String(), nil
10899
}
100+
101+
// getChannelURLStr 根据指定条件,获取频道URL地址
102+
func getChannelURLStr(channelURLs []url.URL, udpxyURL string, multicastFirst bool) (string, error) {
103+
if len(channelURLs) == 0 {
104+
return "", errors.New("no channel urls found")
105+
}
106+
107+
var channelURL url.URL
108+
if len(channelURLs) == 1 {
109+
channelURL = channelURLs[0]
110+
} else {
111+
for _, channelURL = range channelURLs {
112+
if (multicastFirst && channelURL.Scheme == SCHEME_IGMP) ||
113+
(!multicastFirst && channelURL.Scheme != SCHEME_IGMP) {
114+
break
115+
}
116+
}
117+
}
118+
119+
if udpxyURL != "" && channelURL.Scheme == SCHEME_IGMP {
120+
return url.JoinPath(udpxyURL, fmt.Sprintf("/rtp/%s", channelURL.Host))
121+
} else {
122+
return channelURL.String(), nil
123+
}
124+
}

internal/app/iptv/ct/channel.go

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -102,21 +102,19 @@ func (c *Client) GetAllChannelList(ctx context.Context) ([]iptv.Channel, error)
102102
}
103103

104104
// channelURL类型转换
105-
// channelURL可能同时返回组播和单播多个地址(通过|分割),这里优先取组播地址
106-
var channelURL *url.URL
105+
// channelURL可能同时返回组播和单播多个地址(通过|分割)
107106
channelURLStrList := strings.Split(string(matches[4]), "|")
107+
channelURLs := make([]url.URL, 0, len(channelURLStrList))
108108
for _, channelURLStr := range channelURLStrList {
109-
channelURL, err = url.Parse(channelURLStr)
109+
channelURL, err := url.Parse(channelURLStr)
110110
if err != nil {
111111
continue
112112
}
113113

114-
if channelURL != nil && channelURL.Scheme == iptv.SCHEME_IGMP {
115-
break
116-
}
114+
channelURLs = append(channelURLs, *channelURL)
117115
}
118116

119-
if channelURL == nil {
117+
if len(channelURLs) == 0 {
120118
c.logger.Warn("The channelURL of this channel is illegal, skip it.", zap.String("channelName", channelName), zap.String("channelURL", string(matches[4])))
121119
continue
122120
}
@@ -144,7 +142,7 @@ func (c *Client) GetAllChannelList(ctx context.Context) ([]iptv.Channel, error)
144142
ChannelID: string(matches[1]),
145143
ChannelName: channelName,
146144
UserChannelID: string(matches[3]),
147-
ChannelURL: channelURL,
145+
ChannelURLs: channelURLs,
148146
TimeShift: string(matches[5]),
149147
TimeShiftLength: time.Duration(timeShiftLength) * time.Minute,
150148
TimeShiftURL: timeShiftURL,

internal/app/router/channel_controller.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"iptv/internal/app/iptv"
77
"net/http"
8+
"strconv"
89
"sync/atomic"
910
"time"
1011

@@ -34,15 +35,21 @@ func GetM3UData(c *gin.Context) {
3435
catchupSource = diypCatchupSource
3536
}
3637

37-
channels := *channelsPtr.Load()
38+
// 是否优先是由组播地址
39+
multiFirstStr := c.DefaultQuery("multiFirst", "true")
40+
multicastFirst, err := strconv.ParseBool(multiFirstStr)
41+
if err != nil {
42+
multicastFirst = true
43+
}
3844

45+
channels := *channelsPtr.Load()
3946
if len(channels) == 0 {
4047
c.Status(http.StatusNotFound)
4148
return
4249
}
4350

4451
// 将获取到的频道列表转换为m3u格式
45-
m3uContent, err := iptv.ToM3UFormat(channels, udpxyURL, catchupSource)
52+
m3uContent, err := iptv.ToM3UFormat(channels, udpxyURL, catchupSource, multicastFirst)
4653
if err != nil {
4754
logger.Error("Failed to convert channel list to m3u format.", zap.Error(err))
4855
// 返回响应
@@ -56,15 +63,21 @@ func GetM3UData(c *gin.Context) {
5663

5764
// GetTXTData 查询直播源txt
5865
func GetTXTData(c *gin.Context) {
59-
channels := *channelsPtr.Load()
66+
// 是否优先是由组播地址
67+
multiFirstStr := c.DefaultQuery("multiFirst", "true")
68+
multicastFirst, err := strconv.ParseBool(multiFirstStr)
69+
if err != nil {
70+
multicastFirst = true
71+
}
6072

73+
channels := *channelsPtr.Load()
6174
if len(channels) == 0 {
6275
c.Status(http.StatusNotFound)
6376
return
6477
}
6578

6679
// 将获取到的频道列表转换为txt格式
67-
txtContent, err := iptv.ToTxtFormat(channels, udpxyURL)
80+
txtContent, err := iptv.ToTxtFormat(channels, udpxyURL, multicastFirst)
6881
if err != nil {
6982
logger.Error("Failed to convert channel list to txt format.", zap.Error(err))
7083
// 返回响应

0 commit comments

Comments
 (0)