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 4S 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.

Tip: A little more convenient workflow for crafting your templates is: use the trick from the Note above to have your Mirror connect both to the screen and as a USB drive to your computer at the same time.

Then every time you want to test your template, “eject” the Mirror USB drive, but don’t unplug it from the computer. This will force the Mirror to reload the templates. After a short while, it’ll automatically re-appear as a USB drive on your computer, so that you can continue your work.

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 the Display API (which gives you access to the built-in views), we’ll the use the Context API (which allows apps to send arbitrary context/data to Mirror).

iOS

In your main view controller, add:

class ViewController: UIViewController {
    var mirror: EMSDeviceMirror? = nil
    let deviceManager = EMSDeviceManager()

    override func viewDidLoad() {
        super.viewDidLoad()

        self.deviceManager.delegate = self
        self.deviceManager.startDeviceDiscovery(with: EMSDeviceFilterMirror())
        // or restrict scanning to Mirrors with specific identifiers:
        // EMSDeviceFilterMirror(identifierArray: ["1a2b...", "a1b2..."])
    }
}

This will have the device manager start scanning for nearby Mirrors.

Let’s add some handling for what to do with the scan results:

extension ViewController: EMSDeviceManagerDelegate {
    func deviceManager(_ manager: EMSDeviceManager,
                       didDiscoverDevices devices: [Any]) {
        guard let mirror = devices.first as? EMSDeviceMirror else { return }

        self.deviceManager.stopDeviceDiscovery()

        self.mirror = mirror
        self.mirror!.delegate = self
        self.mirror!.connect()
    }
}

Here, we just take the first Mirror we found and start connecting to it.

Finally, let’s add some handling for when the connection succeeds:

extension ViewController: EMSDeviceConnectableDelegate {
    func emsDeviceConnectionDidSucceed(_ device: EMSDeviceConnectable) {
        let dictionary = [
            "template": "flight_info",
            "name": "Joe",
            "gate": "G22"]
        self.mirror.display(dictionary) { (response, error) in
            if let error = error {
                print("Oh no! \(error)")
            } else {
                print("Success!")
            }
        }
    }

    // you might also want to implement `didFailConnectionWithError`,
    // to know when something goes wrong during the connection process
}

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:

private MirrorContextManager ctxMgr;

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

    this.ctxMgr = MirrorContextManager.createMirrorContextManager(this);

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

    this.ctxMgr.display(dictionary, Zone.NEAR, new DisplayCallback() {
        @Override public void onDataDisplayed() {
            // success! our template is shown, and the data has been delivered to it
        }

        @Override public void onFinish() {
            // device has disconnected from Mirror
        }

        @Override public void onFailure(MirrorException exception) {
            // oh no! something went wrong
        }
    });
}

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.

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