Skip to content

Commit

Permalink
Add url load (partially working)
Browse files Browse the repository at this point in the history
  • Loading branch information
barnybug committed May 31, 2018
1 parent a92c32b commit 8f8491c
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 7 deletions.
1 change: 1 addition & 0 deletions apps.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
package cast

const AppMedia = "CC1AD845"
const AppURL = "5CB45E5A"
34 changes: 29 additions & 5 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type Client struct {
connection *controllers.ConnectionController
receiver *controllers.ReceiverController
media *controllers.MediaController
url *controllers.URLController

Events chan events.Event
}
Expand Down Expand Up @@ -128,28 +129,36 @@ func (c *Client) Receiver() *controllers.ReceiverController {
return c.receiver
}

func (c *Client) launchMediaApp(ctx context.Context) (string, error) {
func (c *Client) launchApp(ctx context.Context, appId string) (string, error) {
// get transport id
status, err := c.receiver.GetStatus(ctx)
if err != nil {
return "", err
}
app := status.GetSessionByAppId(AppMedia)
app := status.GetSessionByAppId(appId)
if app == nil {
// needs launching
status, err = c.receiver.LaunchApp(ctx, AppMedia)
status, err = c.receiver.LaunchApp(ctx, appId)
if err != nil {
return "", err
}
app = status.GetSessionByAppId(AppMedia)
app = status.GetSessionByAppId(appId)
}

if app == nil {
return "", errors.New("Failed to get media transport")
return "", errors.New("Failed to get transport")
}
return *app.TransportId, nil
}

func (c *Client) launchMediaApp(ctx context.Context) (string, error) {
return c.launchApp(ctx, AppMedia)
}

func (c *Client) launchURLApp(ctx context.Context) (string, error) {
return c.launchApp(ctx, AppURL)
}

func (c *Client) IsPlaying(ctx context.Context) bool {
status, err := c.receiver.GetStatus(ctx)
if err != nil {
Expand Down Expand Up @@ -183,3 +192,18 @@ func (c *Client) Media(ctx context.Context) (*controllers.MediaController, error
}
return c.media, nil
}

func (c *Client) URL(ctx context.Context) (*controllers.URLController, error) {
if c.url == nil {
transportId, err := c.launchURLApp(ctx)
if err != nil {
return nil, err
}
conn := controllers.NewConnectionController(c.conn, c.Events, DefaultSender, transportId)
if err := conn.Start(ctx); err != nil {
return nil, err
}
c.url = controllers.NewURLController(c.conn, c.Events, DefaultSender, transportId)
}
return c.url, nil
}
23 changes: 21 additions & 2 deletions cmd/cast/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,18 @@ func main() {
},
},
},
{
Name: "url",
Usage: "url commands (chromecast tv only)",
Subcommands: []cli.Command{
{
Name: "load",
Usage: "load a url",
ArgsUsage: "load url",
Action: cliCommand,
},
},
},
{
Name: "volume",
Usage: "set current volume",
Expand Down Expand Up @@ -287,6 +299,7 @@ var minArgs = map[string]int{
"stop": 0,
"quit": 0,
"volume": 1,
"load": 1,
}

var maxArgs = map[string]int{
Expand All @@ -295,6 +308,7 @@ var maxArgs = map[string]int{
"stop": 0,
"quit": 0,
"volume": 1,
"load": 1,
}

func checkCommand(cmd string, args []string) bool {
Expand All @@ -311,8 +325,6 @@ func checkCommand(cmd string, args []string) bool {
return false
}
switch cmd {
case "play":

case "volume":
if err := validateFloat(args[0], 0.0, 1.0); err != nil {
fmt.Printf("Command '%s': %s\n", cmd, err)
Expand Down Expand Up @@ -379,6 +391,13 @@ func runCommand(ctx context.Context, client *cast.Client, cmd string, args []str
_, err := receiver.SetVolume(ctx, &volume)
checkErr(err)

case "load":
controller, err := client.URL(ctx)
checkErr(err)
url := args[0]
_, err = controller.LoadURL(ctx, url)
checkErr(err)

case "quit":
receiver := client.Receiver()
_, err := receiver.QuitApp(ctx)
Expand Down
149 changes: 149 additions & 0 deletions controllers/url.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package controllers

import (
"encoding/json"
"errors"
"fmt"
"time"

"golang.org/x/net/context"

"github.com/barnybug/go-cast/api"
"github.com/barnybug/go-cast/events"
"github.com/barnybug/go-cast/log"
"github.com/barnybug/go-cast/net"
)

type URLController struct {
interval time.Duration
channel *net.Channel
eventsCh chan events.Event
DestinationID string
URLSessionID int
}

const NamespaceURL = "urn:x-cast:com.url.cast"

var getURLStatus = net.PayloadHeaders{Type: "GET_STATUS"}

var commandURLLoad = net.PayloadHeaders{Type: "LOAD"}

type LoadURLCommand struct {
net.PayloadHeaders
URL string `json:"url"`
Type string `json:"type"`
}

type URLStatusURL struct {
ContentId string `json:"contentId"`
StreamType string `json:"streamType"`
ContentType string `json:"contentType"`
Duration float64 `json:"duration"`
}

func NewURLController(conn *net.Connection, eventsCh chan events.Event, sourceId, destinationID string) *URLController {
controller := &URLController{
channel: conn.NewChannel(sourceId, destinationID, NamespaceURL),
eventsCh: eventsCh,
DestinationID: destinationID,
}

controller.channel.OnMessage("URL_STATUS", controller.onStatus)

return controller
}

func (c *URLController) SetDestinationID(id string) {
c.channel.DestinationId = id
c.DestinationID = id
}

func (c *URLController) sendEvent(event events.Event) {
select {
case c.eventsCh <- event:
default:
log.Printf("Dropped event: %#v", event)
}
}

func (c *URLController) onStatus(message *api.CastMessage) {
response, err := c.parseStatus(message)
if err != nil {
log.Errorf("Error parsing status: %s", err)
}

for _, status := range response.Status {
c.sendEvent(*status)
}
}

func (c *URLController) parseStatus(message *api.CastMessage) (*URLStatusResponse, error) {
response := &URLStatusResponse{}

err := json.Unmarshal([]byte(*message.PayloadUtf8), response)

if err != nil {
return nil, fmt.Errorf("Failed to unmarshal status message:%s - %s", err, *message.PayloadUtf8)
}

for _, status := range response.Status {
c.URLSessionID = status.URLSessionID
}

return response, nil
}

type URLStatusResponse struct {
net.PayloadHeaders
Status []*URLStatus `json:"status,omitempty"`
}

type URLStatus struct {
net.PayloadHeaders
URLSessionID int `json:"mediaSessionId"`
PlaybackRate float64 `json:"playbackRate"`
PlayerState string `json:"playerState"`
CurrentTime float64 `json:"currentTime"`
SupportedURLCommands int `json:"supportedURLCommands"`
Volume *Volume `json:"volume,omitempty"`
URL *URLStatusURL `json:"media"`
CustomData map[string]interface{} `json:"customData"`
RepeatMode string `json:"repeatMode"`
IdleReason string `json:"idleReason"`
}

func (c *URLController) Start(ctx context.Context) error {
_, err := c.GetStatus(ctx)
return err
}

func (c *URLController) GetStatus(ctx context.Context) (*URLStatusResponse, error) {
message, err := c.channel.Request(ctx, &getURLStatus)
if err != nil {
return nil, fmt.Errorf("Failed to get receiver status: %s", err)
}

return c.parseStatus(message)
}

func (c *URLController) LoadURL(ctx context.Context, url string) (*api.CastMessage, error) {
message, err := c.channel.Request(ctx, &LoadURLCommand{
PayloadHeaders: commandURLLoad,
URL: url,
Type: "loc",
})
if err != nil {
return nil, fmt.Errorf("Failed to send load command: %s", err)
}

response := &net.PayloadHeaders{}
err = json.Unmarshal([]byte(*message.PayloadUtf8), response)
if err != nil {
return nil, err
}
if response.Type == "LOAD_FAILED" {
return nil, errors.New("Load URL failed")
}

return message, nil
}

0 comments on commit 8f8491c

Please sign in to comment.