Build your own Mirror template

If built-in views are not enough, or if you want full control over the UI of your big-screen interaction, you can build your own Mirror template.

Mirror templates are HTML5 pages/web apps with access to Mirror JavaScript API, which allows them to receive data from mobile apps, and more. Templates are stored on Mirror’s flash memory.

We call them templates, because you can think of them as regular HTML5 pages that have some “blanks” to be filled in. For example, when writing a quiz game, the blank could be the question that the host’s mobile app sends to Mirror, so that all participants can see it on the big screen.

In this guide, we’ll show you how to build a Mirror template, and how to send data to it from a mobile app.

What’s ahead (aka Table of Contents)

You will need…

  • 1 x iPhone 5 or newer, or 1 x smartphone with Android 4.3 or above
    • iOS and Android simulators don’t support Bluetooth
  • 1 x Estimote Mirror
  • 1 x TV or other display with HDMI and USB ports
    • you can twist and bend your Mirror to make it fit into the ports
    • alternatively, you can power it from your computer’s USB port, or use a USB wall charger
    • use a USB extension cable if needed

Create an HTML5 page

We’re going to build a simplified version of the “Flight Info” demo, which you can find in the Estimote app. The goal is to show the user’s name and gate when they approach the screen.

Create an index.html file and paste this simple HTML into it:

<!doctype html>
<html>
<head>
    <script><!-- we'll add our JavaScript here in a moment --></script>
</head>
<body>
    <h1>Hi, <span id="name"></span></h1>
    <p>Your flight is from gate <span id="gate"></span></p>
</body>
</html>

See the “blanks” we mentioned earlier? Hopefully it’s a bit more clear now why we call them templates!

Info: We’re building a simple single-page here, but you can use the full power of HTML5, CSS3 and JavaScript to build yours. You can put your stylesheet and scripts in separate files, embed jQuery or React, transpile JSX etc. At the end of the day, Mirror will load your template just as if it were a regular HTML5 web page hosted on a regular server. index.html is just the entry point.

Keep in mind though: Mirror has processing power equivalent to a mid-range smartphone. Try to avoid/limit CSS animations or heavy JS scripts.

Playing HD <video> is also not officially supported at this time—it might stutter.

Mirror JavaScript API

Inside a Mirror template, you’ll have access to a global mirror object, which allows you to use Mirror JavaScript API.

Let’s see it in action! Inside the <script></script> tag, add:

mirror.init();
mirror.listen(Mirror.Events.USER_DATA, {
    ondata: function(event) {
        const receivedData = event.data;
        document.getElementById("name").innerText = receivedData.name;
        document.getElementById("gate").innerText = receivedData.gate;
    },
    ondisconnect: function(event) {
        mirror.selectTemplate(null);
    }
});

What’s going on here?

  • First, we initialize the API. Always call mirror.init() first!

  • Then, we subscribe to a USER_DATA event. That’s the event generated when a nearby device connects to Mirror over Bluetooth and sends some data its way.

  • Finally, we define two handlers:

    • ondata, we use the incoming data to fill in our HTML placeholders.

    • ondisconnect, we switch away from our “flight info” template back to the default one. This ensures that if the user moves away and his/her device disconnects from Mirror, we’ll stop showing their flight info.

Tip: The USER_DATA event object has a handy property called from, which is a string identifier of the device that sent the data and/or disconnected.

This allows you to build templates that many users can interact with at the same time. For example:

  • in ondata, create a new div with the received flight info, and append it to the page; assign the from identifier to the div’s id attribute
  • in ondisconnect, use the from property to remove the matching div, to stop showing the disconnected user’s flight info

Simple as that, you’ve just upgraded your flight info example with support for multiple users!

Load the template onto your Mirror

Unplug the Mirror from your TV, and plug its USB port into your computer for a moment. After a minute or so, it will appear as an external USB drive. (Hey, remember pen-drives? :wink:)

Note: When Mirror is plugged to HDMI, it’s USB-drive interface is disabled. This is to prevent modern TVs from showing a “browse photos/videos from this USB?” popup, and obstructing your content. That’s why we’re asking you to unplug the Mirror from the TV before plugging it to the computer.

If you do want to use both HDMI and USB at the same time: plug Mirror to USB first, wait till it starts and shows the USB drive on your computer, and only then connect it back to HDMI.

Open the Mirror USB drive, and navigate to the templates directory. Create a new sub-directory with the name of your template—let’s say, my_flight_info. Copy your index.html file inside it. Voilà!

Plug the Mirror back into your TV. Uhh … nothing changed yet, because Mirror still loads the default template first, so let’s see how to switch to our new template, and populate it with data from a mobile app.

Important: Before unplugging Mirror from your computer, make sure to “safely remove”/“eject” it.

macOS especially has in-memory buffering turned on by default for external drives. This means that files aren’t immediately copied to the drive. If you simply unplug Mirror, your template might end up missing, incomplete, or corrupted.

Interlude: working with templates

Constantly moving the Mirror between the TV and the computer doesn’t work well when you’re building anything more complex than “hello world”. For template development, we recommend this workflow instead:

  1. Connect Mirror to the USB port of your computer, and wait till it shows up as an external drive.
  2. Connect the HDMI port to a screen.
  3. Build your template!
  4. When ready to test, “eject” the Mirror drive in your operating system, but don’t physically unplug Mirror.
  5. Mirror will reboot and reload the templates, and then reappear as an external drive. This usually takes about 20 seconds.

Info: When the “ejection-reload” happens, the ondata handler will be automatically re-played from cache. This is so that you don’t need to re-send all the data manually from your mobile app each time you want to test some changes to your template.

Trigger the template from a mobile app

If you haven’t yet integrated Mirror SDK into your app, follow these steps from the “cast content from a mobile app” guide.

We’ll be using the same SDK, but instead of passing a pre-defined view, we’ll pass arbitrary context/data to Mirror.

iOS

// create and store a strong reference to a MirrorClient
let mirrorClient = MirrorClient(appID: "your-app-id", appToken: "your-app-token")

override func viewDidLoad() {
    // prepare the dictionary filled with appropriate data
    let dictionary = [
            "template": "flight_info",
            "name": "Joe",
            "gate": "G22"]

    // prepare credentials for ProximitySDK
    let credentials = CloudCredentials(appID: "your-app-id", appToken: "your-app-token")
    
    // initialize a ProximityObserver instance
    self.proximityObserver = ProximityObserver(credentials: credentials, onError: { error in
        print("\(error)")
    })
        
    // create a mirror tagged zone
    let mirrorZone = ProximityZone(tag: "mirror", range: ProximityRange.near)
    mirrorZone.onEnter = { zoneContext in
        print("Enter mirror")

        // execute a display action once you get inside the zone
        self.mirrorClient.display(dictionary, onMirror: zoneContext.deviceIdentifier)
    }

}

There we go! Note the dictionary object: we set the data we want to send, and the template it should be delivered to. If the template is not currently shown on screen, Mirror will switch to it.

Android

In your main activity, add:


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    //Create a MirrorClient object
    final MirrorClient mirrorClient = new MirrorClient.Builder(this).build();

    //Build ProximityObserver with Cloud credentials
    //You can find here how to build it: https://developer.estimote.com/proximity/android-tutorial/#create-a-proximity-observer-object
    ProximityObserver proximityObserver =
                new ProximityObserverBuilder(applicationContext, cloudCredentials)
                        .withLowLatencyPowerMode()
                        .withTelemetryReportingDisabled()
                        .withEstimoteSecureMonitoringDisabled()
                        .onError(new Function1<Throwable, Unit>() {
                            @Override
                            public Unit invoke(Throwable throwable) {
                                return null;
                            }
                        }).build();

    //Define a dictionary
    final Dictionary dictionary = new Dictionary();
    dictionary.setTemplate("flight_info");
    dictionary.put("name", "Joe");
    dictionary.put("gate", "G22");

    //Define a near proximity zone
    ProximityZone nearZone = new ProximityZoneBuilder()
                .forTag("mirror")
                .inNearRange()
                .onEnter(new Function1<ProximityZoneContext, Unit>() {
                    @Override
                    public Unit invoke(ProximityZoneContext proximityZoneContext) {
                        mirrorClient
                        .forDevice(proximityZoneContext.getDeviceId())
                        .take(dictionary)
                        .display();
                    }
                })
                .build();
                
    //Start proximity observation for declared Proximity Zone
    ProximityObserver.Handler observationHandler = proximityObserver.startObserving(nearZone);           
}

@Override
protected void onDestroy() {
  mirrorClient.destroy();
  super.onDestroy();
}

There we go! Note the Dictionary object: we set the data we want to send, and the template it should be delivered to. If the template is not currently shown on screen, Mirror will switch to it.

Important: Remember to check if Bluetooth is on, and to ask for the ACCESS_COARSE_LOCATION runtime permission. This is required for Bluetooth scanning to work.

You can use Estimote’s support library called Mustard. Check Estimote’s guide how to request location permissions using our library.

For more information, see the Request App Permissions Android guide.

What else can templates do?

If you’ve configured your Mirror with WiFi, you can load assets from remote servers, and make XHR requests to your own backend (you’ll need to enable Cross-Origin Resource Sharing though).

Perhaps the most interesting other feature, however, is that you can subscribe your template to receive information about Bluetooth beacons in range of Mirror. So much, that it warrants its own guide:

Templates with beacon triggers