mirror of
				https://github.com/xaionaro-go/streamctl.git
				synced 2025-10-31 19:02:34 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			117 lines
		
	
	
		
			2.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			117 lines
		
	
	
		
			2.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package streams
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/AlexxIT/go2rtc/pkg/core"
 | |
| 	"github.com/facebookincubator/go-belt/tool/experimental/errmon"
 | |
| 	"github.com/facebookincubator/go-belt/tool/logger"
 | |
| 	"github.com/hashicorp/go-multierror"
 | |
| 	"github.com/xaionaro-go/streamctl/pkg/streamserver/server"
 | |
| )
 | |
| 
 | |
| type StreamForwarding struct {
 | |
| 	sync.Mutex
 | |
| 	Stream         *Stream
 | |
| 	Consumer       core.Consumer
 | |
| 	StreamHandler  *StreamHandler
 | |
| 	CancelFunc     context.CancelFunc
 | |
| 	URL            string
 | |
| 	TrafficCounter server.NumBytesReaderWroter
 | |
| }
 | |
| 
 | |
| func NewStreamForwarding(streamHandler *StreamHandler) *StreamForwarding {
 | |
| 	return &StreamForwarding{StreamHandler: streamHandler}
 | |
| }
 | |
| 
 | |
| func (sf *StreamForwarding) Start(
 | |
| 	ctx context.Context,
 | |
| 	s *Stream,
 | |
| 	url string,
 | |
| ) error {
 | |
| 	sf.Lock()
 | |
| 	defer sf.Unlock()
 | |
| 
 | |
| 	cons, trafficCounter, run, err := sf.StreamHandler.GetConsumer(url)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("unable to initialize consumer of '%s': %w", url, err)
 | |
| 	}
 | |
| 	sf.Stream = s
 | |
| 	sf.URL = url
 | |
| 	sf.Consumer = cons
 | |
| 	sf.TrafficCounter = trafficCounter
 | |
| 
 | |
| 	ctx, cancelFn := context.WithCancel(ctx)
 | |
| 	sf.CancelFunc = cancelFn
 | |
| 
 | |
| 	go func() {
 | |
| 		for {
 | |
| 			select {
 | |
| 			case <-ctx.Done():
 | |
| 				return
 | |
| 			default:
 | |
| 			}
 | |
| 			err = s.AddConsumer(cons)
 | |
| 			if errors.Is(err, ErrNoProducer{}) {
 | |
| 				logger.Debugf(ctx, "waiting for a producer")
 | |
| 				time.Sleep(time.Second)
 | |
| 				continue
 | |
| 			}
 | |
| 			if err != nil {
 | |
| 				logger.Errorf(ctx, "unable to add consumer of '%s': %v", sf.URL, err)
 | |
| 				time.Sleep(time.Second * 5)
 | |
| 				continue
 | |
| 			}
 | |
| 			break
 | |
| 		}
 | |
| 
 | |
| 		err := run(ctx)
 | |
| 		errmon.ObserveErrorCtx(ctx, err)
 | |
| 		s.RemoveConsumer(cons)
 | |
| 
 | |
| 		// TODO: more smart retry
 | |
| 		time.Sleep(5 * time.Second)
 | |
| 		_, err = s.Publish(ctx, url)
 | |
| 		if err != nil {
 | |
| 			errmon.ObserveErrorCtx(ctx, err)
 | |
| 			err := sf.Close()
 | |
| 			errmon.ObserveErrorCtx(ctx, err)
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (sf *StreamForwarding) IsClosed() bool {
 | |
| 	sf.Lock()
 | |
| 	defer sf.Unlock()
 | |
| 	return sf.isClosed()
 | |
| }
 | |
| 
 | |
| func (sf *StreamForwarding) isClosed() bool {
 | |
| 	return sf.CancelFunc == nil
 | |
| }
 | |
| 
 | |
| func (sf *StreamForwarding) Close() error {
 | |
| 	sf.Lock()
 | |
| 	defer sf.Unlock()
 | |
| 	if sf.isClosed() {
 | |
| 		return nil
 | |
| 	}
 | |
| 	sf.CancelFunc()
 | |
| 
 | |
| 	var result *multierror.Error
 | |
| 	err := sf.Consumer.Stop()
 | |
| 	if err != nil {
 | |
| 		result = multierror.Append(result, err)
 | |
| 	}
 | |
| 	sf.Stream = nil
 | |
| 	sf.Consumer = nil
 | |
| 	sf.CancelFunc = nil
 | |
| 	return result.ErrorOrNil()
 | |
| }
 | 
