Skip to content

Rust

This tutorial will show you basic usage of the FCast sender SDK from rust.

Project setup

First we need to create a new project:

cargo new --bin fcast-tutorial

And add the SDK as a dependency to your Cargo.toml:

[dependencies.fcast-sender-sdk]
version = "0.1.2"
default-features = false
features = ["chromecast", "discovery", "fcast"] # (1)!
  1. The crates features can be found here

At the core of the SDK we have a CastContext, let's create one:

use fcast_sender_sdk::context::CastContext;

fn main() {
    let ctx = CastContext::new().unwrap();
}

Receiver discovery

The next step will be to find and connect to a receiver device. The receiver documentation introduces Automatic Discovery which is what we'll use. To do this we need to use the start_discovery() helper function provided by the cast context. It expects a type which implements a callback trait:

...

use fcast_sender_sdk::{DeviceDiscovererEventHandler, device::DeviceInfo};
use std::sync::Arc;

struct DiscovererEventHandler {}

impl DeviceDiscovererEventHandler for DiscovererEventHandler {
    fn device_available(&self, device_info: DeviceInfo) {
        println!("Device available: {device_info:?}");
    }

    fn device_removed(&self, _device_name: String) {}

    fn device_changed(&self, _device_info: DeviceInfo) {}
}

fn main() {
    ...

    ctx.start_discovery(Arc::new(DiscovererEventHandler {}));

    std::thread::sleep(std::time::Duration::from_secs(5));
}

When running the program when there are receiver devices on your network the program output might look something like this:

Device available: DeviceInfo { name: "MyFCast", protocol: FCast, addresses: [V4 { o1: 192, o2: 168, o3: 50, o4: 173 }], port: 46899 }
Device available: DeviceInfo { name: "MyChromecast", protocol: Chromecast, addresses: [V4 { o1: 192, o2: 168, o3: 50, o4: 36 }], port: 8009 }

Now let's actually connect to a device. We'll do something very simple for the sake of this tutorial:

...

use std::sync::mpsc::{Sender, channel};

struct DiscovererEventHandler {
    device_tx: Sender<DeviceInfo>,
}

impl DeviceDiscovererEventHandler for DiscovererEventHandler {
    fn device_available(&self, device_info: DeviceInfo) {
        self.device_tx.send(device_info).unwrap();
    }

    ...
}

fn main() {
    ...

    let (device_tx, device_rx) = channel();
    ctx.start_discovery(Arc::new(DiscovererEventHandler { device_tx }));

We get the first discovered device and create a CastingDevice with the information:

    let device_info = device_rx.recv().unwrap();
    println!("Device info: {device_info:?}");
    let device = ctx.create_device_from_info(device_info);
}

Connecting

Now that we have a device we can initiate a connection which requires some boilerplate. Similar to the discoverer we need to create a type that implements a callback trait:

...

use fcast_sender_sdk::device::{
    DeviceConnectionState, DeviceEventHandler, DeviceInfo, KeyEvent,
    MediaEvent, PlaybackState, Source,
};

struct DevEventHandler {}

impl DeviceEventHandler for DevEventHandler {
    fn connection_state_changed(&self, state: DeviceConnectionState) {
        println!("Connection state changed: {state:?}");
    }

    fn volume_changed(&self, volume: f64) {
        println!("Volume changed: {volume}");
    }

    fn time_changed(&self, time: f64) {
        println!("Time changed: {time}");
    }

    fn playback_state_changed(&self, state: PlaybackState) {
        println!("Playback state changed: {state:?}");
    }

    fn duration_changed(&self, _duration: f64) {}

    fn speed_changed(&self, _speed: f64) {}

    fn source_changed(&self, _source: Source) {}

    fn key_event(&self, _event: KeyEvent) {}

    fn media_event(&self, _event: MediaEvent) {}

    fn playback_error(&self, _message: String) {}
}

fn main {
    ...

    device.connect(None, Arc::new(DevEventHandler {}), 1000).unwrap();

Sleep for a long time:

    std::thread::sleep(std::time::Duration::from_secs(600));
}

Running the program you might get a terminal output that looks like this:

Device info: DeviceInfo { name: "MyFCast", protocol: FCast, addresses: [V4 { o1: 192, o2: 168, o3: 50, o4: 173 }], port: 46899 }
Connection state changed: Connecting
Connection state changed: Connected { used_remote_addr: V4 { o1: 192, o2: 168, o3: 50, o4: 173 }, local_addr: V4 { o1: 192, o2: 168, o3: 50, o4: 173 } }

Cast

Let's try to cast something. We need add some more boilerplate:

use std::sync::Weak;
use fcast_sender_sdk::device::CastingDevice;

struct DevEventHandler {
    device_weak: Weak<dyn CastingDevice>
}

impl DeviceEventHandler for DevEventHandler {
    fn connection_state_changed(&self, state: DeviceConnectionState) {
        println!("Connection state changed: {state:?}");

        if matches!(state, DeviceConnectionState::Connected { .. }) {
            if let Some(device) = self.device_weak.upgrade() {

We wait until we receive the Connected connection state change event before loading our video:

                device.load(LoadRequest::Video {
                    content_type: "video/mp4".to_owned(),
                    url: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4".to_owned(),
                    resume_position: 0.0,
                    speed: None,
                    volume: None,
                    metadata: None,
                    request_headers: None,
                }).unwrap();
            }
        }
    }

    ...
}

fn main() {
    ...

    device
        .connect(
            None,
            Arc::new(DevEventHandler {
                device_weak: Arc::downgrade(&device),
            }),
            1000,
        )
        .unwrap();

    ...
}

If we run the program now we see that Big Buck Bunny is started playing on the receiver and our program is receiving updates of the playback state!

Device info: DeviceInfo { name: "MyFCast", protocol: FCast, addresses: [V4 { o1: 192, o2: 168, o3: 50, o4: 173 }], port: 46899 }
Connection state changed: Connecting
Connection state changed: Connected { used_remote_addr: V4 { o1: 192, o2: 168, o3: 50, o4: 173 }, local_addr: V4 { o1: 192, o2: 168, o3: 50, o4: 173 } }
Playback state changed: Playing
Volume changed: 1
Time changed: 1.204268
Time changed: 2.278132
Time changed: 3.34539
Time changed: 4.409731
Time changed: 5.479233
Time changed: 6.533175
Time changed: 7.579753

Other examples

A more in depth example can be found here.

Complete code

use std::sync::{Arc, Weak};

use fcast_sender_sdk::context::CastContext;
use fcast_sender_sdk::device::{
    CastingDevice, DeviceConnectionState, DeviceEventHandler, DeviceInfo,
    KeyEvent, LoadRequest, MediaEvent, PlaybackState, Source,
};
use fcast_sender_sdk::DeviceDiscovererEventHandler;

use std::sync::mpsc::{Sender, channel};

struct DevEventHandler {
    device_weak: Weak<dyn CastingDevice>,
}

impl DeviceEventHandler for DevEventHandler {
    fn connection_state_changed(&self, state: DeviceConnectionState) {
        println!("Connection state changed: {state:?}");

        if matches!(state, DeviceConnectionState::Connected { .. }) {
            if let Some(device) = self.device_weak.upgrade() {
                device.load(LoadRequest::Video {
                    content_type: "video/mp4".to_owned(),
                    url: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4".to_owned(),
                    resume_position: 0.0,
                    speed: None,
                    volume: None,
                    metadata: None,
                    request_headers: None,
                }).unwrap();
            }
        }
    }

    fn volume_changed(&self, volume: f64) {
        println!("Volume changed: {volume}");
    }

    fn time_changed(&self, time: f64) {
        println!("Time changed: {time}");
    }

    fn playback_state_changed(&self, state: PlaybackState) {
        println!("Playback state changed: {state:?}");
    }

    fn duration_changed(&self, _duration: f64) {}

    fn speed_changed(&self, _speed: f64) {}

    fn source_changed(&self, _source: Source) {}

    fn key_event(&self, _event: KeyEvent) {}

    fn media_event(&self, _event: MediaEvent) {}

    fn playback_error(&self, _message: String) {}
}

struct DiscovererEventHandler {
    device_tx: Sender<DeviceInfo>,
}

impl DeviceDiscovererEventHandler for DiscovererEventHandler {
    fn device_available(&self, device_info: DeviceInfo) {
        self.device_tx.send(device_info).unwrap();
    }

    fn device_removed(&self, _device_name: String) {}

    fn device_changed(&self, _device_info: DeviceInfo) {}
}

fn main() {
    let ctx = CastContext::new().unwrap();

    let (device_tx, device_rx) = channel();

    ctx.start_discovery(Arc::new(DiscovererEventHandler { device_tx }));

    let device_info = device_rx.recv().unwrap();
    println!("Device info: {device_info:?}");
    let device = ctx.create_device_from_info(device_info);

    device
        .connect(
            None,
            Arc::new(DevEventHandler {
                device_weak: Arc::downgrade(&device),
            }),
            1000,
        )
        .unwrap();

    std::thread::sleep(std::time::Duration::from_secs(600));
}