/** Chrome 63+, Safari 11.1+ */
import {VideoRTC} from './video-rtc.js?v=1.9.9';
import {DigitalPTZ} from './digital-ptz.js?v=3.3.0';
class WebRTCCamera extends VideoRTC {
/**
* Step 1. Called by the Hass, when config changed.
* @param {Object} config
*/
setConfig(config) {
if (!config.url && !config.entity && !config.streams) throw new Error('Missing `url` or `entity` or `streams`');
if (config.background) this.background = config.background;
if (config.intersection === 0) this.visibilityThreshold = 0;
else this.visibilityThreshold = config.intersection || 0.75;
/**
* @type {{
* url: string,
* entity: string,
* mode: string,
* media: string,
*
* streams: Array<{
* name: string,
* url: string,
* entity: string,
* mode: string,
* media: string,
* }>,
*
* title: string,
* poster: string,
* poster_remote: boolean,
* muted: boolean,
* intersection: number,
* ui: boolean,
* style: string,
* background: boolean,
*
* server: string,
*
* mse: boolean,
* webrtc: boolean,
*
* digital_ptz:{
* mouse_drag_pan: boolean,
* mouse_wheel_zoom: boolean,
* mouse_double_click_zoom: boolean,
* touch_pinch_zoom: boolean,
* touch_drag_pan: boolean,
* touch_tap_drag_zoom: boolean,
* persist: boolean|string,
* },
* ptz:{
* opacity: number|string,
* service: string,
* data_left, data_up, data_right, data_down, data_zoom_in, data_zoom_out, data_home
* },
* shortcuts:Array<{ name:string, icon:string }>,
* }} config
*/
this.config = Object.assign({
mode: config.mse === false ? 'webrtc' : config.webrtc === false ? 'mse' : this.mode,
media: this.media,
streams: [{url: config.url, entity: config.entity}],
poster_remote: config.poster && (config.poster.indexOf('://') > 0 || config.poster.charAt(0) === '/'),
}, config);
this.streamID = -1;
this.nextStream(false);
this.onhass = [];
}
set hass(hass) {
this._hass = hass;
this.onhass.forEach(fn => fn());
// if card in vertical stack - `hass` property assign after `onconnect`
// this.onconnect();
}
get hass() {
return this._hass;
}
/**
* Called by the Hass to calculate default card height.
*/
getCardSize() {
return 5; // x 50px
}
/**
* Called by the Hass to get defaul card config
* @return {{url: string}}
*/
static getStubConfig() {
return {'url': ''};
}
setStatus(mode, status) {
const divMode = this.querySelector('.mode').innerText;
if (mode === 'error' && divMode !== 'Loading..' && divMode !== 'Loading...') return;
this.querySelector('.mode').innerText = mode;
this.querySelector('.status').innerText = status || '';
}
/** @param reload {boolean} */
nextStream(reload) {
this.streamID = (this.streamID + 1) % this.config.streams.length;
const stream = this.config.streams[this.streamID];
this.config.url = stream.url;
this.config.entity = stream.entity;
this.mode = stream.mode || this.config.mode;
this.media = stream.media || this.config.media;
if (reload) {
this.ondisconnect();
setTimeout(() => this.onconnect(), 100); // wait ws.close event
}
}
/** @return {string} */
get streamName() {
return this.config.streams[this.streamID].name || `S${this.streamID}`;
}
oninit() {
super.oninit();
this.renderMain();
this.renderDigitalPTZ();
this.renderPTZ();
this.renderCustomUI();
this.renderShortcuts();
this.renderStyle();
}
onconnect() {
if (!this.config || !this.hass) return false;
if (!this.isConnected || this.ws || this.pc) return false;
const divMode = this.querySelector('.mode').innerText;
if (divMode === 'Loading..') return;
this.setStatus('Loading..');
this.hass.callWS({
type: 'auth/sign_path', path: '/api/webrtc/ws'
}).then(data => {
if (this.config.poster && !this.config.poster_remote) {
this.video.poster = this.hass.hassUrl(data.path) + '&poster=' + encodeURIComponent(this.config.poster);
}
this.wsURL = 'ws' + this.hass.hassUrl(data.path).substring(4);
if (this.config.entity) {
this.wsURL += '&entity=' + this.config.entity;
} else if (this.config.url) {
this.wsURL += '&url=' + encodeURIComponent(this.config.url);
} else {
this.setStatus('IMG');
return;
}
if (this.config.server) {
this.wsURL += '&server=' + encodeURIComponent(this.config.server);
}
if (super.onconnect()) {
this.setStatus('Loading...');
} else {
this.setStatus('error', 'unable to connect');
}
}).catch(er => {
this.setStatus('error', er);
});
}
onopen() {
const result = super.onopen();
this.onmessage['stream'] = msg => {
switch (msg.type) {
case 'error':
this.setStatus('error', msg.value);
break;
case 'mse':
case 'hls':
case 'mp4':
case 'mjpeg':
this.setStatus(msg.type.toUpperCase(), this.config.title || '');
break;
}
};
return result;
}
onpcvideo(ev) {
super.onpcvideo(ev);
if (this.pcState !== WebSocket.CLOSED) {
this.setStatus('RTC', this.config.title || '');
}
}
renderMain() {
const shadow = this.attachShadow({mode: 'open'});
shadow.innerHTML = `