use std::borrow::Cow;
use std::cell::RefCell;
use std::convert::Into;
use std::io::Write;
use std::rc::Rc;
use std::str::FromStr;
use std::string::ToString;
use serde_json;
use cast::proxies;
use errors::Error;
use message_manager::{CastMessage, CastMessagePayload, MessageManager};
const CHANNEL_NAMESPACE: &'static str = "urn:x-cast:com.google.cast.receiver";
const MESSAGE_TYPE_LAUNCH: &'static str = "LAUNCH";
const MESSAGE_TYPE_STOP: &'static str = "STOP";
const MESSAGE_TYPE_GET_STATUS: &'static str = "GET_STATUS";
const MESSAGE_TYPE_SET_VOLUME: &'static str = "SET_VOLUME";
const MESSAGE_TYPE_RECEIVER_STATUS: &'static str = "RECEIVER_STATUS";
const MESSAGE_TYPE_LAUNCH_ERROR: &'static str = "LAUNCH_ERROR";
const APP_DEFAULT_MEDIA_RECEIVER_ID: &'static str = "CC1AD845";
const APP_BACKDROP_ID: &'static str = "E8C28D3C";
const APP_YOUTUBE_ID: &'static str = "233637DE";
#[derive(Debug)]
pub struct Volume {
pub level: Option<f32>,
pub muted: Option<bool>,
}
impl Into<Volume> for f32 {
fn into(self) -> Volume {
Volume {
level: Some(self),
muted: None,
}
}
}
impl Into<Volume> for bool {
fn into(self) -> Volume {
Volume {
level: None,
muted: Some(self),
}
}
}
impl Into<Volume> for (f32, bool) {
fn into(self) -> Volume {
Volume {
level: Some(self.0),
muted: Some(self.1),
}
}
}
#[derive(Debug)]
pub struct Application {
pub app_id: String,
pub session_id: String,
pub transport_id: String,
pub namespaces: Vec<String>,
pub display_name: String,
pub status_text: String,
}
#[derive(Debug)]
pub struct Status {
pub applications: Vec<Application>,
pub is_active_input: bool,
pub is_stand_by: bool,
pub volume: Volume,
}
#[derive(Debug)]
pub enum ReceiverResponse {
Status(Status),
LaunchError,
NotImplemented(String, serde_json::Value),
}
#[derive(Debug, PartialEq)]
pub enum CastDeviceApp {
DefaultMediaReceiver,
Backdrop,
YouTube,
Custom(String),
}
impl FromStr for CastDeviceApp {
type Err = ();
fn from_str(s: &str) -> Result<CastDeviceApp, ()> {
let app = match s {
APP_DEFAULT_MEDIA_RECEIVER_ID | "default" => CastDeviceApp::DefaultMediaReceiver,
APP_BACKDROP_ID | "backdrop" => CastDeviceApp::Backdrop,
APP_YOUTUBE_ID | "youtube" => CastDeviceApp::YouTube,
custom @ _ => CastDeviceApp::Custom(custom.to_owned())
};
Ok(app)
}
}
impl ToString for CastDeviceApp {
fn to_string(&self) -> String {
match *self {
CastDeviceApp::DefaultMediaReceiver => APP_DEFAULT_MEDIA_RECEIVER_ID.to_owned(),
CastDeviceApp::Backdrop => APP_BACKDROP_ID.to_owned(),
CastDeviceApp::YouTube => APP_YOUTUBE_ID.to_owned(),
CastDeviceApp::Custom(ref app_id) => app_id.to_owned(),
}
}
}
pub struct ReceiverChannel<'a, W> where W: Write {
sender: Cow<'a, str>,
receiver: Cow<'a, str>,
writer: Rc<RefCell<W>>,
}
impl<'a, W> ReceiverChannel<'a, W> where W: Write {
pub fn new<S>(sender: S, receiver: S, writer: Rc<RefCell<W>>)
-> ReceiverChannel<'a, W> where S: Into<Cow<'a, str>> {
ReceiverChannel {
sender: sender.into(),
receiver: receiver.into(),
writer: writer,
}
}
pub fn launch_app(&self, app: CastDeviceApp) -> Result<(), Error> {
let payload = try!(serde_json::to_string(
&proxies::receiver::AppLaunchRequest {
typ: MESSAGE_TYPE_LAUNCH.to_owned(),
request_id: 10000,
app_id: app.to_string(),
}));
MessageManager::send(&mut *self.writer.borrow_mut(), CastMessage {
namespace: CHANNEL_NAMESPACE.to_owned(),
source: self.sender.to_string(),
destination: self.receiver.to_string(),
payload: CastMessagePayload::String(payload),
})
}
pub fn stop_app<S>(&self, session_id: S) -> Result<(), Error> where S: Into<Cow<'a, str>> {
let payload = try!(serde_json::to_string(
&proxies::receiver::AppStopRequest {
typ: MESSAGE_TYPE_STOP.to_owned(),
request_id: 20000,
session_id: session_id.into(),
}));
MessageManager::send(&mut *self.writer.borrow_mut(), CastMessage {
namespace: CHANNEL_NAMESPACE.to_owned(),
source: self.sender.to_string(),
destination: self.receiver.to_string(),
payload: CastMessagePayload::String(payload),
})
}
pub fn get_status(&self) -> Result<(), Error> {
let payload = try!(serde_json::to_string(
&proxies::receiver::GetStatusRequest {
typ: MESSAGE_TYPE_GET_STATUS.to_owned(),
request_id: 30000,
}));
MessageManager::send(&mut *self.writer.borrow_mut(), CastMessage {
namespace: CHANNEL_NAMESPACE.to_owned(),
source: self.sender.to_string(),
destination: self.receiver.to_string(),
payload: CastMessagePayload::String(payload),
})
}
pub fn set_volume<T>(&self, volume: T) -> Result<(), Error> where T: Into<Volume> {
let volume = volume.into();
let payload = try!(serde_json::to_string(
&proxies::receiver::SetVolumeRequest {
typ: MESSAGE_TYPE_SET_VOLUME.to_owned(),
request_id: 40000,
volume: proxies::receiver::Volume {
level: volume.level,
muted: volume.muted,
},
}));
MessageManager::send(&mut *self.writer.borrow_mut(), CastMessage {
namespace: CHANNEL_NAMESPACE.to_owned(),
source: self.sender.to_string(),
destination: self.receiver.to_string(),
payload: CastMessagePayload::String(payload),
})
}
pub fn can_handle(&self, message: &CastMessage) -> bool {
message.namespace == CHANNEL_NAMESPACE
}
pub fn parse(&self, message: &CastMessage) -> Result<ReceiverResponse, Error> {
let reply = match message.payload {
CastMessagePayload::String(ref payload) => try!(
serde_json::from_str::<serde_json::Value>(payload)),
_ => return Err(Error::Internal("Binary payload is not supported!".to_owned())),
};
let message_type = reply.as_object()
.and_then(|object| object.get("type"))
.and_then(|property| property.as_string())
.unwrap_or("")
.to_owned();
let response = match message_type.as_ref() {
MESSAGE_TYPE_RECEIVER_STATUS => {
let status_reply: proxies::receiver::StatusReply = try!(
serde_json::value::from_value(reply));
let status = Status {
applications: status_reply.status.applications.iter().map(|ref app| {
Application {
app_id: app.app_id.clone(),
session_id: app.session_id.clone(),
transport_id: app.transport_id.clone(),
namespaces: app.namespaces.iter().map(|ref ns| ns.name.clone())
.collect::<Vec<String>>(),
display_name: app.display_name.clone(),
status_text: app.status_text.clone(),
}
}).collect::<Vec<Application>>(),
is_active_input: status_reply.status.is_active_input,
is_stand_by: status_reply.status.is_stand_by,
volume: Volume {
level: status_reply.status.volume.level,
muted: status_reply.status.volume.muted,
},
};
ReceiverResponse::Status(status)
},
MESSAGE_TYPE_LAUNCH_ERROR => ReceiverResponse::LaunchError,
_ => ReceiverResponse::NotImplemented(message_type.to_owned(), reply),
};
Ok(response)
}
}