Attempting an intercom

My wife has wanted an intercom in our house for years. Recently I switched from Indigo to HA for multiple reasons, but a bit one was the quality of the UI I could present to her. I was playing around with some frontend things and ran across browser_mod. After realizing it registered a media_player entity I started wondering if I could use that to give her her intercom using wall tablets I had already planned on putting in place.

I started playing with SIP to do this and make calls between browsers, but I wasn’t overly excited about the user interface (probably fixable) and didn’t want to add another thing I had to make sure was always running to keep the WAF high, so I tried to go back to simple.

Disclosure that I am not a developer, but I can copy/paste with the best of them. The below code seems to work (obviously needs a massive amount of cleanup/UI improvements to be at all useful), but curious if anyone sees an issue with what I am doing. My biggest concern is just the size of the audio recording being sent between the browsers, but I’m not really sure a way around that with this approach.

If nobody says this is the worst idea they’ve ever seen I’ll probably try to take this to the finish line and make it usable to everyone besides just me.

class IntercomCard extends HTMLElement {

    constructor() {
        super();
        this.attachShadow({ mode: 'open' });
    }

    set hass(hass) {
        if (!this._hass) {
            console.log("haSs")
            this._hass = hass;
            this.initRecordings()
        };
    }

    setConfig(config) {
        this.config = config;
        const root = this.shadowRoot;
        if (root.lastChild) root.removeChild(root.lastChild);

        const card = document.createElement('ha-card');
        const content = document.createElement('div');
        const style = document.createElement('style');
        style.textContent = `
            ha-card {
                /* sample css */
            }
            .button {
                overflow: auto;
                padding: 16px;
                text-align: right;
            }
            mwc-button {
                margin-right: 16px;
            }
            `;
        // list of buttons needs to be configurable
        content.innerHTML = `
        <div id='cameraview'>
            <p style="padding: 16px">Destinations should be registered here</p>
            <audio id='audio-player'></audio>
        </div>
        <div class='button'>
            <mwc-button raised id='art_room'>` + 'Art Room' + `</mwc-button>
            <mwc-button style='display:none' raised id='stop'>` + 'Stop' + `</mwc-button>
        </div>
        `;
        card.appendChild(content);
        card.appendChild(style);
        root.appendChild(card);
    }

    initRecordings() {
        if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
            navigator.mediaDevices.getUserMedia({ audio: true, video: false })
                .then(stream => { handlerFunction(stream, this) })
                .catch(function(err) {
                    console.log("The following error occurred initializing intercom (getUserMedia)" + err);
                })
        } else {
            console.log("Intercom is not supported by this browser (lacking getUserMedia)")
        }
  
        function handlerFunction(stream, intercomCard) {
            const options = {mimeType: 'audio/mp4'};
            const mediaRecorder = new MediaRecorder(stream, options);
            let chunks = [];

            let artRoomBtn = intercomCard.getElementById('art_room');
            let stopBtn = intercomCard.getElementById('stop');
    
            artRoomBtn.onclick = function() {
                mediaRecorder.start();
                artRoomBtn.style.display = 'none';
                stopBtn.style.display = 'inline-flex';
            }

            stopBtn.onclick = function() {
                mediaRecorder.stop();
                artRoomBtn.style.display = 'inline-flex';
                stopBtn.style.display = 'none';        
            }

            mediaRecorder.ondataavailable = function(e) {
                chunks.push(e.data);
            }

            mediaRecorder.onstop = function(e) {
                const blob = new Blob(chunks, { 'type' : 'audio/ogg; codecs=opus' });
                chunks = [];

                var reader = new FileReader();
                reader.readAsDataURL(blob); 
                reader.onloadend = function() {
                    intercomCard.playAudio(reader.result);             
                }
  
            }
        }
    }

    playAudio(content) {
        // Destination needs to be dynamic
        // need to have some sort of popup/ui with browser_mod to make it easy to reply
        // play doesn't support anything other than media_content_id https://github.com/thomasloven/hass-browser_mod/blob/f13ac064cabc8f48da04f85cedff9c3936b47642/js/main.js#L166
        this._hass.callService('media_player', 'play_media', {
            entity_id: 'media_player.575b8ffa_9b18fb9d',
            media_content_type: 'music',
            media_content_id: content
        });

    }
  
    // The height of your card. Home Assistant uses this to automatically
    // distribute all cards over the available columns.
    getCardSize() {
        return 1;
    }

    getElementById(id) {
        return this.shadowRoot.querySelector(`#${id}`);
    }
}

customElements.define('intercom-card', IntercomCard);

1 Like

I think this is a good idea. I’m a while from looking at installing my front door intercom and like the idea of not having to install another panel on the wall but using an integrated HA interface.

My very brief look into it was that the camera side was easy but the SIP side was not there then that project got put on the back burner.

But yeah, most of the intercom solutions I looked at used SIP. By the looks of it, the expensive “hub” part of an intercom system can be replaced with a FreePBX type solution.