Files
gortsplib/pkg/description/session.go
2023-08-22 20:11:37 +02:00

165 lines
3.6 KiB
Go

package description
import (
"fmt"
"strings"
psdp "github.com/pion/sdp/v3"
"github.com/bluenviron/gortsplib/v4/pkg/sdp"
"github.com/bluenviron/gortsplib/v4/pkg/url"
)
func atLeastOneHasMID(medias []*Media) bool {
for _, media := range medias {
if media.ID != "" {
return true
}
}
return false
}
func atLeastOneDoesntHaveMID(medias []*Media) bool {
for _, media := range medias {
if media.ID == "" {
return true
}
}
return false
}
func hasMediaWithID(medias []*Media, id string) bool {
for _, media := range medias {
if media.ID == id {
return true
}
}
return false
}
// SessionFECGroup is a FEC group.
type SessionFECGroup []string
// Session is the description of a RTSP stream.
type Session struct {
// Base URL of the stream (read only).
BaseURL *url.URL
// Title of the stream (optional).
Title string
// FEC groups (RFC5109).
FECGroups []SessionFECGroup
// Media streams.
Medias []*Media
}
// FindFormat finds a certain format among all the formats in all the medias of the stream.
// If the format is found, it is inserted into forma, and its media is returned.
func (d *Session) FindFormat(forma interface{}) *Media {
for _, media := range d.Medias {
ok := media.FindFormat(forma)
if ok {
return media
}
}
return nil
}
// Unmarshal decodes the description from SDP.
func (d *Session) Unmarshal(ssd *sdp.SessionDescription) error {
d.Title = string(ssd.SessionName)
if d.Title == " " {
d.Title = ""
}
d.Medias = make([]*Media, len(ssd.MediaDescriptions))
for i, md := range ssd.MediaDescriptions {
var m Media
err := m.Unmarshal(md)
if err != nil {
return fmt.Errorf("media %d is invalid: %v", i+1, err)
}
if m.ID != "" && hasMediaWithID(d.Medias[:i], m.ID) {
return fmt.Errorf("duplicate media IDs")
}
d.Medias[i] = &m
}
if atLeastOneHasMID(d.Medias) && atLeastOneDoesntHaveMID(d.Medias) {
return fmt.Errorf("media IDs sent partially")
}
for _, attr := range ssd.Attributes {
if attr.Key == "group" && strings.HasPrefix(attr.Value, "FEC ") {
group := SessionFECGroup(strings.Split(attr.Value[len("FEC "):], " "))
for _, id := range group {
if !hasMediaWithID(d.Medias, id) {
return fmt.Errorf("FEC group points to an invalid media ID: %v", id)
}
}
d.FECGroups = append(d.FECGroups, group)
}
}
return nil
}
// Marshal encodes the description in SDP.
func (d Session) Marshal(multicast bool) ([]byte, error) {
var sessionName psdp.SessionName
if d.Title != "" {
sessionName = psdp.SessionName(d.Title)
} else {
// RFC 4566: If a session has no meaningful name, the
// value "s= " SHOULD be used (i.e., a single space as the session name).
sessionName = psdp.SessionName(" ")
}
var address string
if multicast {
address = "224.1.0.0"
} else {
address = "0.0.0.0"
}
sout := &sdp.SessionDescription{
SessionName: sessionName,
Origin: psdp.Origin{
Username: "-",
NetworkType: "IN",
AddressType: "IP4",
UnicastAddress: "127.0.0.1",
},
// required by Darwin Sessioning Server
ConnectionInformation: &psdp.ConnectionInformation{
NetworkType: "IN",
AddressType: "IP4",
Address: &psdp.Address{Address: address},
},
TimeDescriptions: []psdp.TimeDescription{
{Timing: psdp.Timing{StartTime: 0, StopTime: 0}},
},
MediaDescriptions: make([]*psdp.MediaDescription, len(d.Medias)),
}
for i, media := range d.Medias {
sout.MediaDescriptions[i] = media.Marshal()
}
for _, group := range d.FECGroups {
sout.Attributes = append(sout.Attributes, psdp.Attribute{
Key: "group",
Value: "FEC " + strings.Join(group, " "),
})
}
return sout.Marshal()
}