streaming

Media streaming and broadcast systems in Go
Log | Files | Refs | README | LICENSE

cair.go (2815B)


      1 // Package cair provides a client to version 24.1 of the Cinegy Air
      2 // HTTP API as documented at
      3 // https://open.cinegy.com/products/air/24.1/cinegy-air-http-api/.
      4 //
      5 // Typical usage starts with creating a Client, then fetching the
      6 // current playlist by calling Playlist on Client. For example, to print
      7 // the names of all future items that are 3 minutes or less (e.g.
      8 // station breaks):
      9 //
     10 //	client := &Client{http.DefaultClient, "http://air.example.com:5521")
     11 //	playlist, err := client.Playlist("video")
     12 //	if err != nil {
     13 //		// handle error...
     14 //	}
     15 //	for _, it := range playlist.Items {
     16 //		if it.ScheduledAt.Before(time.Now()) {
     17 //			continue
     18 //		}
     19 //		if it.Duration <= 3*time.Minute {
     20 //			fmt.Println(it.Name)
     21 //		}
     22 //	}
     23 package cair
     24 
     25 import (
     26 	"encoding/xml"
     27 	"fmt"
     28 	"net/http"
     29 	"path"
     30 )
     31 
     32 const DefaultPort = 5521
     33 const defaultRoot = "http://127.0.0.1:5521"
     34 
     35 // Client is used to communicate with the Cinegy Air API.
     36 type Client struct {
     37 	*http.Client // http.DefaultClient if nil.
     38 	// Root is a HTTP URL pointing to the root from the Air API is served.
     39 	// If empty the client assumes the value "http://127.0.0.1:5521".
     40 	Root string
     41 }
     42 
     43 func (c *Client) get(path string) (*http.Response, error) {
     44 	if c.Client == nil {
     45 		c.Client = http.DefaultClient
     46 	}
     47 	if c.Root == "" {
     48 		c.Root = defaultRoot
     49 	}
     50 	return c.Get(c.Root + path)
     51 }
     52 
     53 // Playlist retrieves the current Playlist for the named device.
     54 // Supported names include:
     55 //   - "video"
     56 //   - "titler_0"
     57 //   - "logo"
     58 //   - "cg_[0-8]" e.g. cg_0, cg_1 ...
     59 //   - "cg_logo"
     60 //   - "gfx_[0-8]"
     61 //   - "audio"
     62 //
     63 // For all supported names, see Cinegy Air 24.1 HTTP API, chapter 1.
     64 func (c *Client) Playlist(device string) (*Playlist, error) {
     65 	resp, err := c.get(path.Join("/", device, "list"))
     66 	if err != nil {
     67 		return nil, err
     68 	}
     69 	defer resp.Body.Close()
     70 	if resp.StatusCode >= http.StatusBadRequest {
     71 		return nil, fmt.Errorf("non-OK status code from engine: %s", resp.Status)
     72 	}
     73 	return ParsePlaylist(resp.Body)
     74 }
     75 
     76 func (c *Client) Status() (*Status, error) {
     77 	resp, err := c.get("/videos/status")
     78 	if err != nil {
     79 		return nil, err
     80 	}
     81 	defer resp.Body.Close()
     82 	if resp.StatusCode >= http.StatusBadRequest {
     83 		return nil, fmt.Errorf("non-OK status code from engine: %s", resp.Status)
     84 	}
     85 	var st Status
     86 	if err := xml.NewDecoder(resp.Body).Decode(&st); err != nil {
     87 		return nil, fmt.Errorf("decode status: %w", err)
     88 	}
     89 	return &st, nil
     90 }
     91 
     92 type Status struct {
     93 	XMLName xml.Name `xml:"Status"`
     94 	Active  struct {
     95 		ID string `xml:"Id,attr"`
     96 	}
     97 	Cued struct {
     98 		ID string `xml:"Id,attr"`
     99 	}
    100 	License struct {
    101 		State string `xml:",attr"`
    102 	}
    103 	Output struct {
    104 		State string `xml:",attr"`
    105 	}
    106 	Client clientStatus
    107 }
    108 
    109 type clientStatus struct {
    110 	XMLName   xml.Name `xml:"Client"`
    111 	Connected string   `xml:",attr"`
    112 	Identity  string   `xml:",attr"`
    113 }