Skip to content

Commit

Permalink
Fix and add HLS support for Xtream provider service
Browse files Browse the repository at this point in the history
Signed-off-by: Pierre-Emmanuel Jacquier <[email protected]>
  • Loading branch information
pierre-emmanuelJ committed Nov 7, 2019
1 parent bd125d7 commit d85ffb6
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 20 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM golang:1.11.5-alpine
FROM golang:1.13.1-alpine

WORKDIR /go/src/github.com/pierre-emmanuelJ/iptv-proxy
COPY . .
Expand Down
48 changes: 43 additions & 5 deletions pkg/routes/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func Routes(proxyConfig *config.ProxyConfig, r *gin.RouterGroup) {
r.GET(fmt.Sprintf("/live/%s/%s/:id", proxyConfig.User, proxyConfig.Password), p.xtreamStreamLive)
r.GET(fmt.Sprintf("/movie/%s/%s/:id", proxyConfig.User, proxyConfig.Password), p.xtreamStreamMovie)
r.GET(fmt.Sprintf("/series/%s/%s/:id", proxyConfig.User, proxyConfig.Password), p.xtreamStreamSeries)
r.GET("/hlsr/:token/:username/:password/:channel/:tmp/:extension", p.hlsrStream)
r.GET(fmt.Sprintf("/hlsr/:token/%s/%s/:channel/:hash/:chunk", proxyConfig.User, proxyConfig.Password), p.hlsrStream)

if strings.Contains(p.XtreamBaseURL, p.RemoteURL.Host) &&
p.XtreamUser == p.RemoteURL.Query().Get("username") &&
Expand Down Expand Up @@ -106,20 +106,58 @@ func (p *proxy) getM3U(c *gin.Context) {
func (p *proxy) reverseProxy(c *gin.Context) {
rpURL, err := url.Parse(p.Track.URI)
if err != nil {
log.Fatal(err)
c.AbortWithError(http.StatusInternalServerError, err)
return
}

stream(c, rpURL)
p.stream(c, rpURL)
}

func stream(c *gin.Context, oriURL *url.URL) {
resp, err := http.Get(oriURL.String())
func (p *proxy) stream(c *gin.Context, oriURL *url.URL) {
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}

resp, err := client.Get(oriURL.String())
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
defer resp.Body.Close()

if resp.StatusCode == http.StatusFound {
location, err := resp.Location()
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
id := c.Param("id")
if strings.Contains(location.String(), id) {
hlsChannelsRedirectURLLock.Lock()
hlsChannelsRedirectURL[id] = *location
hlsChannelsRedirectURLLock.Unlock()

hlsResp, err := http.Get(location.String())
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
defer hlsResp.Body.Close()

b, err := ioutil.ReadAll(hlsResp.Body)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
body := string(b)
body = strings.ReplaceAll(body, "/"+p.XtreamUser+"/"+p.XtreamPassword+"/", "/"+p.User+"/"+p.Password+"/")
c.Data(http.StatusOK, hlsResp.Header.Get("Content-Type"), []byte(body))
return
}
}

copyHTTPHeader(c, resp.Header)
c.Status(resp.StatusCode)
c.Stream(func(w io.Writer) bool {
Expand Down
53 changes: 39 additions & 14 deletions pkg/routes/xtream.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package routes

import (
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"log"
Expand All @@ -24,10 +25,13 @@ type cacheMeta struct {
time.Time
}

var hlsChannelsRedirectURL map[string]url.URL = map[string]url.URL{}
var hlsChannelsRedirectURLLock = sync.RWMutex{}

// XXX Use key/value storage e.g: etcd, redis...
// and remove that dirty globals
var xtreamM3uCache map[string]cacheMeta = map[string]cacheMeta{}
var lock = sync.RWMutex{}
var xtreamM3uCacheLock = sync.RWMutex{}

func (p *proxy) cacheXtreamM3u(m3uURL *url.URL) error {
playlist, err := m3u.Parse(m3uURL.String())
Expand All @@ -45,14 +49,14 @@ func (p *proxy) cacheXtreamM3u(m3uURL *url.URL) error {
return err
}

lock.Lock()
xtreamM3uCacheLock.Lock()
path, err := writeCacheTmp([]byte(result), m3uURL.String())
if err != nil {
return err
}

xtreamM3uCache[m3uURL.String()] = cacheMeta{path, time.Now()}
lock.Unlock()
xtreamM3uCacheLock.Unlock()

return nil
}
Expand Down Expand Up @@ -103,24 +107,24 @@ func (p *proxy) xtreamGet(c *gin.Context) {
return
}

lock.RLock()
xtreamM3uCacheLock.RLock()
meta, ok := xtreamM3uCache[m3uURL.String()]
d := time.Now().Sub(meta.Time)
if !ok || d.Hours() >= float64(p.M3UCacheExpiration) {
log.Printf("[iptv-proxy] %v | %s | xtream cache m3u file\n", time.Now().Format("2006/01/02 - 15:04:05"), c.ClientIP())
lock.RUnlock()
xtreamM3uCacheLock.RUnlock()
if err := p.cacheXtreamM3u(m3uURL); err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
} else {
lock.RUnlock()
xtreamM3uCacheLock.RUnlock()
}

c.Header("Content-Disposition", "attachment; filename=\"iptv.m3u\"")
lock.RLock()
xtreamM3uCacheLock.RLock()
path := xtreamM3uCache[m3uURL.String()].string
lock.RUnlock()
xtreamM3uCacheLock.RUnlock()
data, err := ioutil.ReadFile(path)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
Expand Down Expand Up @@ -232,7 +236,7 @@ func (p *proxy) xtreamStream(c *gin.Context) {
return
}

stream(c, rpURL)
p.stream(c, rpURL)
}

func (p *proxy) xtreamStreamLive(c *gin.Context) {
Expand All @@ -243,7 +247,7 @@ func (p *proxy) xtreamStreamLive(c *gin.Context) {
return
}

stream(c, rpURL)
p.stream(c, rpURL)
}

func (p *proxy) xtreamStreamMovie(c *gin.Context) {
Expand All @@ -254,7 +258,7 @@ func (p *proxy) xtreamStreamMovie(c *gin.Context) {
return
}

stream(c, rpURL)
p.stream(c, rpURL)
}

func (p *proxy) xtreamStreamSeries(c *gin.Context) {
Expand All @@ -265,17 +269,38 @@ func (p *proxy) xtreamStreamSeries(c *gin.Context) {
return
}

stream(c, rpURL)
p.stream(c, rpURL)
}

func (p *proxy) hlsrStream(c *gin.Context) {
req, err := url.Parse(fmt.Sprintf("%s%s", p.XtreamBaseURL, c.Request.URL.String()))
hlsChannelsRedirectURLLock.RLock()
url, ok := hlsChannelsRedirectURL[c.Param("channel")+".m3u8"]
if !ok {
c.AbortWithError(http.StatusNotFound, errors.New("HSL redirect url not found"))
return
}
hlsChannelsRedirectURLLock.RUnlock()

req, err := url.Parse(
fmt.Sprintf(
"%s://%s/hlsr/%s/%s/%s/%s/%s/%s",
url.Scheme,
url.Host,
c.Param("token"),
p.XtreamUser,
p.XtreamPassword,
c.Param("channel"),
c.Param("hash"),
c.Param("chunk"),
),
)

if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}

stream(c, req)
p.stream(c, req)
}

func xtreamReplaceURL(playlist *m3u.Playlist, user, password string, hostConfig *config.HostConfiguration, https bool) (*m3u.Playlist, error) {
Expand Down

0 comments on commit d85ffb6

Please sign in to comment.