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:
parent
236f25b772
commit
f4fdc6c951
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))
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user