Merge branch 'feature/m3u'
This commit is contained in:
commit
3c79a1de8c
@ -71,6 +71,7 @@ func ActionServe(c *cli.Context) error {
|
|||||||
// Setup RTMP-server
|
// Setup RTMP-server
|
||||||
s := server.NewRTMPServer(ctx, cfg.RTMPListenAddr)
|
s := server.NewRTMPServer(ctx, cfg.RTMPListenAddr)
|
||||||
s.Logger = logger
|
s.Logger = logger
|
||||||
|
s.Hostname = cfg.Hostname
|
||||||
|
|
||||||
// Setup web-server
|
// Setup web-server
|
||||||
webDone := make(chan struct{})
|
webDone := make(chan struct{})
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
var ErrNotFound = errors.New("no config file found")
|
var ErrNotFound = errors.New("no config file found")
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
Hostname string `toml:"Hostname"`
|
||||||
RTMPListenAddr string `toml:"RTMPListenAddr"`
|
RTMPListenAddr string `toml:"RTMPListenAddr"`
|
||||||
HTTPServerEnable bool `toml:"HTTPServerEnable"`
|
HTTPServerEnable bool `toml:"HTTPServerEnable"`
|
||||||
HTTPListenAddr string `toml:"HTTPListenAddr"`
|
HTTPListenAddr string `toml:"HTTPListenAddr"`
|
||||||
@ -36,6 +37,7 @@ func FromReader(r io.Reader) (*Config, error) {
|
|||||||
c.HTTPServerEnable = false
|
c.HTTPServerEnable = false
|
||||||
c.HTTPListenAddr = ":8077"
|
c.HTTPListenAddr = ":8077"
|
||||||
c.LogLevel = "INFO"
|
c.LogLevel = "INFO"
|
||||||
|
c.Hostname = "localhost"
|
||||||
|
|
||||||
decoder := toml.NewDecoder(r)
|
decoder := toml.NewDecoder(r)
|
||||||
if err := decoder.Decode(&c); err != nil {
|
if err := decoder.Decode(&c); err != nil {
|
||||||
@ -56,6 +58,9 @@ func (c *Config) Verify() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) UpdateFromEnv() error {
|
func (c *Config) UpdateFromEnv() error {
|
||||||
|
if hostname, found := os.LookupEnv("DOGTAMER_HOSTNAME"); found {
|
||||||
|
c.Hostname = hostname
|
||||||
|
}
|
||||||
if loglevel, found := os.LookupEnv("DOGTAMER_LOGLEVEL"); found {
|
if loglevel, found := os.LookupEnv("DOGTAMER_LOGLEVEL"); found {
|
||||||
c.LogLevel = loglevel
|
c.LogLevel = loglevel
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
# dogtamer example config file
|
# dogtamer example config file
|
||||||
|
|
||||||
|
# Hostname used when generating m3u playlist
|
||||||
|
# Default: "localhost
|
||||||
|
# ENV: DOGTAMER_HOSTNAME
|
||||||
|
Hostname = "localhost"
|
||||||
|
|
||||||
# Address to listen to for incoming rtmp connections
|
# Address to listen to for incoming rtmp connections
|
||||||
# Default: ":5566"
|
# Default: ":5566"
|
||||||
# ENV: DOGTAMER_RTMPLISTENADDR
|
# ENV: DOGTAMER_RTMPLISTENADDR
|
||||||
|
15
go.mod
15
go.mod
@ -2,15 +2,18 @@ module github.uio.no/torjus/dogtamer
|
|||||||
|
|
||||||
go 1.17
|
go 1.17
|
||||||
|
|
||||||
require github.com/urfave/cli/v2 v2.3.0
|
require (
|
||||||
|
github.com/dustin/go-humanize v1.0.0
|
||||||
|
github.com/go-chi/chi/v5 v5.0.4
|
||||||
|
github.com/nareix/joy5 v0.0.0-20210317075623-2c912ca30590
|
||||||
|
github.com/pelletier/go-toml v1.9.3
|
||||||
|
github.com/urfave/cli/v2 v2.3.0
|
||||||
|
go.uber.org/atomic v1.9.0
|
||||||
|
go.uber.org/zap v1.19.0
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
|
||||||
github.com/nareix/joy5 v0.0.0-20210317075623-2c912ca30590 // indirect
|
|
||||||
github.com/pelletier/go-toml v1.9.3 // indirect
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
go.uber.org/atomic v1.9.0 // indirect
|
|
||||||
go.uber.org/multierr v1.7.0 // indirect
|
go.uber.org/multierr v1.7.0 // indirect
|
||||||
go.uber.org/zap v1.19.0 // indirect
|
|
||||||
)
|
)
|
||||||
|
12
go.sum
12
go.sum
@ -1,12 +1,16 @@
|
|||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
|
github.com/go-chi/chi/v5 v5.0.4 h1:5e494iHzsYBiyXQAHHuI4tyJS9M3V84OuX3ufIIGHFo=
|
||||||
|
github.com/go-chi/chi/v5 v5.0.4/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
@ -14,7 +18,9 @@ github.com/nareix/joy5 v0.0.0-20210317075623-2c912ca30590 h1:PnxRU8L8Y2q82vFC2Qd
|
|||||||
github.com/nareix/joy5 v0.0.0-20210317075623-2c912ca30590/go.mod h1:XmAOs6UJXpNXRwKk+KY/nv5kL6xXYXyellk+A1pTlko=
|
github.com/nareix/joy5 v0.0.0-20210317075623-2c912ca30590/go.mod h1:XmAOs6UJXpNXRwKk+KY/nv5kL6xXYXyellk+A1pTlko=
|
||||||
github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ=
|
github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ=
|
||||||
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
@ -25,12 +31,14 @@ github.com/spf13/pflag v1.0.4-0.20181223182923-24fa6976df40/go.mod h1:DYY7MBk1bd
|
|||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
||||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
|
go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
|
||||||
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
||||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||||
go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec=
|
go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec=
|
||||||
@ -38,6 +46,7 @@ go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95a
|
|||||||
go.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE=
|
go.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE=
|
||||||
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
|
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
@ -46,12 +55,15 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11 h1:Yq9t9jnGoR+dBuitxdo9l6Q7xh/zOyNnYUtDKaQ3x0E=
|
||||||
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
33
m3u/m3u.go
Normal file
33
m3u/m3u.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package m3u
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Playlist struct {
|
||||||
|
items []*PlaylistItem
|
||||||
|
}
|
||||||
|
|
||||||
|
type PlaylistItem struct {
|
||||||
|
Name string
|
||||||
|
Path string
|
||||||
|
Time int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Playlist) WriteTo(w io.Writer) (int64, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.Write([]byte("#EXTM3U\n"))
|
||||||
|
for i, item := range p.items {
|
||||||
|
buf.WriteString(fmt.Sprintf("#EXTINF:%d,%s\n%s", item.Time, item.Name, item.Path))
|
||||||
|
if i+1 < len(p.items) {
|
||||||
|
buf.WriteString("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return io.Copy(w, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Playlist) Add(item *PlaylistItem) {
|
||||||
|
p.items = append(p.items, item)
|
||||||
|
}
|
42
m3u/m3u_test.go
Normal file
42
m3u/m3u_test.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package m3u_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.uio.no/torjus/dogtamer/m3u"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPlaylist(t *testing.T) {
|
||||||
|
t.Run("TestWriteSingle", func(t *testing.T) {
|
||||||
|
var p m3u.Playlist
|
||||||
|
p.Add(&m3u.PlaylistItem{Name: "TestItem", Path: "rtmp://localhost:5566/view/test", Time: -1})
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
_, err := p.WriteTo(&buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to write playlist: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := "#EXTM3U\n#EXTINF:-1,TestItem\nrtmp://localhost:5566/view/test"
|
||||||
|
if buf.String() != expected {
|
||||||
|
t.Errorf("Output does not match expected. Got '%s' want '%s'", buf.String(), expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("TestWriteMultiple", func(t *testing.T) {
|
||||||
|
var p m3u.Playlist
|
||||||
|
p.Add(&m3u.PlaylistItem{Name: "TestItem", Path: "rtmp://localhost:5566/view/test", Time: -1})
|
||||||
|
p.Add(&m3u.PlaylistItem{Name: "TestTwo", Path: "rtmp://localhost:5566/view/testtwo", Time: 5})
|
||||||
|
var buf bytes.Buffer
|
||||||
|
_, err := p.WriteTo(&buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to write playlist: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := "#EXTM3U\n#EXTINF:-1,TestItem\nrtmp://localhost:5566/view/test\n#EXTINF:5,TestTwo\nrtmp://localhost:5566/view/testtwo"
|
||||||
|
if buf.String() != expected {
|
||||||
|
t.Errorf("Output does not match expected. Got '%s' want '%s'", buf.String(), expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
@ -3,6 +3,7 @@ package server
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"regexp"
|
"regexp"
|
||||||
@ -16,6 +17,8 @@ import (
|
|||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrNoSuchItem error = fmt.Errorf("no such stream")
|
||||||
|
|
||||||
type RTMPClient struct {
|
type RTMPClient struct {
|
||||||
c *rtmp.Conn
|
c *rtmp.Conn
|
||||||
nc net.Conn
|
nc net.Conn
|
||||||
@ -238,6 +241,7 @@ func (client *RTMPClient) handleClient() {
|
|||||||
|
|
||||||
type RTMPServer struct {
|
type RTMPServer struct {
|
||||||
ListenAddr string
|
ListenAddr string
|
||||||
|
Hostname string
|
||||||
Logger *zap.SugaredLogger
|
Logger *zap.SugaredLogger
|
||||||
streamsLock sync.Mutex
|
streamsLock sync.Mutex
|
||||||
streams map[string]*Stream
|
streams map[string]*Stream
|
||||||
@ -248,6 +252,7 @@ type RTMPServer struct {
|
|||||||
func NewRTMPServer(ctx context.Context, addr string) *RTMPServer {
|
func NewRTMPServer(ctx context.Context, addr string) *RTMPServer {
|
||||||
serverCtx, cancel := context.WithCancel(ctx)
|
serverCtx, cancel := context.WithCancel(ctx)
|
||||||
rs := &RTMPServer{
|
rs := &RTMPServer{
|
||||||
|
Hostname: "localhost",
|
||||||
ListenAddr: addr,
|
ListenAddr: addr,
|
||||||
Logger: zap.NewNop().Sugar(),
|
Logger: zap.NewNop().Sugar(),
|
||||||
streams: make(map[string]*Stream),
|
streams: make(map[string]*Stream),
|
||||||
@ -358,3 +363,31 @@ func (rs *RTMPServer) handleConn(c *rtmp.Conn, nc net.Conn) {
|
|||||||
// Stream URL is invalid, disconnect
|
// Stream URL is invalid, disconnect
|
||||||
nc.Close()
|
nc.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type StreamInfo struct {
|
||||||
|
Name string
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RTMPServer) List() []*StreamInfo {
|
||||||
|
var results []*StreamInfo
|
||||||
|
_, port, _ := net.SplitHostPort(s.ListenAddr)
|
||||||
|
|
||||||
|
for _, stream := range s.streams {
|
||||||
|
results = append(results, &StreamInfo{Name: stream.Name, Path: fmt.Sprintf("rtmp://%s:%s/view/%s", s.Hostname, port, stream.Name)})
|
||||||
|
}
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RTMPServer) GetInfo(name string) (*StreamInfo, error) {
|
||||||
|
stream, ok := s.streams[name]
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrNoSuchItem
|
||||||
|
}
|
||||||
|
_, port, _ := net.SplitHostPort(s.ListenAddr)
|
||||||
|
|
||||||
|
return &StreamInfo{
|
||||||
|
Name: stream.Name,
|
||||||
|
Path: fmt.Sprintf("rtmp://%s:%s/view/%s", s.Hostname, port, stream.Name),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
<DOCTYPE html>
|
<DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<ol>
|
<ol>
|
||||||
{{ range .Streams }}
|
{{ range .Streams }}
|
||||||
<li>{{ .URL }}</li>
|
<li>
|
||||||
|
<a href="{{ .Path }}">{{ .Name }}</a>
|
||||||
|
</li>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</ol>
|
</ol>
|
||||||
</html>
|
</html>
|
@ -7,6 +7,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.uio.no/torjus/dogtamer/m3u"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,9 +30,12 @@ func NewWebServer(ctx context.Context, rs *RTMPServer) *WebServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ws *WebServer) Serve() error {
|
func (ws *WebServer) Serve() error {
|
||||||
|
r := chi.NewRouter()
|
||||||
|
r.Get("/", ws.IndexHandler)
|
||||||
|
r.Get("/playlist/{name}", ws.PlaylistHandler)
|
||||||
ws.httpServer = &http.Server{
|
ws.httpServer = &http.Server{
|
||||||
Addr: ws.ListenAddr,
|
Addr: ws.ListenAddr,
|
||||||
Handler: http.HandlerFunc(ws.IndexHandler),
|
Handler: r,
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@ -52,22 +57,19 @@ func (ws *WebServer) Serve() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ws *WebServer) IndexHandler(w http.ResponseWriter, r *http.Request) {
|
func (ws *WebServer) IndexHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
data := struct {
|
|
||||||
|
var data struct {
|
||||||
Streams []struct {
|
Streams []struct {
|
||||||
Name string
|
Name string
|
||||||
URL template.URL
|
Path template.URL
|
||||||
}
|
}
|
||||||
}{}
|
}
|
||||||
|
streams := ws.rtmpServer.List()
|
||||||
for _, s := range ws.rtmpServer.streams {
|
for _, stream := range streams {
|
||||||
stream := struct {
|
data.Streams = append(data.Streams, struct {
|
||||||
Name string
|
Name string
|
||||||
URL template.URL
|
Path template.URL
|
||||||
}{
|
}{Name: stream.Name, Path: template.URL(fmt.Sprintf("/playlist/%s", stream.Name))})
|
||||||
Name: s.Name,
|
|
||||||
URL: template.URL(fmt.Sprintf("rtmp://localhost:5566/view/%s", s.Name)),
|
|
||||||
}
|
|
||||||
data.Streams = append(data.Streams, stream)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpl := template.Must(template.ParseFiles("server/templates/index.html"))
|
tmpl := template.Must(template.ParseFiles("server/templates/index.html"))
|
||||||
@ -76,3 +78,34 @@ func (ws *WebServer) IndexHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
tmpl.Execute(w, data)
|
tmpl.Execute(w, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ws *WebServer) PlaylistHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
streamName := chi.URLParam(r, "name")
|
||||||
|
if streamName == "" {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
streamInfo, err := ws.rtmpServer.GetInfo(streamName)
|
||||||
|
if err != nil {
|
||||||
|
if err == ErrNoSuchItem {
|
||||||
|
ws.Logger.Debugw("Client requested non-existing playlist", "stream_name", streamName, "error", err)
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ws.Logger.Warnw("Error getting stream info", "stream_name", streamName, "error", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var p m3u.Playlist
|
||||||
|
p.Add(&m3u.PlaylistItem{Name: streamInfo.Name, Time: -1, Path: streamInfo.Path})
|
||||||
|
|
||||||
|
w.Header().Add("Content-Type", "application/mpegurl")
|
||||||
|
w.Header().Add("Content-Disposition", "attachment; filename=stream.m3u")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
|
||||||
|
_, err = p.WriteTo(w)
|
||||||
|
if err != nil {
|
||||||
|
ws.Logger.Warnw("error generating playlist", "stream_name", streamName, "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user