BIG core logic rewrite

This commit is contained in:
Alexey Khit
2023-03-17 06:48:02 +03:00
parent 2146ea470b
commit 12a7b96289
107 changed files with 3000 additions and 3024 deletions
+2 -2
View File
@@ -3,7 +3,7 @@ package debug
import (
"github.com/AlexxIT/go2rtc/cmd/api"
"github.com/AlexxIT/go2rtc/cmd/streams"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/AlexxIT/go2rtc/pkg/core"
)
func Init() {
@@ -12,6 +12,6 @@ func Init() {
streams.HandleFunc("null", nullHandler)
}
func nullHandler(string) (streamer.Producer, error) {
func nullHandler(string) (core.Producer, error) {
return nil, nil
}
+2 -2
View File
@@ -2,15 +2,15 @@ package dvrip
import (
"github.com/AlexxIT/go2rtc/cmd/streams"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/dvrip"
"github.com/AlexxIT/go2rtc/pkg/streamer"
)
func Init() {
streams.HandleFunc("dvrip", handle)
}
func handle(url string) (streamer.Producer, error) {
func handle(url string) (core.Producer, error) {
conn := dvrip.NewClient(url)
if err := conn.Dial(); err != nil {
return nil, err
+2 -2
View File
@@ -4,15 +4,15 @@ import (
"bytes"
"github.com/AlexxIT/go2rtc/cmd/app"
"github.com/AlexxIT/go2rtc/cmd/streams"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/shell"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"os/exec"
)
func Init() {
log := app.GetLogger("echo")
streams.HandleFunc("echo", func(url string) (streamer.Producer, error) {
streams.HandleFunc("echo", func(url string) (core.Producer, error) {
args := shell.QuoteSplit(url[5:])
b, err := exec.Command(args[0], args[1:]...).Output()
+4 -4
View File
@@ -8,9 +8,9 @@ import (
"github.com/AlexxIT/go2rtc/cmd/app"
"github.com/AlexxIT/go2rtc/cmd/rtsp"
"github.com/AlexxIT/go2rtc/cmd/streams"
"github.com/AlexxIT/go2rtc/pkg/core"
pkg "github.com/AlexxIT/go2rtc/pkg/rtsp"
"github.com/AlexxIT/go2rtc/pkg/shell"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/rs/zerolog"
"os"
"os/exec"
@@ -48,7 +48,7 @@ func Init() {
log = app.GetLogger("exec")
}
func Handle(url string) (streamer.Producer, error) {
func Handle(url string) (core.Producer, error) {
sum := md5.Sum([]byte(url))
path := "/" + hex.EncodeToString(sum[:])
@@ -67,7 +67,7 @@ func Handle(url string) (streamer.Producer, error) {
cmd.Stderr = os.Stderr
}
ch := make(chan streamer.Producer)
ch := make(chan core.Producer)
waitersMu.Lock()
waiters[path] = ch
@@ -116,5 +116,5 @@ func Handle(url string) (streamer.Producer, error) {
// internal
var log zerolog.Logger
var waiters = map[string]chan streamer.Producer{}
var waiters = map[string]chan core.Producer{}
var waitersMu sync.Mutex
+10 -10
View File
@@ -2,7 +2,7 @@ package device
import (
"bytes"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/AlexxIT/go2rtc/pkg/core"
"os/exec"
"strings"
)
@@ -11,15 +11,15 @@ import (
const deviceInputPrefix = "-f avfoundation"
func deviceInputSuffix(videoIdx, audioIdx int) string {
video := findMedia(streamer.KindVideo, videoIdx)
audio := findMedia(streamer.KindAudio, audioIdx)
video := findMedia(core.KindVideo, videoIdx)
audio := findMedia(core.KindAudio, audioIdx)
switch {
case video != nil && audio != nil:
return `"` + video.MID + `:` + audio.MID + `"`
return `"` + video.ID + `:` + audio.ID + `"`
case video != nil:
return `"` + video.MID + `"`
return `"` + video.ID + `"`
case audio != nil:
return `"` + audio.MID + `"`
return `"` + audio.ID + `"`
}
return ""
}
@@ -40,10 +40,10 @@ process:
for _, line := range lines {
switch {
case strings.HasSuffix(line, "video devices:"):
kind = streamer.KindVideo
kind = core.KindVideo
continue
case strings.HasSuffix(line, "audio devices:"):
kind = streamer.KindAudio
kind = core.KindAudio
continue
case strings.HasPrefix(line, "dummy"):
break process
@@ -56,6 +56,6 @@ process:
}
}
func loadMedia(kind, name string) *streamer.Media {
return &streamer.Media{Kind: kind, MID: name}
func loadMedia(kind, name string) *core.Media {
return &core.Media{Kind: kind, ID: name}
}
+7 -7
View File
@@ -2,7 +2,7 @@ package device
import (
"bytes"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/AlexxIT/go2rtc/pkg/core"
"io/ioutil"
"os/exec"
"strings"
@@ -12,8 +12,8 @@ import (
const deviceInputPrefix = "-f v4l2"
func deviceInputSuffix(videoIdx, audioIdx int) string {
video := findMedia(streamer.KindVideo, videoIdx)
return video.MID
video := findMedia(core.KindVideo, videoIdx)
return video.ID
}
func loadMedias() {
@@ -23,8 +23,8 @@ func loadMedias() {
}
for _, file := range files {
log.Trace().Msg("[ffmpeg] " + file.Name())
if strings.HasPrefix(file.Name(), streamer.KindVideo) {
media := loadMedia(streamer.KindVideo, "/dev/"+file.Name())
if strings.HasPrefix(file.Name(), core.KindVideo) {
media := loadMedia(core.KindVideo, "/dev/"+file.Name())
if media != nil {
medias = append(medias, media)
}
@@ -32,7 +32,7 @@ func loadMedias() {
}
}
func loadMedia(kind, name string) *streamer.Media {
func loadMedia(kind, name string) *core.Media {
cmd := exec.Command(
Bin, "-hide_banner", "-f", "v4l2", "-list_formats", "all", "-i", name,
)
@@ -44,5 +44,5 @@ func loadMedia(kind, name string) *streamer.Media {
return nil
}
return &streamer.Media{Kind: kind, MID: name}
return &core.Media{Kind: kind, ID: name}
}
+10 -10
View File
@@ -2,7 +2,7 @@ package device
import (
"bytes"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/AlexxIT/go2rtc/pkg/core"
"os/exec"
"strings"
)
@@ -11,15 +11,15 @@ import (
const deviceInputPrefix = "-f dshow"
func deviceInputSuffix(videoIdx, audioIdx int) string {
video := findMedia(streamer.KindVideo, videoIdx)
audio := findMedia(streamer.KindAudio, audioIdx)
video := findMedia(core.KindVideo, videoIdx)
audio := findMedia(core.KindAudio, audioIdx)
switch {
case video != nil && audio != nil:
return `video="` + video.MID + `":audio=` + audio.MID + `"`
return `video="` + video.ID + `":audio=` + audio.ID + `"`
case video != nil:
return `video="` + video.MID + `"`
return `video="` + video.ID + `"`
case audio != nil:
return `audio="` + audio.MID + `"`
return `audio="` + audio.ID + `"`
}
return ""
}
@@ -37,9 +37,9 @@ func loadMedias() {
for _, line := range lines {
var kind string
if strings.HasSuffix(line, "(video)") {
kind = streamer.KindVideo
kind = core.KindVideo
} else if strings.HasSuffix(line, "(audio)") {
kind = streamer.KindAudio
kind = core.KindAudio
} else {
continue
}
@@ -52,6 +52,6 @@ func loadMedias() {
}
}
func loadMedia(kind, name string) *streamer.Media {
return &streamer.Media{Kind: kind, MID: name}
func loadMedia(kind, name string) *core.Media {
return &core.Media{Kind: kind, ID: name}
}
+3 -3
View File
@@ -4,7 +4,7 @@ import (
"encoding/json"
"github.com/AlexxIT/go2rtc/cmd/api"
"github.com/AlexxIT/go2rtc/cmd/app"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/rs/zerolog"
"net/http"
"net/url"
@@ -52,9 +52,9 @@ func GetInput(src string) (string, error) {
var Bin string
var log zerolog.Logger
var medias []*streamer.Media
var medias []*core.Media
func findMedia(kind string, index int) *streamer.Media {
func findMedia(kind string, index int) *core.Media {
for _, media := range medias {
if media.Kind != kind {
continue
+2 -2
View File
@@ -8,7 +8,7 @@ import (
"github.com/AlexxIT/go2rtc/cmd/ffmpeg/device"
"github.com/AlexxIT/go2rtc/cmd/rtsp"
"github.com/AlexxIT/go2rtc/cmd/streams"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/AlexxIT/go2rtc/pkg/core"
"net/url"
"strconv"
"strings"
@@ -27,7 +27,7 @@ func Init() {
defaults["global"] += " -v error"
}
streams.HandleFunc("ffmpeg", func(url string) (streamer.Producer, error) {
streams.HandleFunc("ffmpeg", func(url string) (core.Producer, error) {
args := parseArgs(url[7:]) // remove `ffmpeg:`
if args == nil {
return nil, errors.New("can't generate ffmpeg command")
+2 -2
View File
@@ -5,7 +5,7 @@ import (
"fmt"
"github.com/AlexxIT/go2rtc/cmd/app"
"github.com/AlexxIT/go2rtc/cmd/streams"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/rs/zerolog"
"os"
"path"
@@ -38,7 +38,7 @@ func Init() {
urls := map[string]string{}
streams.HandleFunc("hass", func(url string) (streamer.Producer, error) {
streams.HandleFunc("hass", func(url string) (core.Producer, error) {
if hurl := urls[url[5:]]; hurl != "" {
return streams.GetProducer(hurl)
}
+2 -2
View File
@@ -4,9 +4,9 @@ import (
"fmt"
"github.com/AlexxIT/go2rtc/cmd/api"
"github.com/AlexxIT/go2rtc/cmd/streams"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/mp4"
"github.com/AlexxIT/go2rtc/pkg/mpegts"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/rs/zerolog/log"
"net/http"
"strconv"
@@ -27,7 +27,7 @@ func Init() {
}
type Consumer interface {
streamer.Consumer
core.Consumer
Init() ([]byte, error)
MimeCodecs() string
Start()
+3 -3
View File
@@ -5,8 +5,8 @@ import (
"github.com/AlexxIT/go2rtc/cmd/app"
"github.com/AlexxIT/go2rtc/cmd/srtp"
"github.com/AlexxIT/go2rtc/cmd/streams"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/homekit"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/rs/zerolog"
)
@@ -20,12 +20,12 @@ func Init() {
var log zerolog.Logger
func streamHandler(url string) (streamer.Producer, error) {
func streamHandler(url string) (core.Producer, error) {
conn, err := homekit.NewClient(url, srtp.Server)
if err != nil {
return nil, err
}
if err = conn.Dial();err!=nil{
if err = conn.Dial(); err != nil {
return nil, err
}
return conn, nil
+2 -2
View File
@@ -4,10 +4,10 @@ import (
"errors"
"fmt"
"github.com/AlexxIT/go2rtc/cmd/streams"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/mjpeg"
"github.com/AlexxIT/go2rtc/pkg/mpegts"
"github.com/AlexxIT/go2rtc/pkg/rtmp"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/AlexxIT/go2rtc/pkg/tcp"
"net/http"
"strings"
@@ -18,7 +18,7 @@ func Init() {
streams.HandleFunc("https", handle)
}
func handle(url string) (streamer.Producer, error) {
func handle(url string) (core.Producer, error) {
// first we get the Content-Type to define supported producer
req, err := http.NewRequest("GET", url, nil)
if err != nil {
+2 -2
View File
@@ -2,15 +2,15 @@ package isapi
import (
"github.com/AlexxIT/go2rtc/cmd/streams"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/isapi"
"github.com/AlexxIT/go2rtc/pkg/streamer"
)
func Init() {
streams.HandleFunc("isapi", handle)
}
func handle(url string) (streamer.Producer, error) {
func handle(url string) (core.Producer, error) {
conn, err := isapi.NewClient(url)
if err != nil {
return nil, err
+2 -2
View File
@@ -2,13 +2,13 @@ package ivideon
import (
"github.com/AlexxIT/go2rtc/cmd/streams"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/ivideon"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"strings"
)
func Init() {
streams.HandleFunc("ivideon", func(url string) (streamer.Producer, error) {
streams.HandleFunc("ivideon", func(url string) (core.Producer, error) {
id := strings.Replace(url[8:], "/", ":", 1)
prod := ivideon.NewClient(id)
if err := prod.Dial(); err != nil {
+3 -3
View File
@@ -4,8 +4,8 @@ import (
"github.com/AlexxIT/go2rtc/cmd/api"
"github.com/AlexxIT/go2rtc/cmd/app"
"github.com/AlexxIT/go2rtc/cmd/streams"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/mp4"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/rs/zerolog"
"net/http"
"strconv"
@@ -105,10 +105,10 @@ func handlerMP4(w http.ResponseWriter, r *http.Request) {
cons := &mp4.Consumer{
RemoteAddr: r.RemoteAddr,
UserAgent: r.UserAgent(),
Medias: streamer.ParseQuery(r.URL.Query()),
Medias: core.ParseQuery(r.URL.Query()),
}
cons.Listen(func(msg interface{}) {
cons.Listen(func(msg any) {
if data, ok := msg.([]byte); ok {
if _, err := w.Write(data); err != nil && exit != nil {
exit <- err
+14 -14
View File
@@ -4,8 +4,8 @@ import (
"errors"
"github.com/AlexxIT/go2rtc/cmd/api"
"github.com/AlexxIT/go2rtc/cmd/streams"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/mp4"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"strings"
)
@@ -94,40 +94,40 @@ func handlerWSMP4(tr *api.Transport, msg *api.Message) error {
return nil
}
func parseMedias(codecs string, parseAudio bool) (medias []*streamer.Media) {
var videos []*streamer.Codec
var audios []*streamer.Codec
func parseMedias(codecs string, parseAudio bool) (medias []*core.Media) {
var videos []*core.Codec
var audios []*core.Codec
for _, name := range strings.Split(codecs, ",") {
switch name {
case mp4.MimeH264:
codec := &streamer.Codec{Name: streamer.CodecH264}
codec := &core.Codec{Name: core.CodecH264}
videos = append(videos, codec)
case mp4.MimeH265:
codec := &streamer.Codec{Name: streamer.CodecH265}
codec := &core.Codec{Name: core.CodecH265}
videos = append(videos, codec)
case mp4.MimeAAC:
codec := &streamer.Codec{Name: streamer.CodecAAC}
codec := &core.Codec{Name: core.CodecAAC}
audios = append(audios, codec)
case mp4.MimeOpus:
codec := &streamer.Codec{Name: streamer.CodecOpus}
codec := &core.Codec{Name: core.CodecOpus}
audios = append(audios, codec)
}
}
if videos != nil {
media := &streamer.Media{
Kind: streamer.KindVideo,
Direction: streamer.DirectionRecvonly,
media := &core.Media{
Kind: core.KindVideo,
Direction: core.DirectionSendonly,
Codecs: videos,
}
medias = append(medias, media)
}
if audios != nil && parseAudio {
media := &streamer.Media{
Kind: streamer.KindAudio,
Direction: streamer.DirectionRecvonly,
media := &core.Media{
Kind: core.KindAudio,
Direction: core.DirectionSendonly,
Codecs: audios,
}
medias = append(medias, media)
+2 -2
View File
@@ -3,8 +3,8 @@ package rtmp
import (
"github.com/AlexxIT/go2rtc/cmd/api"
"github.com/AlexxIT/go2rtc/cmd/streams"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/rtmp"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/rs/zerolog/log"
"io"
"net/http"
@@ -16,7 +16,7 @@ func Init() {
api.HandleFunc("api/stream.flv", apiHandle)
}
func streamsHandle(url string) (streamer.Producer, error) {
func streamsHandle(url string) (core.Producer, error) {
conn := rtmp.NewClient(url)
if err := conn.Dial(); err != nil {
return nil, err
+6 -13
View File
@@ -3,9 +3,9 @@ package rtsp
import (
"github.com/AlexxIT/go2rtc/cmd/app"
"github.com/AlexxIT/go2rtc/cmd/streams"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/mp4"
"github.com/AlexxIT/go2rtc/pkg/rtsp"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/AlexxIT/go2rtc/pkg/tcp"
"github.com/rs/zerolog"
"net"
@@ -86,9 +86,9 @@ var Port string
var log zerolog.Logger
var handlers []Handler
var defaultMedias []*streamer.Media
var defaultMedias []*core.Media
func rtspHandler(url string) (streamer.Producer, error) {
func rtspHandler(url string) (core.Producer, error) {
backchannel := true
if i := strings.IndexByte(url, '#'); i > 0 {
@@ -98,11 +98,7 @@ func rtspHandler(url string) (streamer.Producer, error) {
url = url[:i]
}
conn, err := rtsp.NewClient(url)
if err != nil {
return nil, err
}
conn := rtsp.NewClient(url)
conn.UserAgent = app.UserAgent
if log.Trace().Enabled() {
@@ -118,12 +114,12 @@ func rtspHandler(url string) (streamer.Producer, error) {
})
}
if err = conn.Dial(); err != nil {
if err := conn.Dial(); err != nil {
return nil, err
}
conn.Backchannel = backchannel
if err = conn.Describe(); err != nil {
if err := conn.Describe(); err != nil {
if !backchannel {
return nil, err
}
@@ -211,9 +207,6 @@ func tcpHandler(conn *rtsp.Conn) {
closer = func() {
stream.RemoveProducer(conn)
}
case streamer.StatePlaying:
log.Debug().Str("stream", name).Msg("[rtsp] start")
}
})
-15
View File
@@ -1,15 +0,0 @@
package streams
import (
"encoding/json"
"github.com/AlexxIT/go2rtc/pkg/streamer"
)
type Consumer struct {
element streamer.Consumer
tracks []*streamer.Track
}
func (c *Consumer) MarshalJSON() ([]byte, error) {
return json.Marshal(c.element)
}
+3 -3
View File
@@ -2,12 +2,12 @@ package streams
import (
"fmt"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/AlexxIT/go2rtc/pkg/core"
"strings"
"sync"
)
type Handler func(url string) (streamer.Producer, error)
type Handler func(url string) (core.Producer, error)
var handlers = map[string]Handler{}
var handlersMu sync.Mutex
@@ -32,7 +32,7 @@ func HasProducer(url string) bool {
return getHandler(url) != nil
}
func GetProducer(url string) (streamer.Producer, error) {
func GetProducer(url string) (core.Producer, error) {
handler := getHandler(url)
if handler == nil {
return nil, fmt.Errorf("unsupported scheme: %s", url)
+25 -26
View File
@@ -2,14 +2,14 @@ package streams
import (
"errors"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/AlexxIT/go2rtc/pkg/core"
)
func (s *Stream) Play(source string) error {
s.mu.Lock()
for _, producer := range s.producers {
if producer.state == stateInternal && producer.element != nil {
_ = producer.element.Stop()
if producer.state == stateInternal && producer.conn != nil {
_ = producer.conn.Stop()
}
}
s.mu.Unlock()
@@ -18,14 +18,14 @@ func (s *Stream) Play(source string) error {
return nil
}
var src streamer.Producer
var src core.Producer
for _, producer := range s.producers {
if producer.element == nil {
if producer.conn == nil {
continue
}
cons, ok := producer.element.(streamer.Consumer)
cons, ok := producer.conn.(core.Consumer)
if !ok {
continue
}
@@ -59,7 +59,7 @@ func (s *Stream) Play(source string) error {
}
// check if client support consumer interface
cons, ok := dst.(streamer.Consumer)
cons, ok := dst.(core.Consumer)
if !ok {
_ = dst.Stop()
continue
@@ -98,50 +98,49 @@ func (s *Stream) Play(source string) error {
return errors.New("can't find consumer")
}
func (s *Stream) AddInternalProducer(prod streamer.Producer) {
producer := &Producer{element: prod, state: stateInternal}
func (s *Stream) AddInternalProducer(conn core.Producer) {
producer := &Producer{conn: conn, state: stateInternal}
s.mu.Lock()
s.producers = append(s.producers, producer)
s.mu.Unlock()
}
func (s *Stream) AddInternalConsumer(cons streamer.Consumer) {
consumer := &Consumer{element: cons}
func (s *Stream) AddInternalConsumer(conn core.Consumer) {
s.mu.Lock()
s.consumers = append(s.consumers, consumer)
s.consumers = append(s.consumers, conn)
s.mu.Unlock()
}
func (s *Stream) RemoveInternalConsumer(cons streamer.Consumer) {
func (s *Stream) RemoveInternalConsumer(conn core.Consumer) {
s.mu.Lock()
for i, consumer := range s.consumers {
if consumer.element == cons {
s.removeConsumer(i)
if consumer == conn {
s.consumers = append(s.consumers[:i], s.consumers[i+1:]...)
break
}
}
s.mu.Unlock()
}
func matchMedia(prod streamer.Producer, cons streamer.Consumer) bool {
func matchMedia(prod core.Producer, cons core.Consumer) bool {
for _, consMedia := range cons.GetMedias() {
for _, prodMedia := range prod.GetMedias() {
// codec negotiation
prodCodec := prodMedia.MatchMedia(consMedia)
if prodMedia.Direction != core.DirectionRecvonly {
continue
}
prodCodec, consCodec := prodMedia.MatchMedia(consMedia)
if prodCodec == nil {
continue
}
// setup producer track
prodTrack := prod.GetTrack(prodMedia, prodCodec)
if prodTrack == nil {
return false
track, err := prod.GetTrack(prodMedia, prodCodec)
if err != nil {
continue
}
// setup consumer track
consTrack := cons.AddTrack(consMedia, prodTrack)
if consTrack == nil {
return false
if err = cons.AddTrack(consMedia, consCodec, track); err != nil {
continue
}
return true
+117 -83
View File
@@ -2,7 +2,8 @@ package streams
import (
"encoding/json"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"errors"
"github.com/AlexxIT/go2rtc/pkg/core"
"strings"
"sync"
"time"
@@ -20,20 +21,95 @@ const (
)
type Producer struct {
streamer.Element
core.Listener
url string
template string
element streamer.Producer
conn core.Producer
receivers []*core.Receiver
senders []*core.Receiver
lastErr error
tracks []*streamer.Track
state state
mu sync.Mutex
workerID int
}
func (p *Producer) Dial() error {
p.mu.Lock()
defer p.mu.Unlock()
if p.state == stateNone {
conn, err := GetProducer(p.url)
if err != nil {
return err
}
p.conn = conn
p.state = stateMedias
}
return nil
}
func (p *Producer) GetMedias() []*core.Media {
p.mu.Lock()
defer p.mu.Unlock()
return p.conn.GetMedias()
}
func (p *Producer) GetTrack(media *core.Media, codec *core.Codec) (*core.Receiver, error) {
p.mu.Lock()
defer p.mu.Unlock()
if p.state == stateNone {
return nil, errors.New("get track from none state")
}
for _, track := range p.receivers {
if track.Codec == codec {
return track, nil
}
}
track, err := p.conn.GetTrack(media, codec)
if err != nil {
return nil, err
}
p.receivers = append(p.receivers, track)
if p.state == stateMedias {
p.state = stateTracks
}
return track, nil
}
func (p *Producer) AddTrack(media *core.Media, codec *core.Codec, track *core.Receiver) error {
p.mu.Lock()
defer p.mu.Unlock()
if p.state == stateNone {
return errors.New("add track from none state")
}
if err := p.conn.(core.Consumer).AddTrack(media, codec, track); err != nil {
return err
}
p.senders = append(p.senders, track)
if p.state == stateMedias {
p.state = stateTracks
}
return nil
}
func (p *Producer) SetSource(s string) {
if p.template == "" {
p.template = p.url
@@ -41,64 +117,12 @@ func (p *Producer) SetSource(s string) {
p.url = strings.Replace(p.template, "{input}", s, 1)
}
func (p *Producer) GetMedias() []*streamer.Media {
p.mu.Lock()
defer p.mu.Unlock()
if p.state == stateNone {
log.Debug().Msgf("[streams] probe producer url=%s", p.url)
p.element, p.lastErr = GetProducer(p.url)
if p.lastErr != nil || p.element == nil {
log.Error().Err(p.lastErr).Str("url", p.url).Caller().Send()
return nil
}
p.state = stateMedias
}
// if element in reconnect state
if p.element == nil {
return nil
}
return p.element.GetMedias()
}
func (p *Producer) GetTrack(media *streamer.Media, codec *streamer.Codec) *streamer.Track {
p.mu.Lock()
defer p.mu.Unlock()
if p.state == stateNone {
return nil
}
for _, track := range p.tracks {
if track.Codec == codec {
return track
}
}
track := p.element.GetTrack(media, codec)
if track == nil {
return nil
}
p.tracks = append(p.tracks, track)
if p.state == stateMedias {
p.state = stateTracks
}
return track
}
func (p *Producer) MarshalJSON() ([]byte, error) {
if p.element != nil {
return json.Marshal(p.element)
if p.conn != nil {
return json.Marshal(p.conn)
}
info := streamer.Info{URL: p.url}
info := core.Info{URL: p.url}
return json.Marshal(info)
}
@@ -117,11 +141,11 @@ func (p *Producer) start() {
p.state = stateStart
p.workerID++
go p.worker(p.element, p.workerID)
go p.worker(p.conn, p.workerID)
}
func (p *Producer) worker(element streamer.Producer, workerID int) {
if err := element.Start(); err != nil {
func (p *Producer) worker(conn core.Producer, workerID int) {
if err := conn.Start(); err != nil {
p.mu.Lock()
closed := p.workerID != workerID
p.mu.Unlock()
@@ -147,9 +171,8 @@ func (p *Producer) reconnect(workerID int) {
log.Debug().Msgf("[streams] reconnect to url=%s", p.url)
p.element, p.lastErr = GetProducer(p.url)
if p.lastErr != nil || p.element == nil {
log.Debug().Msgf("[streams] producer=%s", p.lastErr)
if err := p.Dial(); err != nil {
log.Debug().Msgf("[streams] producer=%s", err)
// TODO: dynamic timeout
time.AfterFunc(30*time.Second, func() {
p.reconnect(workerID)
@@ -157,27 +180,37 @@ func (p *Producer) reconnect(workerID int) {
return
}
medias := p.element.GetMedias()
for _, media := range p.conn.GetMedias() {
switch media.Direction {
case core.DirectionRecvonly:
for _, receiver := range p.receivers {
codec := media.MatchCodec(receiver.Codec)
if codec == nil {
continue
}
// convert all old producer tracks to new tracks
for i, oldTrack := range p.tracks {
// match new element medias with old track codec
for _, media := range medias {
codec := media.MatchCodec(oldTrack.Codec)
if codec == nil {
continue
track, err := p.conn.GetTrack(media, codec)
if err != nil {
continue
}
receiver.Replace(track)
break
}
// move sink from old track to new track
newTrack := p.element.GetTrack(media, codec)
newTrack.GetSink(oldTrack)
p.tracks[i] = newTrack
case core.DirectionSendonly:
for _, sender := range p.senders {
codec := media.MatchCodec(sender.Codec)
if codec == nil {
continue
}
break
_ = p.conn.(core.Consumer).AddTrack(media, codec, sender)
}
}
}
go p.worker(p.element, workerID)
go p.worker(p.conn, workerID)
}
func (p *Producer) stop() {
@@ -197,11 +230,12 @@ func (p *Producer) stop() {
log.Debug().Msgf("[streams] stop producer url=%s", p.url)
if p.element != nil {
_ = p.element.Stop()
p.element = nil
if p.conn != nil {
_ = p.conn.Stop()
p.conn = nil
}
p.state = stateNone
p.tracks = nil
p.receivers = nil
p.senders = nil
}
+63 -92
View File
@@ -4,7 +4,7 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/AlexxIT/go2rtc/pkg/core"
"strings"
"sync"
"sync/atomic"
@@ -12,12 +12,12 @@ import (
type Stream struct {
producers []*Producer
consumers []*Consumer
consumers []core.Consumer
mu sync.Mutex
requests int32
}
func NewStream(source interface{}) *Stream {
func NewStream(source any) *Stream {
switch source := source.(type) {
case string:
s := new(Stream)
@@ -38,7 +38,7 @@ func NewStream(source interface{}) *Stream {
case nil:
return new(Stream)
default:
panic("wrong source type")
panic(core.Caller())
}
}
@@ -48,57 +48,71 @@ func (s *Stream) SetSource(source string) {
}
}
func (s *Stream) AddConsumer(cons streamer.Consumer) (err error) {
func (s *Stream) AddConsumer(cons core.Consumer) (err error) {
// support for multiple simultaneous requests from different consumers
atomic.AddInt32(&s.requests, 1)
ic := len(s.consumers)
consumer := &Consumer{element: cons}
var producers []*Producer // matched producers for consumer
var codecs string
// Step 1. Get consumer medias
for icc, consMedia := range cons.GetMedias() {
log.Trace().Stringer("media", consMedia).
Msgf("[streams] consumer=%d candidate=%d", ic, icc)
for _, consMedia := range cons.GetMedias() {
producers:
for ip, prod := range s.producers {
// Step 2. Get producer medias (not tracks yet)
for ipc, prodMedia := range prod.GetMedias() {
log.Trace().Stringer("media", prodMedia).
Msgf("[streams] producer=%d candidate=%d", ip, ipc)
for _, prod := range s.producers {
if err = prod.Dial(); err != nil {
continue
}
// Step 2. Get producer medias (not tracks yet)
for _, prodMedia := range prod.GetMedias() {
collectCodecs(prodMedia, &codecs)
// Step 3. Match consumer/producer codecs list
prodCodec := prodMedia.MatchMedia(consMedia)
if prodCodec != nil {
log.Trace().Stringer("codec", prodCodec).
Msgf("[streams] match producer:%d:%d => consumer:%d:%d", ip, ipc, ic, icc)
prodCodec, consCodec := prodMedia.MatchMedia(consMedia)
if prodCodec == nil {
continue
}
// Step 4. Get producer track
prodTrack := prod.GetTrack(prodMedia, prodCodec)
if prodTrack == nil {
log.Warn().Str("url", prod.url).Msg("[streams] can't get track")
var track *core.Receiver
switch prodMedia.Direction {
case core.DirectionRecvonly:
// Step 4. Get recvonly track from producer
if track, err = prod.GetTrack(prodMedia, prodCodec); err != nil {
log.Info().Err(err).Msg("[streams] can't get track")
continue
}
// Step 5. Add track to consumer
if err = cons.AddTrack(consMedia, consCodec, track); err != nil {
log.Info().Err(err).Msg("[streams] can't add track")
continue
}
// Step 5. Add track to consumer and get new track
consTrack := consumer.element.AddTrack(consMedia, prodTrack)
consumer.tracks = append(consumer.tracks, consTrack)
producers = append(producers, prod)
if !consMedia.MatchAll() {
break producers
case core.DirectionSendonly:
// Step 4. Get recvonly track from consumer (backchannel)
if track, err = cons.(core.Producer).GetTrack(consMedia, consCodec); err != nil {
log.Info().Err(err).Msg("[streams] can't get track")
continue
}
// Step 5. Add track to producer
if err = prod.AddTrack(prodMedia, prodCodec, track); err != nil {
log.Info().Err(err).Msg("[streams] can't add track")
continue
}
}
producers = append(producers, prod)
if !consMedia.MatchAll() {
break producers
}
}
}
}
// stop producers if they don't have readers
if atomic.AddInt32(&s.requests, -1) == 0 {
s.stopProducers()
}
@@ -118,7 +132,7 @@ func (s *Stream) AddConsumer(cons streamer.Consumer) (err error) {
}
s.mu.Lock()
s.consumers = append(s.consumers, consumer)
s.consumers = append(s.consumers, cons)
s.mu.Unlock()
// there may be duplicates, but that's not a problem
@@ -129,16 +143,13 @@ func (s *Stream) AddConsumer(cons streamer.Consumer) (err error) {
return nil
}
func (s *Stream) RemoveConsumer(cons streamer.Consumer) {
func (s *Stream) RemoveConsumer(cons core.Consumer) {
_ = cons.Stop()
s.mu.Lock()
for i, consumer := range s.consumers {
if consumer.element == cons {
// remove consumer pads from all producers
for _, track := range consumer.tracks {
track.Unbind()
}
// remove consumer from slice
s.removeConsumer(i)
if consumer == cons {
s.consumers = append(s.consumers[:i], s.consumers[i+1:]...)
break
}
}
@@ -147,18 +158,18 @@ func (s *Stream) RemoveConsumer(cons streamer.Consumer) {
s.stopProducers()
}
func (s *Stream) AddProducer(prod streamer.Producer) {
producer := &Producer{element: prod, state: stateExternal}
func (s *Stream) AddProducer(prod core.Producer) {
producer := &Producer{conn: prod, state: stateExternal}
s.mu.Lock()
s.producers = append(s.producers, producer)
s.mu.Unlock()
}
func (s *Stream) RemoveProducer(prod streamer.Producer) {
func (s *Stream) RemoveProducer(prod core.Producer) {
s.mu.Lock()
for i, producer := range s.producers {
if producer.element == prod {
s.removeProducer(i)
if producer.conn == prod {
s.producers = append(s.producers[:i], s.producers[i+1:]...)
break
}
}
@@ -169,8 +180,8 @@ func (s *Stream) stopProducers() {
s.mu.Lock()
producers:
for _, producer := range s.producers {
for _, track := range producer.tracks {
if track.HasSink() {
for _, track := range producer.receivers {
if len(track.Senders()) > 0 {
continue producers
}
}
@@ -179,20 +190,6 @@ producers:
s.mu.Unlock()
}
//func (s *Stream) Active() bool {
// if len(s.consumers) > 0 {
// return true
// }
//
// for _, prod := range s.producers {
// if prod.element != nil {
// return true
// }
// }
//
// return false
//}
func (s *Stream) MarshalJSON() ([]byte, error) {
if !s.mu.TryLock() {
log.Warn().Msgf("[streams] json locked")
@@ -200,8 +197,8 @@ func (s *Stream) MarshalJSON() ([]byte, error) {
}
var info struct {
Producers []*Producer `json:"producers"`
Consumers []*Consumer `json:"consumers"`
Producers []*Producer `json:"producers"`
Consumers []core.Consumer `json:"consumers"`
}
info.Producers = s.producers
info.Consumers = s.consumers
@@ -211,40 +208,14 @@ func (s *Stream) MarshalJSON() ([]byte, error) {
return json.Marshal(info)
}
func (s *Stream) removeConsumer(i int) {
switch {
case len(s.consumers) == 1: // only one element
s.consumers = nil
case i == 0: // first element
s.consumers = s.consumers[1:]
case i == len(s.consumers)-1: // last element
s.consumers = s.consumers[:i]
default: // middle element
s.consumers = append(s.consumers[:i], s.consumers[i+1:]...)
}
}
func (s *Stream) removeProducer(i int) {
switch {
case len(s.producers) == 1: // only one element
s.producers = nil
case i == 0: // first element
s.producers = s.producers[1:]
case i == len(s.producers)-1: // last element
s.producers = s.producers[:i]
default: // middle element
s.producers = append(s.producers[:i], s.producers[i+1:]...)
}
}
func collectCodecs(media *streamer.Media, codecs *string) {
if media.Direction == streamer.DirectionRecvonly {
func collectCodecs(media *core.Media, codecs *string) {
if media.Direction == core.DirectionRecvonly {
return
}
for _, codec := range media.Codecs {
name := codec.Name
if name == streamer.CodecAAC {
if name == core.CodecAAC {
name = "AAC"
}
if strings.Contains(*codecs, name) {
+2 -2
View File
@@ -2,7 +2,7 @@ package tapo
import (
"github.com/AlexxIT/go2rtc/cmd/streams"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/tapo"
)
@@ -10,7 +10,7 @@ func Init() {
streams.HandleFunc("tapo", handle)
}
func handle(url string) (streamer.Producer, error) {
func handle(url string) (core.Producer, error) {
conn := tapo.NewClient(url)
if err := conn.Dial(); err != nil {
return nil, err
+12 -13
View File
@@ -4,7 +4,6 @@ import (
"errors"
"github.com/AlexxIT/go2rtc/cmd/api"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/AlexxIT/go2rtc/pkg/webrtc"
"github.com/gorilla/websocket"
pion "github.com/pion/webrtc/v3"
@@ -14,7 +13,7 @@ import (
"time"
)
func streamsHandler(url string) (streamer.Producer, error) {
func streamsHandler(url string) (core.Producer, error) {
url = url[7:]
if i := strings.Index(url, "://"); i > 0 {
switch url[:i] {
@@ -29,7 +28,7 @@ func streamsHandler(url string) (streamer.Producer, error) {
// asyncClient can connect only to go2rtc server
// ex: ws://localhost:1984/api/ws?src=camera1
func asyncClient(url string) (streamer.Producer, error) {
func asyncClient(url string) (core.Producer, error) {
// 1. Connect to signalign server
ws, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
@@ -52,7 +51,7 @@ func asyncClient(url string) (streamer.Producer, error) {
prod := webrtc.NewConn(pc)
prod.Desc = "WebRTC/WebSocket async"
prod.Mode = streamer.ModeActiveProducer
prod.Mode = core.ModeActiveProducer
prod.Listen(func(msg any) {
switch msg := msg.(type) {
case pion.PeerConnectionState:
@@ -67,10 +66,10 @@ func asyncClient(url string) (streamer.Producer, error) {
}
})
medias := []*streamer.Media{
{Kind: streamer.KindVideo, Direction: streamer.DirectionRecvonly},
{Kind: streamer.KindAudio, Direction: streamer.DirectionRecvonly},
{Kind: streamer.KindAudio, Direction: streamer.DirectionSendonly},
medias := []*core.Media{
{Kind: core.KindVideo, Direction: core.DirectionRecvonly},
{Kind: core.KindAudio, Direction: core.DirectionRecvonly},
{Kind: core.KindAudio, Direction: core.DirectionSendonly},
}
// 3. Create offer
@@ -129,7 +128,7 @@ func asyncClient(url string) (streamer.Producer, error) {
// syncClient - support WebRTC-HTTP Egress Protocol (WHEP)
// ex: http://localhost:1984/api/webrtc?src=camera1
func syncClient(url string) (streamer.Producer, error) {
func syncClient(url string) (core.Producer, error) {
// 2. Create PeerConnection
pc, err := PeerConnection(true)
if err != nil {
@@ -139,11 +138,11 @@ func syncClient(url string) (streamer.Producer, error) {
prod := webrtc.NewConn(pc)
prod.Desc = "WebRTC/WHEP sync"
prod.Mode = streamer.ModeActiveProducer
prod.Mode = core.ModeActiveProducer
medias := []*streamer.Media{
{Kind: streamer.KindVideo, Direction: streamer.DirectionRecvonly},
{Kind: streamer.KindAudio, Direction: streamer.DirectionRecvonly},
medias := []*core.Media{
{Kind: core.KindVideo, Direction: core.DirectionRecvonly},
{Kind: core.KindAudio, Direction: core.DirectionRecvonly},
}
// 3. Create offer
+9 -10
View File
@@ -6,7 +6,6 @@ import (
"github.com/AlexxIT/go2rtc/cmd/app"
"github.com/AlexxIT/go2rtc/cmd/streams"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/AlexxIT/go2rtc/pkg/webrtc"
pion "github.com/pion/webrtc/v3"
"github.com/rs/zerolog"
@@ -87,16 +86,16 @@ var PeerConnection func(active bool) (*pion.PeerConnection, error)
func asyncHandler(tr *api.Transport, msg *api.Message) error {
var stream *streams.Stream
var mode streamer.Mode
var mode core.Mode
query := tr.Request.URL.Query()
if name := query.Get("src"); name != "" {
stream = streams.GetOrNew(name)
mode = streamer.ModePassiveConsumer
mode = core.ModePassiveConsumer
log.Debug().Str("src", name).Msg("[webrtc] new consumer")
} else if name = query.Get("dst"); name != "" {
stream = streams.Get(name)
mode = streamer.ModePassiveProducer
mode = core.ModePassiveProducer
log.Debug().Str("src", name).Msg("[webrtc] new producer")
}
@@ -124,9 +123,9 @@ func asyncHandler(tr *api.Transport, msg *api.Message) error {
return
}
switch mode {
case streamer.ModePassiveConsumer:
case core.ModePassiveConsumer:
stream.RemoveConsumer(conn)
case streamer.ModePassiveProducer:
case core.ModePassiveProducer:
stream.RemoveProducer(conn)
}
@@ -158,14 +157,14 @@ func asyncHandler(tr *api.Transport, msg *api.Message) error {
}
switch mode {
case streamer.ModePassiveConsumer:
case core.ModePassiveConsumer:
// 2. AddConsumer, so we get new tracks
if err = stream.AddConsumer(conn); err != nil {
log.Debug().Err(err).Msg("[webrtc] add consumer")
_ = conn.Close()
return err
}
case streamer.ModePassiveProducer:
case core.ModePassiveProducer:
stream.AddProducer(conn)
}
@@ -202,9 +201,9 @@ func ExchangeSDP(stream *streams.Stream, offer, desc, userAgent string) (answer
// create new webrtc instance
conn := webrtc.NewConn(pc)
conn.Desc = desc
conn.Mode = streamer.ModePassiveConsumer
conn.Mode = core.ModePassiveConsumer
conn.UserAgent = userAgent
conn.Listen(func(msg interface{}) {
conn.Listen(func(msg any) {
switch msg := msg.(type) {
case pion.PeerConnectionState:
if msg == pion.PeerConnectionStateClosed {
+2 -2
View File
@@ -3,7 +3,7 @@ package webrtc
import (
"encoding/json"
"github.com/AlexxIT/go2rtc/cmd/streams"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/webrtc"
pion "github.com/pion/webrtc/v3"
"io"
@@ -161,7 +161,7 @@ func inputWebRTC(w http.ResponseWriter, r *http.Request) {
// create new webrtc instance
prod := webrtc.NewConn(pc)
prod.Desc = "WebRTC/WHIP sync"
prod.Mode = streamer.ModePassiveProducer
prod.Mode = core.ModePassiveProducer
prod.UserAgent = r.UserAgent()
if err = prod.SetOffer(string(offer)); err != nil {
+1 -2
View File
@@ -8,7 +8,6 @@ import (
"github.com/AlexxIT/go2rtc/cmd/streams"
"github.com/AlexxIT/go2rtc/cmd/webrtc"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/AlexxIT/go2rtc/pkg/webtorrent"
"github.com/rs/zerolog"
"net/http"
@@ -142,7 +141,7 @@ func apiHandle(w http.ResponseWriter, r *http.Request) {
}
}
func streamHandle(rawURL string) (streamer.Producer, error) {
func streamHandle(rawURL string) (core.Producer, error) {
u, err := url.Parse(rawURL)
if err != nil {
return nil, err