1
0
Fork 0
mirror of https://github.com/owncast/owncast.git synced 2024-10-28 10:09:39 +01: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:
Gabe Kangas 2020-10-02 00:12:47 -07:00 committed by GitHub
parent 236f25b772
commit f4fdc6c951
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 176 additions and 0 deletions

View 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"`
}

View file

@ -21,6 +21,7 @@ var (
_storage models.ChunkStorageProvider
_cleanupTimer *time.Timer
_yp *yp.YP
_broadcaster *models.Broadcaster
)
//Start starts up the core processing

View file

@ -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
View 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"
}

View file

@ -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
View 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"`
}

View file

@ -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))