mirror of
				https://github.com/owncast/owncast.git
				synced 2025-11-01 02:44:31 +08:00 
			
		
		
		
	Current broadcaster details admin api (#206)
* Add support for ending the inbound stream. Closes #191 * Add a simple success response to API requests * Store inbound broadcast details for admin purposes * Add /api/admin/broadcaster endpoint * Reset broadcaster on disconnect * Move controller to admin directory
This commit is contained in:
		
							
								
								
									
										38
									
								
								controllers/admin/inboundBroadcasterDetails.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								controllers/admin/inboundBroadcasterDetails.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | ||||
| package admin | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"net/http" | ||||
|  | ||||
| 	"github.com/gabek/owncast/controllers" | ||||
| 	"github.com/gabek/owncast/core" | ||||
| 	"github.com/gabek/owncast/models" | ||||
| 	"github.com/gabek/owncast/router/middleware" | ||||
| ) | ||||
|  | ||||
| // GetInboundBroadasterDetails gets the details of the inbound broadcaster | ||||
| func GetInboundBroadasterDetails(w http.ResponseWriter, r *http.Request) { | ||||
| 	middleware.EnableCors(&w) | ||||
|  | ||||
| 	broadcaster := core.GetBroadcaster() | ||||
| 	if broadcaster == nil { | ||||
| 		controllers.WriteSimpleResponse(w, false, "no broadcaster connected") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	response := inboundBroadasterDetailsResponse{ | ||||
| 		models.BaseAPIResponse{ | ||||
| 			true, | ||||
| 			"", | ||||
| 		}, | ||||
| 		broadcaster, | ||||
| 	} | ||||
|  | ||||
| 	w.Header().Set("Content-Type", "application/json") | ||||
| 	json.NewEncoder(w).Encode(response) | ||||
| } | ||||
|  | ||||
| type inboundBroadasterDetailsResponse struct { | ||||
| 	models.BaseAPIResponse | ||||
| 	Broadcaster *models.Broadcaster `json:"broadcaster"` | ||||
| } | ||||
| @ -21,6 +21,7 @@ var ( | ||||
| 	_storage      models.ChunkStorageProvider | ||||
| 	_cleanupTimer *time.Timer | ||||
| 	_yp           *yp.YP | ||||
| 	_broadcaster  *models.Broadcaster | ||||
| ) | ||||
|  | ||||
| //Start starts up the core processing | ||||
|  | ||||
| @ -17,6 +17,7 @@ import ( | ||||
| 	"github.com/gabek/owncast/config" | ||||
| 	"github.com/gabek/owncast/core" | ||||
| 	"github.com/gabek/owncast/core/ffmpeg" | ||||
| 	"github.com/gabek/owncast/models" | ||||
| 	"github.com/gabek/owncast/utils" | ||||
| 	"github.com/nareix/joy5/format/rtmp" | ||||
| ) | ||||
| @ -62,10 +63,36 @@ func Start() { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func setCurrentBroadcasterInfo(t flvio.Tag, remoteAddr string) { | ||||
| 	data, err := getInboundDetailsFromMetadata(t.DebugFields()) | ||||
| 	if err != nil { | ||||
| 		log.Errorln(err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	broadcaster := models.Broadcaster{ | ||||
| 		RemoteAddr: remoteAddr, | ||||
| 		Time:       time.Now(), | ||||
| 		StreamDetails: models.InboundStreamDetails{ | ||||
| 			Width:          data.Width, | ||||
| 			Height:         data.Height, | ||||
| 			VideoBitrate:   int(data.VideoBitrate), | ||||
| 			VideoCodec:     getVideoCodec(data.VideoCodec), | ||||
| 			VideoFramerate: data.VideoFramerate, | ||||
| 			AudioBitrate:   int(data.AudioBitrate), | ||||
| 			AudioCodec:     getAudioCodec(data.AudioCodec), | ||||
| 			Encoder:        data.Encoder, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	core.SetBroadcaster(broadcaster) | ||||
| } | ||||
|  | ||||
| func HandleConn(c *rtmp.Conn, nc net.Conn) { | ||||
| 	c.LogTagEvent = func(isRead bool, t flvio.Tag) { | ||||
| 		if t.Type == flvio.TAG_AMF0 { | ||||
| 			log.Tracef("%+v\n", t.DebugFields()) | ||||
| 			setCurrentBroadcasterInfo(t, nc.RemoteAddr().String()) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|  | ||||
							
								
								
									
										64
									
								
								core/rtmp/utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								core/rtmp/utils.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,64 @@ | ||||
| package rtmp | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"regexp" | ||||
|  | ||||
| 	"github.com/gabek/owncast/models" | ||||
| 	"github.com/nareix/joy5/format/flv/flvio" | ||||
| ) | ||||
|  | ||||
| func getInboundDetailsFromMetadata(metadata []interface{}) (models.RTMPStreamMetadata, error) { | ||||
| 	metadataComponentsString := fmt.Sprintf("%+v", metadata) | ||||
| 	re := regexp.MustCompile(`\{(.*?)\}`) | ||||
| 	submatchall := re.FindAllString(metadataComponentsString, 1) | ||||
|  | ||||
| 	if len(submatchall) == 0 { | ||||
| 		return models.RTMPStreamMetadata{}, errors.New("unable to parse inbound metadata") | ||||
| 	} | ||||
|  | ||||
| 	metadataJSONString := submatchall[0] | ||||
| 	var details models.RTMPStreamMetadata | ||||
| 	json.Unmarshal([]byte(metadataJSONString), &details) | ||||
| 	return details, nil | ||||
| } | ||||
|  | ||||
| func getAudioCodec(codec interface{}) string { | ||||
| 	var codecID float64 | ||||
| 	if assertedCodecID, ok := codec.(float64); ok { | ||||
| 		codecID = assertedCodecID | ||||
| 	} else { | ||||
| 		return codec.(string) | ||||
| 	} | ||||
|  | ||||
| 	switch codecID { | ||||
| 	case flvio.SOUND_MP3: | ||||
| 		return "MP3" | ||||
| 	case flvio.SOUND_AAC: | ||||
| 		return "AAC" | ||||
| 	case flvio.SOUND_SPEEX: | ||||
| 		return "Speex" | ||||
| 	} | ||||
|  | ||||
| 	return "Unknown" | ||||
| } | ||||
|  | ||||
| func getVideoCodec(codec interface{}) string { | ||||
| 	var codecID float64 | ||||
| 	if assertedCodecID, ok := codec.(float64); ok { | ||||
| 		codecID = assertedCodecID | ||||
| 	} else { | ||||
| 		return codec.(string) | ||||
| 	} | ||||
|  | ||||
| 	switch codecID { | ||||
| 	case flvio.VIDEO_H264: | ||||
| 		return "H.264" | ||||
| 	case flvio.VIDEO_H265: | ||||
| 		return "H.265" | ||||
| 	} | ||||
|  | ||||
| 	return "Unknown" | ||||
| } | ||||
| @ -49,6 +49,7 @@ func SetStreamAsConnected() { | ||||
| func SetStreamAsDisconnected() { | ||||
| 	_stats.StreamConnected = false | ||||
| 	_stats.LastDisconnectTime = utils.NullTime{time.Now(), true} | ||||
| 	_broadcaster = nil | ||||
|  | ||||
| 	if _yp != nil { | ||||
| 		_yp.Stop() | ||||
| @ -57,3 +58,12 @@ func SetStreamAsDisconnected() { | ||||
| 	ffmpeg.ShowStreamOfflineState() | ||||
| 	startCleanupTimer() | ||||
| } | ||||
|  | ||||
| // SetBroadcaster will store the current inbound broadcasting details | ||||
| func SetBroadcaster(broadcaster models.Broadcaster) { | ||||
| 	_broadcaster = &broadcaster | ||||
| } | ||||
|  | ||||
| func GetBroadcaster() *models.Broadcaster { | ||||
| 	return _broadcaster | ||||
| } | ||||
|  | ||||
							
								
								
									
										33
									
								
								models/broadcaster.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								models/broadcaster.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | ||||
| package models | ||||
|  | ||||
| import "time" | ||||
|  | ||||
| // Broadcaster represents the details around the inbound broadcasting connection. | ||||
| type Broadcaster struct { | ||||
| 	RemoteAddr    string               `json:"remoteAddr"` | ||||
| 	StreamDetails InboundStreamDetails `json:"streamDetails"` | ||||
| 	Time          time.Time            `json:"time"` | ||||
| } | ||||
|  | ||||
| type InboundStreamDetails struct { | ||||
| 	Width          int    `json:"width"` | ||||
| 	Height         int    `json:"height"` | ||||
| 	VideoFramerate int    `json:"framerate"` | ||||
| 	VideoBitrate   int    `json:"videoBitrate"` | ||||
| 	VideoCodec     string `json:"videoCodec"` | ||||
| 	AudioBitrate   int    `json:"audioBitrate"` | ||||
| 	AudioCodec     string `json:"audioCodec"` | ||||
| 	Encoder        string `json:"encoder"` | ||||
| } | ||||
|  | ||||
| // RTMPStreamMetadata is the raw metadata that comes in with a RTMP connection | ||||
| type RTMPStreamMetadata struct { | ||||
| 	Width          int         `json:"width"` | ||||
| 	Height         int         `json:"height"` | ||||
| 	VideoBitrate   float32     `json:"videodatarate"` | ||||
| 	VideoCodec     interface{} `json:"videocodecid"` | ||||
| 	VideoFramerate int         `json:"framerate"` | ||||
| 	AudioBitrate   float32     `json:"audiodatarate"` | ||||
| 	AudioCodec     interface{} `json:"audiocodecid"` | ||||
| 	Encoder        string      `json:"encoder"` | ||||
| } | ||||
| @ -51,6 +51,9 @@ func Start() error { | ||||
|  | ||||
| 	// Authenticated admin requests | ||||
|  | ||||
| 	// Current inbound broadcaster | ||||
| 	http.HandleFunc("/api/admin/broadcaster", middleware.RequireAdminAuth(admin.GetInboundBroadasterDetails)) | ||||
|  | ||||
| 	// Disconnect inbound stream | ||||
| 	http.HandleFunc("/api/admin/disconnect", middleware.RequireAdminAuth(admin.DisconnectInboundConnection)) | ||||
|  | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Gabe Kangas
					Gabe Kangas