Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Media Browser Panel #6772

Merged
merged 41 commits into from
Sep 4, 2020
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
eac6975
updates
zsarnett Sep 2, 2020
26962f7
Merge branch 'dev' of https://github.com/home-assistant/frontend into…
zsarnett Sep 2, 2020
42b5d5f
local storage
zsarnett Sep 2, 2020
1adf210
Changes
zsarnett Sep 2, 2020
05b6ee1
fix dialog heading
zsarnett Sep 2, 2020
f05d855
console.bye
zsarnett Sep 2, 2020
b4b4e56
add HLS and Exo Player
zsarnett Sep 3, 2020
2b7632d
Merge branch 'dev' of https://github.com/home-assistant/frontend into…
zsarnett Sep 3, 2020
0e8f0d2
comments
zsarnett Sep 3, 2020
ba746be
Update src/panels/media-browser/hui-dialog-browser-media-player.ts
zsarnett Sep 3, 2020
2f2c51c
Update src/panels/media-browser/hui-dialog-select-media-player.ts
zsarnett Sep 3, 2020
3b4df76
Update src/data/media-player.ts
zsarnett Sep 3, 2020
464b034
Update src/data/media-player.ts
zsarnett Sep 3, 2020
b3607b5
Update src/data/media-player.ts
zsarnett Sep 3, 2020
e63b151
Update src/data/media-player.ts
zsarnett Sep 3, 2020
1992825
Update src/data/media-player.ts
zsarnett Sep 3, 2020
e5da468
Update src/data/media-player.ts
zsarnett Sep 3, 2020
f446b4c
Update src/data/media-player.ts
zsarnett Sep 3, 2020
f90c8f4
Update src/data/media-player.ts
zsarnett Sep 3, 2020
9febb2d
Update src/data/media-player.ts
zsarnett Sep 3, 2020
6082ca4
Update src/components/media-player/dialog-media-player-browse.ts
zsarnett Sep 3, 2020
0cff9a6
add imports
zsarnett Sep 3, 2020
5476691
Undo hack for panel
zsarnett Sep 4, 2020
5691bdd
more undo
zsarnett Sep 4, 2020
c6aa013
last undo
zsarnett Sep 4, 2020
5b969b4
remove un used code
zsarnett Sep 4, 2020
144dca4
remove un used translation
zsarnett Sep 4, 2020
a46d4e5
Update src/components/ha-dialog.ts
zsarnett Sep 4, 2020
227db34
comments
zsarnett Sep 4, 2020
16dc8ff
Change from memoize
zsarnett Sep 4, 2020
30a5269
comments
zsarnett Sep 4, 2020
afd40c6
comments
zsarnett Sep 4, 2020
39972ab
more comments
zsarnett Sep 4, 2020
41d5463
convert ha-camera-stream to use new element
zsarnett Sep 4, 2020
36ac286
move to a private function
zsarnett Sep 4, 2020
4b753a5
comment
zsarnett Sep 4, 2020
c4d2f04
Merge branch 'dev' of https://github.com/home-assistant/frontend into…
zsarnett Sep 4, 2020
4ca6b2e
Local storage
zsarnett Sep 4, 2020
a6f6203
fix more info controls
zsarnett Sep 4, 2020
073e749
fix connected
zsarnett Sep 4, 2020
b1e9d4e
attached logic
zsarnett Sep 4, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
184 changes: 19 additions & 165 deletions src/components/ha-camera-stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,56 +3,39 @@ import {
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
internalProperty,
PropertyValues,
TemplateResult,
} from "lit-element";
import { fireEvent } from "../common/dom/fire_event";
import { computeStateName } from "../common/entity/compute_state_name";
import { supportsFeature } from "../common/entity/supports-feature";
import { nextRender } from "../common/util/render-status";
import { getExternalConfig } from "../external_app/external_config";
import {
CAMERA_SUPPORT_STREAM,
computeMJPEGStreamUrl,
fetchStreamUrl,
} from "../data/camera";
import { CameraEntity, HomeAssistant } from "../types";

type HLSModule = typeof import("hls.js");
import "./ha-hls-player";

@customElement("ha-camera-stream")
class HaCameraStream extends LitElement {
@property({ attribute: false }) public hass?: HomeAssistant;

@property() public stateObj?: CameraEntity;
@property({ attribute: false }) public stateObj?: CameraEntity;

@property({ type: Boolean }) public showControls = false;

@internalProperty() private _attached = false;

// We keep track if we should force MJPEG with a string
// that way it automatically resets if we change entity.
@internalProperty() private _forceMJPEG: string | undefined = undefined;

private _hlsPolyfillInstance?: Hls;
@internalProperty() private _forceMJPEG?: string;

private _useExoPlayer = false;

public connectedCallback() {
super.connectedCallback();
this._attached = true;
}

public disconnectedCallback() {
super.disconnectedCallback();
this._attached = false;
}
@internalProperty() private _url?: string;

protected render(): TemplateResult {
if (!this.stateObj || !this._attached) {
if (!this.stateObj || (!this._forceMJPEG && !this._url)) {
return html``;
}

Expand All @@ -70,50 +53,22 @@ class HaCameraStream extends LitElement {
/>
`
: html`
<video
<ha-hls-player
autoplay
muted
playsinline
?controls=${this.showControls}
@loadeddata=${this._elementResized}
></video>
.hass=${this.hass}
.url=${this._url!}
></ha-hls-player>
`}
`;
}

protected updated(changedProps: PropertyValues) {
super.updated(changedProps);

const stateObjChanged = changedProps.has("stateObj");
const attachedChanged = changedProps.has("_attached");

const oldState = changedProps.get("stateObj") as this["stateObj"];
const oldEntityId = oldState ? oldState.entity_id : undefined;
const curEntityId = this.stateObj ? this.stateObj.entity_id : undefined;

if (
(!stateObjChanged && !attachedChanged) ||
(stateObjChanged && oldEntityId === curEntityId)
) {
return;
}

// If we are no longer attached, destroy polyfill.
if (attachedChanged && !this._attached) {
this._destroyPolyfill();
return;
}

// Nothing to do if we are render MJPEG.
if (this._shouldRenderMJPEG) {
return;
}

// Tear down existing polyfill, if available
this._destroyPolyfill();

if (curEntityId) {
this._startHls();
protected updated(changedProps: PropertyValues): void {
if (changedProps.has("stateObj")) {
this._forceMJPEG = undefined;
this._getStreamUrl();
}
}

Expand All @@ -125,136 +80,35 @@ class HaCameraStream extends LitElement {
);
}

private get _videoEl(): HTMLVideoElement {
return this.shadowRoot!.querySelector("video")!;
}

private async _getUseExoPlayer(): Promise<boolean> {
if (!this.hass!.auth.external) {
return false;
}
const externalConfig = await getExternalConfig(this.hass!.auth.external);
return externalConfig && externalConfig.hasExoPlayer;
}

private async _startHls(): Promise<void> {
// eslint-disable-next-line
let hls;
const videoEl = this._videoEl;
this._useExoPlayer = await this._getUseExoPlayer();
if (!this._useExoPlayer) {
hls = ((await import(/* webpackChunkName: "hls.js" */ "hls.js")) as any)
.default as HLSModule;
let hlsSupported = hls.isSupported();

if (!hlsSupported) {
hlsSupported =
videoEl.canPlayType("application/vnd.apple.mpegurl") !== "";
}

if (!hlsSupported) {
this._forceMJPEG = this.stateObj!.entity_id;
return;
}
}

private async _getStreamUrl(): Promise<void> {
try {
const { url } = await fetchStreamUrl(
this.hass!,
this.stateObj!.entity_id
);

if (this._useExoPlayer) {
this._renderHLSExoPlayer(url);
} else if (hls.isSupported()) {
this._renderHLSPolyfill(videoEl, hls, url);
} else {
this._renderHLSNative(videoEl, url);
}
return;
this._url = url;
} catch (err) {
// Fails if we were unable to get a stream
// eslint-disable-next-line
console.error(err);

this._forceMJPEG = this.stateObj!.entity_id;
}
}

private async _renderHLSExoPlayer(url: string) {
window.addEventListener("resize", this._resizeExoPlayer);
this.updateComplete.then(() => nextRender()).then(this._resizeExoPlayer);
this._videoEl.style.visibility = "hidden";
await this.hass!.auth.external!.sendMessage({
type: "exoplayer/play_hls",
payload: new URL(url, window.location.href).toString(),
});
}

private _resizeExoPlayer = () => {
const rect = this._videoEl.getBoundingClientRect();
this.hass!.auth.external!.fireMessage({
type: "exoplayer/resize",
payload: {
left: rect.left,
top: rect.top,
right: rect.right,
bottom: rect.bottom,
},
});
};

private async _renderHLSNative(videoEl: HTMLVideoElement, url: string) {
videoEl.src = url;
await new Promise((resolve) =>
videoEl.addEventListener("loadedmetadata", resolve)
);
videoEl.play();
}

private async _renderHLSPolyfill(
videoEl: HTMLVideoElement,
// eslint-disable-next-line
Hls: HLSModule,
url: string
) {
const hls = new Hls({
liveBackBufferLength: 60,
fragLoadingTimeOut: 30000,
manifestLoadingTimeOut: 30000,
levelLoadingTimeOut: 30000,
});
this._hlsPolyfillInstance = hls;
hls.attachMedia(videoEl);
hls.on(Hls.Events.MEDIA_ATTACHED, () => {
hls.loadSource(url);
});
}

private _elementResized() {
fireEvent(this, "iron-resize");
}

private _destroyPolyfill() {
if (this._hlsPolyfillInstance) {
this._hlsPolyfillInstance.destroy();
this._hlsPolyfillInstance = undefined;
}
if (this._useExoPlayer) {
window.removeEventListener("resize", this._resizeExoPlayer);
this.hass!.auth.external!.fireMessage({ type: "exoplayer/stop" });
}
}

static get styles(): CSSResult {
return css`
:host,
img,
video {
img {
display: block;
}

img,
video {
img {
width: 100%;
}
`;
Expand Down
9 changes: 8 additions & 1 deletion src/components/ha-dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import "./ha-icon-button";
const MwcDialog = customElements.get("mwc-dialog") as Constructor<Dialog>;

export const createCloseHeading = (hass: HomeAssistant, title: string) => html`
${title}
<span class="header_title">${title}</span>
<mwc-icon-button
aria-label=${hass.localize("ui.dialogs.generic.close")}
dialogAction="close"
Expand Down Expand Up @@ -77,10 +77,17 @@ export class HaDialog extends MwcDialog {
text-decoration: none;
color: inherit;
}
.header_title {
margin-right: 40px;
}
[dir="rtl"].header_button {
right: auto;
left: 16px;
}
[dir="rtl"].header_title {
margin-left: 40px;
zsarnett marked this conversation as resolved.
Show resolved Hide resolved
margin-right: 0px;
}
`,
];
}
Expand Down