Trackpad to control android tv

Control android tv from the smartphone is realy convenient, but all solutions I found to do this involved having to tap a button on the screen. It works, but having to constantly look at the smartphone to not miss the button is a bit unconfortable.
I came up with this solution (it’s definetly not perfect, but it works): I created a simple trackpad that using javascript detects swipe gestures and call a nodered endpoint that based on the selected source send the right command (this way I can control android tv, satelite receiver and blue ray player).

If someone is interested here is the html code

<html>
    <header>
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
    </header>
    <style>
        body{
            margin: 0;
            padding: 0;
        }

        #touchsurface{
            width: 100vw;
            height: 100vh;
            padding: 0;
            margin: 0;
            background-color: #000;
        }

        #buttons{
            display: none;
            width: 100vw;
            height: 100vh;
            padding: 0;
            margin: 0;
        }

        .toggle{
            position: fixed;
            right: 2%;
            top: 2%;
            cursor: pointer;
            color: white;
        }

        #touchIcon{
            display: none;
        }

        .grid-container{
            height: 100vh;
            display: grid;
            grid-template-areas:
                '. up .'
                'left enter right'
                '. down .';
            background-color: #000;
        }

        .grid-container > i {
            background-color: rgba(255, 2550, 255, 0.1);
            text-align: center;
            color: white;
            border-radius: 20px;
            margin: auto;
            font-size: 25vmin;
        }

        .item1 { grid-area: up; cursor: pointer;}
        .item2 { grid-area: left; cursor: pointer;}
        .item3 { grid-area: enter; cursor: pointer;}
        .item4 { grid-area: right; cursor: pointer;}
        .item5 { grid-area: down; cursor: pointer;}
        
    </style>



    <span id="buttonsIcon" class="material-icons toggle" onclick="buttons()"> gamepad </span>
    <span id="touchIcon" class="material-icons toggle" onclick="touch()"> settings_overscan </span>

    <div id="touchsurface"></div>

    <div id="buttons">
        <div class="grid-container">
            <i onclick="up()" class="item1 material-icons">&#xe5ce;</i>
            <i onclick="left()" class="item2 material-icons">&#xe5cb;</i>
            <i onclick="enter()" class="item3 material-icons">&#xe061;</i>  
            <i onclick="right()" class="item4 material-icons">&#xe5cc;</i>
            <i onclick="down()" class="item5 material-icons">&#xe5cf;</i>
        </div>
    </div>

    <!-- trackpad / buttons -->
    <script>
        function buttons() {
        var touch = document.getElementById("touchsurface");
        var touchIcon = document.getElementById("touchIcon");
        var buttons = document.getElementById("buttons");
        var buttonsIcon = document.getElementById("buttonsIcon");
        
        touch.style.display = "none";
        buttonsIcon.style.display = "none";
        buttons.style.display = "block";
        touchIcon.style.display = "block";
        }

        function touch() {
        var touch = document.getElementById("touchsurface");
        var touchIcon = document.getElementById("touchIcon");
        var buttons = document.getElementById("buttons");
        var buttonsIcon = document.getElementById("buttonsIcon");
        
        touch.style.display = "block";
        buttonsIcon.style.display = "block";
        buttons.style.display = "none";
        touchIcon.style.display = "none";
        }
    </script>

    <!-- Button get -->
    <script>
        function up() {
            var xmlHttp = new XMLHttpRequest();
            xmlHttp.open( "GET", "https://nodered_url_here/endpoint/cinema_up", false );
            xmlHttp.send( null );
            return xmlHttp.responseText;
        }
        function down() {
            var xmlHttp = new XMLHttpRequest();
            xmlHttp.open( "GET", "https://nodered_url_here/endpoint/cinema_down", false );
            xmlHttp.send( null );
            return xmlHttp.responseText;
        }
        function left() {
            var xmlHttp = new XMLHttpRequest();
            xmlHttp.open( "GET", "https://nodered_url_here/endpoint/cinema_left", false );
            xmlHttp.send( null );
            return xmlHttp.responseText;
        }
        function right() {
            var xmlHttp = new XMLHttpRequest();
            xmlHttp.open( "GET", "https://nodered_url_here/endpoint/cinema_right", false );
            xmlHttp.send( null );
            return xmlHttp.responseText;
        }
        function enter() {
            var xmlHttp = new XMLHttpRequest();
            xmlHttp.open( "GET", "https://nodered_url_here/endpoint/cinema_enter", false );
            xmlHttp.send( null );
            return xmlHttp.responseText;
        }
    </script>

    <!--Swipe detect-->
    <script>
        function swipedetect(el, callback){

            var touchsurface = el,
            swipedir,
            startX,
            startY,
            distX,
            distY,
            threshold = 15, //required min distance traveled to be considered swipe
            restraint = 100, // maximum distance allowed at the same time in perpendicular direction
            allowedTime = 1000, // maximum time allowed to travel that distance
            elapsedTime,
            startTime,
            handleswipe = callback || function(swipedir){}

            touchsurface.addEventListener('touchstart', function(e){
                var touchobj = e.changedTouches[0]
                swipedir = 'none'
                dist = 0
                startX = touchobj.pageX
                startY = touchobj.pageY
                startTime = new Date().getTime() // record time when finger first makes contact with surface
                e.preventDefault()
            }, false)

            touchsurface.addEventListener('touchmove', function(e){
                e.preventDefault() // prevent scrolling when inside DIV
            }, false)

            touchsurface.addEventListener('touchend', function(e){
                var touchobj = e.changedTouches[0]
                distX = touchobj.pageX - startX // get horizontal dist traveled by finger while in contact with surface
                distY = touchobj.pageY - startY // get vertical dist traveled by finger while in contact with surface
                elapsedTime = new Date().getTime() - startTime // get time elapsed
                if (elapsedTime <= allowedTime){ // first condition for awipe met
                    if (Math.abs(distX) >= threshold && Math.abs(distY) <= restraint){ // 2nd condition for horizontal swipe met
                        swipedir = (distX < 0)? 'left' : 'right' // if dist traveled is negative, it indicates left swipe
                    }
                    else if (Math.abs(distY) >= threshold && Math.abs(distX) <= restraint){ // 2nd condition for vertical swipe met
                        swipedir = (distY < 0)? 'up' : 'down' // if dist traveled is negative, it indicates up swipe
                    }
                }
                handleswipe(swipedir)
                e.preventDefault()
            }, false)
            }

            //USAGE:
            /*
            var el = document.getElementById('someel')
            swipedetect(el, function(swipedir){
            swipedir contains either "none", "left", "right", "top", or "down"
            if (swipedir =='left')
                alert('You just swiped left!')
            })
            */
    </script>

    <!--Event listener-->     
    <script>
        window.addEventListener('load', function(){
            var el = document.getElementById('touchsurface')
            swipedetect(el, function(swipedir){
                // touchsurface.innerHTML = swipedir
                if(swipedir == 'up'){
                    var xmlHttp = new XMLHttpRequest();
                    xmlHttp.open( "GET", "https://nodered_url_here/endpoint/cinema_up", false ); // false for synchronous request
                    xmlHttp.send( null );
                    return xmlHttp.responseText;
                }
                if(swipedir == 'down'){
                    var xmlHttp = new XMLHttpRequest();
                    xmlHttp.open( "GET", "https://nodered_url_here/endpoint/cinema_down", false ); // false for synchronous request
                    xmlHttp.send( null );
                    return xmlHttp.responseText;
                }
                if(swipedir == 'left'){
                    var xmlHttp = new XMLHttpRequest();
                    xmlHttp.open( "GET", "https://nodered_url_here/endpoint/cinema_left", false ); // false for synchronous request
                    xmlHttp.send( null );
                    return xmlHttp.responseText;
                }
                if(swipedir == 'right'){
                    var xmlHttp = new XMLHttpRequest();
                    xmlHttp.open( "GET", "https://nodered_url_here/endpoint/cinema_right", false ); // false for synchronous request
                    xmlHttp.send( null );
                    return xmlHttp.responseText;
                }
                if(swipedir == 'none'){
                    var xmlHttp = new XMLHttpRequest();
                    xmlHttp.open( "GET", "https://nodered_url_here/endpoint/cinema_enter", false ); // false for synchronous request
                    xmlHttp.send( null );
                    return xmlHttp.responseText;
                }
            })
        }, false)
    </script>

</html>

As said it is not a perfect solution, I think that should be possible to make a custom card to do this, unfortunately I’m not a developer and making a custom card is a bit beyond my capabilities.

2 Likes

Where do you put this code?

The code is in a file called trackpad.html inside /config/www, then I added an iframe card to the dashboard with trackpad.html as the source.

here is the code for the card.

type: iframe
url: /local/trackpad.html?v1
aspect_ratio: 80%