Introducing Ente Cast

May 12, 2024
james@ente.io
A family of yellow ducklings sitting on a blue couch in front of an old-fashioned TV with a pair of antennae displaying two photograph icons. Text with large font at the top half of the image reads 'Cast albums to TV with Ente.'

Have you ever been to one of those big events or receptions, like weddings, graduation ceremonies or birthday parties? Usually, there will be a slideshow of photos to show the good times. But unfortunately, this slideshow functionality has largely only existed on the big tech photo storage platforms, like iCloud Photos and Google Photos. Being restricted to these platforms comes at a cost of privacy and security. Unlike Ente, your photos may not be end-to-end encrypted by default, leaving them vulnerable to all types of nasty outcomes. Remember the big iCloud photo library leak that lead to numerous celebrity scandals? Yeah, not exactly something you want to happen on your big day.

Fear not though, as Ente now supports photo slideshows! Even though all Ente photo libraries are end-to-end encrypted, slideshow functionality isn't compromised. I'd even argue it's much simpler than the big platforms as our implementation works on any screen, not just Apple TV or Google TV -- so you're not locked into any ecosystem.

Let's get to casting!

  1. Curate an album of your favourite photos!
A screenshot of an album title saying 'Canada's Wonderland 20...'

Before we get anywhere, we need the perfect photo album to show off. For example, if it's a wedding, find all the lighthearted photos you can with you and your partner. Graduation ceremony? Definitely include the flicks in your gown. Birthday party? Ask all your friends for the funny photos they've taken of you over the years.

Once you've uploaded them all to Ente (or, if they're already in your library), go to the Albums tab (second from the left-hand side of the bottom navigation bar), click the + icon and give it a name.

  1. Initiate slideshow mode!
cast dialog

After creating your album, simply open it by clicking on its cover square. Then, click the three dots menu at the top right of the screen. There, you'll find a new "Play album on TV" button with a TV icon right next to it. Click on it.

A menu will pop up asking you to choose between two modes, "Auto pair" and "Pair with PIN". If you have a Chromecast enabled device, like a smart TV, on the same network as your mobile device, click on "Auto pair." Otherwise, click "Pair with PIN."

  1. Get those photos on your TV!

If you selected "Auto pair," just find the TV in the list and choose it. If you selected "Pair with PIN," open up cast.ente.io on the device you want to play your photos on and type in the 6-digit code that appears on screen into the mobile app.

Upon pairing, you should see all your photos playing on the selected device within a few seconds. And voila, you can watch all your photos on the big screen, and the slideshow will keep playing until you turn it off.

The best part is, all parts of this interaction, from pairing to playback, is all end-to-end encrypted. We specifically designed this process to be as frictionless as possible, so all of the encryption processes that happen in the background are abstracted.

We hope you choose Ente for your next big event's photo slideshow! Be sure to send a pic of us at your event and we'll feature you on our socials.


Fields notes of developing a Cast app

Before developing Ente Cast, nobody on our team had any experience developing software for Chromecasts -- or any smart TVs for that matter. It was all new to us, but we love a good challenge.

Cast apps are a lie

You know that cast icon you see on a lot of Android apps, like YouTube and Netflix? From a first glance, it may seem like your phone would be launching the app, handling all the logic and simply projecting its display output to the TV over wireless, but in fact, that's not how it works at all.

When developing a cast integration, there's two parts of the equation: the sender and the receiver. The sender is, you guessed it, the device trying to cast media onto the TV. But the receiver, well that's actually a web app the TV fires up upon connection. Yep, that's right - it's all web apps, once again.

When your phone and TV find each other, they open up a secure bidirectional communication channel. The first step is the handshake where your phone will send some instructions on what to do. In our case, we give the TV the cast.ente.io URL.

Your TV does all the heavy lifting

After the TV successfully loads up the site, it will create a pair of public and private keys and upload the public key to our API server. This keypair allows for a secure exchange of sensitive information between your phone and the TV without requiring you to enter a long 32 character secret key on both devices. Once done, the server returns a 6-digit unique code to identify this keypair which is displayed at the center of the pairing page.

When you enter those 6 digits on your phone, it requests for the associated public key uploaded by your TV. Thanks to the nature of asymmetric cryptography, the public key is all your phone needs to encrypt information that only the TV can decrypt using its private key, which is kept private. Your phone encrypts the selected album's encryption key and shares it with your TV. With the album key, your TV will be able to decrypt all the photos within it.

Once it has received all the details it needs, your TV begins the slideshow where it will decrypt each photo, one-by-one, preparing the next photo in the queue in the background of each slide change to make it as seamless as possible.

An actual quick start guide

To be totally honest, the official Chromecast docs suck. So, here's a quick start guide that you'll actually be able to follow.

  1. Get your app registered with Google

Grab your Chromecast and make note of its software serial number. Your hardware serial number will not work!

Then, head to the Google Cast SDK Developer Console. You'll need to enter your application's name, its web address and then register your Chromecast device as a beta-testing device.

Once you've registered your Chromecast device in the console, wait 15 minutes or so and then restart the Chromecast.

  1. Load up the Chromecast SDK in your receiver app

Interacting with Chromecast's API cannot be done through normal browser functions, so instead, you'll need to supplement with Google's official SDK script.

If you're using HTML, you can do a simple script tag as follows.

<script src="https://www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js"></script>

If you're using a JavaScript framework, you can programmatically load it in by creating a script tag and appending it to the DOM.

const script = document.createElement("script");
script.src = "https://www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js";
script.addEventListener("load", () => {
    castReceiver.cast = cast;
    resolve(cast);
});
document.body.appendChild(script);
  1. Prepare the Chromecast session

The Chromecast SDK revolves around the "context". The context is kind of like your gateway to all things Chromecast. It represents the Chromecast device itself.

In code, the context is a class that can be instantiated like this:

// "cast" is a class that's automatically attached to the window object once the SDK is loaded.
const context = cast.framework.CastReceiverContext.getInstance();

Create an options object to configure how you want the Chromecast to act.

const options = new cast.framework.CastReceiverOptions();

In our case, we don't use any of the built-in player functionality, so we disabled them for performance optimization using options.skipPlayersLoad = true;.

Another important detail is that, by default, the Chromecast will automatically terminate your app if it's not using any of the built-in APIs after 60 seconds, so to override this annoying behaviour, use options.disableIdleTimeout = true;. When I was building the prototype for the Cast app, it took me way too long to figure this out 🤦‍♂️.

  1. Add some event listeners

To receive messages from the bidirectional communication channel setup between the phone and the Chromecast, you need to listen for messages. Google's SDK makes this pretty similar to how adding window event listeners works.

One difference is that you need to come up with a namespace identifier. In this context, the namespace allows your Chromecast to distinguish the types of messages it receives. Your namespace must be prefixed with urn:. For our application, we settled on a modest urn:x-cast:pair-request.

context.addCustomMessageListener(namespace, (message) => {})

Now, what is message comprised of you may ask? It contains two key fields: senderId and data. The senderId property is key to allowing your Chromecast to respond to messages it receives. Think of it like the "From" header in an email - it lets you use the reply button to send a message back to the person who sent you it. To send a reply to the sender, you can use context.sendCustomMessage(namespace, senderId, object);. That data field is probably self-explanatory.

  1. Wrap it all up in one line

Once you've gotten your context, configured the options to your liking and added all the event listeners you need, you can start it up.

context.start(options)
  1. Setup your sender

Your sender app will need to actually talk to the Chromecast to get the process going. Thankfully, this part is a lot easier.

Load up the sender SDK script.

In HTML:

<script src="https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"></script>

Programmatically:

const script = document.createElement("script");
script.src = "https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1";
  1. Get your sender to make the first move

The sender SDK, once loaded, will call a function attached to the window object called "__onGCastApiAvailable". Right after creating that script element, you want to write this function.

window.__onGCastApiAvailable = (isAvailable) => {
})

Within this function, you should get the cast context and pass in your application details.

cast.framework.CastContext.getInstance().setOptions({
    receiverApplicationId: applicationId,
    autoJoinPolicy: chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED,
})

For you React devs, we have a great example of using hooks to simplify this entire process. Check it out here.

Hopefully this guide has saved you a few hours of trial and error. If you have a success story, feel free to let us know via email or on our socials!