diff --git a/custom_components/browser_mod/browser_mod.js b/custom_components/browser_mod/browser_mod.js index c7a4d57..5238601 100644 --- a/custom_components/browser_mod/browser_mod.js +++ b/custom_components/browser_mod/browser_mod.js @@ -619,13 +619,19 @@ const MediaPlayerMixin = (SuperClass) => { return class MediaPlayerMixinClass extends SuperClass { constructor() { super(); - this.player = new Audio(); + this._audio_player = new Audio(); + this._video_player = document.createElement("video"); + this._video_player.controls = true; + this._video_player.style.setProperty("width", "100%"); + this.player = this._audio_player; this._player_enabled = false; for (const ev of ["play", "pause", "ended", "volumechange"]) { - this.player.addEventListener(ev, () => this._player_update()); + this._audio_player.addEventListener(ev, () => this._player_update()); + this._video_player.addEventListener(ev, () => this._player_update()); } for (const ev of ["timeupdate"]) { - this.player.addEventListener(ev, () => this._player_update_choked()); + this._audio_player.addEventListener(ev, () => this._player_update_choked()); + this._video_player.addEventListener(ev, () => this._player_update_choked()); } this.firstInteraction.then(() => { this._player_enabled = true; @@ -633,10 +639,18 @@ const MediaPlayerMixin = (SuperClass) => { this.player.play(); }); this.addEventListener("command-player-play", (ev) => { - var _a; - if ((_a = ev.detail) === null || _a === void 0 ? void 0 : _a.media_content_id) + var _a, _b, _c; + if (this.player.src) + this.player.pause(); + if ((_a = ev.detail) === null || _a === void 0 ? void 0 : _a.media_type) + if ((_b = ev.detail) === null || _b === void 0 ? void 0 : _b.media_type.startsWith("video")) + this.player = this._video_player; + else + this.player = this._audio_player; + if ((_c = ev.detail) === null || _c === void 0 ? void 0 : _c.media_content_id) this.player.src = ev.detail.media_content_id; this.player.play(); + this._show_video_player(); }); this.addEventListener("command-player-pause", (ev) => this.player.pause()); this.addEventListener("command-player-stop", (ev) => { @@ -660,8 +674,30 @@ const MediaPlayerMixin = (SuperClass) => { this.player.currentTime = ev.detail.position; setTimeout(() => this._player_update(), 10); }); + this.addEventListener("command-player-turn-off", (ev) => { + if (this.player === this._video_player && + this._video_player.isConnected) + this.closePopup(); + else if (this.player.src) + this.player.pause(); + this.player.src = ""; + this._player_update(); + }); this.connectionPromise.then(() => this._player_update()); } + _show_video_player() { + if (this.player === this._video_player && this.player.src) { + selectTree(document, "home-assistant $ dialog-media-player-browse").then((el) => el === null || el === void 0 ? void 0 : el.closeDialog()); + this.showPopup(undefined, this._video_player, { + dismiss_action: () => this._video_player.pause(), + size: "wide", + }); + } + else if (this.player !== this._video_player && + this._video_player.isConnected) { + this.closePopup(); + } + } _player_update_choked() { if (this._player_update_cooldown) return; @@ -670,13 +706,13 @@ const MediaPlayerMixin = (SuperClass) => { } _player_update() { const state = this._player_enabled - ? this.player.src - ? this.player.ended + ? !this.player.src || this.player.src === window.location.href + ? "off" + : this.player.ended ? "stopped" : this.player.paused ? "paused" : "playing" - : "stopped" : "unavailable"; this.sendUpdate({ player: { @@ -1167,13 +1203,17 @@ class BrowserModPopup extends s { } this._autocloseListener = undefined; if (this._autoclose) { - this._autocloseListener = this._dismiss.bind(this); - window.browser_mod.addEventListener("browser-mod-activity", this._autocloseListener); + this._autocloseListener = () => this.dialog.close(); + window.browser_mod.addEventListener("browser-mod-activity", this._autocloseListener, { once: true }); } } async setupDialog(title, content, { right_button = undefined, right_button_action = undefined, left_button = undefined, left_button_action = undefined, dismissable = true, dismiss_action = undefined, timeout = undefined, timeout_action = undefined, size = undefined, style = undefined, autoclose = false, } = {}) { this.title = title; - if (content && typeof content === "object") { + if (content && content instanceof HTMLElement) { + this.card = undefined; + this.content = content; + } + else if (content && typeof content === "object") { // Create a card from config in content this.card = true; const helpers = await window.loadCardHelpers(); @@ -1204,30 +1244,30 @@ class BrowserModPopup extends s { this._autoclose = autoclose; } async _primary() { - var _a, _b, _c; + var _a, _b, _c, _d; if ((_a = this._actions) === null || _a === void 0 ? void 0 : _a.dismiss_action) this._actions.dismiss_action = undefined; - await this.closeDialog(); - (_c = (_b = this._actions) === null || _b === void 0 ? void 0 : _b.right_button_action) === null || _c === void 0 ? void 0 : _c.call(_b); + (_b = this.dialog) === null || _b === void 0 ? void 0 : _b.close(); + (_d = (_c = this._actions) === null || _c === void 0 ? void 0 : _c.right_button_action) === null || _d === void 0 ? void 0 : _d.call(_c); } async _secondary() { - var _a, _b, _c; + var _a, _b, _c, _d; if ((_a = this._actions) === null || _a === void 0 ? void 0 : _a.dismiss_action) this._actions.dismiss_action = undefined; - await this.closeDialog(); - (_c = (_b = this._actions) === null || _b === void 0 ? void 0 : _b.left_button_action) === null || _c === void 0 ? void 0 : _c.call(_b); + (_b = this.dialog) === null || _b === void 0 ? void 0 : _b.close(); + (_d = (_c = this._actions) === null || _c === void 0 ? void 0 : _c.left_button_action) === null || _d === void 0 ? void 0 : _d.call(_c); } - async _dismiss() { - var _a, _b; - await this.closeDialog(); - (_b = (_a = this._actions) === null || _a === void 0 ? void 0 : _a.dismiss_action) === null || _b === void 0 ? void 0 : _b.call(_a); + async _dismiss(ev) { + var _a, _b, _c; + (_a = this.dialog) === null || _a === void 0 ? void 0 : _a.close(); + (_c = (_b = this._actions) === null || _b === void 0 ? void 0 : _b.dismiss_action) === null || _c === void 0 ? void 0 : _c.call(_b); } async _timeout() { - var _a, _b, _c; + var _a, _b, _c, _d; if ((_a = this._actions) === null || _a === void 0 ? void 0 : _a.dismiss_action) this._actions.dismiss_action = undefined; - await this.closeDialog(); - (_c = (_b = this._actions) === null || _b === void 0 ? void 0 : _b.timeout_action) === null || _c === void 0 ? void 0 : _c.call(_b); + (_b = this.dialog) === null || _b === void 0 ? void 0 : _b.close(); + (_d = (_c = this._actions) === null || _c === void 0 ? void 0 : _c.timeout_action) === null || _d === void 0 ? void 0 : _d.call(_c); } render() { if (!this.open) @@ -1235,10 +1275,12 @@ class BrowserModPopup extends s { return $ ` ${this.timeout ? $ `
` @@ -1289,6 +1331,7 @@ class BrowserModPopup extends s { static get styles() { return r$2 ` ha-dialog { + z-index: 10; --mdc-dialog-min-width: var(--popup-min-width, 400px); --mdc-dialog-max-width: var(--popup-max-width, 600px); --mdc-dialog-heading-ink-color: var(--primary-text-color); @@ -2180,7 +2223,7 @@ const BrowserIDMixin = (SuperClass) => { - Tweaks - Quickbar tweaks (ctrl+enter)? x Card-mod preload - - Video player? + x Video player? x Media_seek - Screensavers x IMPORTANT: FIX DEFAULT HIDING OF ENTITIES diff --git a/custom_components/browser_mod/media_player.py b/custom_components/browser_mod/media_player.py index 10389a6..b6fbc66 100644 --- a/custom_components/browser_mod/media_player.py +++ b/custom_components/browser_mod/media_player.py @@ -16,6 +16,8 @@ from homeassistant.components.media_player.const import ( MEDIA_TYPE_URL, SUPPORT_BROWSE_MEDIA, SUPPORT_SEEK, + SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, ) from homeassistant.const import ( STATE_UNAVAILABLE, @@ -23,6 +25,8 @@ from homeassistant.const import ( STATE_PLAYING, STATE_IDLE, STATE_UNKNOWN, + STATE_ON, + STATE_OFF, ) from homeassistant.util import dt @@ -63,6 +67,8 @@ class BrowserModPlayer(BrowserModEntity, MediaPlayerEntity): "paused": STATE_PAUSED, "stopped": STATE_IDLE, "unavailable": STATE_UNAVAILABLE, + "on": STATE_ON, + "off": STATE_OFF, }.get(state, STATE_UNKNOWN) @property @@ -76,6 +82,8 @@ class BrowserModPlayer(BrowserModEntity, MediaPlayerEntity): | SUPPORT_VOLUME_MUTE | SUPPORT_BROWSE_MEDIA | SUPPORT_SEEK + | SUPPORT_TURN_OFF + | SUPPORT_TURN_ON ) @property @@ -108,21 +116,24 @@ class BrowserModPlayer(BrowserModEntity, MediaPlayerEntity): async def async_play_media(self, media_type, media_id, **kwargs): if media_source.is_media_source_id(media_id): - media_type = MEDIA_TYPE_URL play_item = await media_source.async_resolve_media( self.hass, media_id, self.entity_id ) + media_type = play_item.mime_type media_id = play_item.url + media_id = async_process_play_media_url(self.hass, media_id) if media_type in (MEDIA_TYPE_URL, MEDIA_TYPE_MUSIC): media_id = async_process_play_media_url(self.hass, media_id) - self.browser.send("player-play", media_content_id=media_id) + self.browser.send( + "player-play", media_content_id=media_id, media_type=media_type, **kwargs + ) async def async_browse_media(self, media_content_type=None, media_content_id=None): """Implement the websocket media browsing helper.""" return await media_source.async_browse_media( self.hass, media_content_id, - content_filter=lambda item: item.media_content_type.startswith("audio/"), + # content_filter=lambda item: item.media_content_type.startswith("audio/"), ) def media_play(self): @@ -136,3 +147,9 @@ class BrowserModPlayer(BrowserModEntity, MediaPlayerEntity): def media_seek(self, position): self.browser.send("player-seek", position=position) + + def turn_off(self): + self.browser.send("player-turn-off") + + def turn_on(self, **kwargs): + self.browser.send("player-turn-on", **kwargs) diff --git a/js/plugin/main.ts b/js/plugin/main.ts index 57c3d29..2c08bd7 100644 --- a/js/plugin/main.ts +++ b/js/plugin/main.ts @@ -67,7 +67,7 @@ import { BrowserIDMixin } from "./browserID"; - Tweaks - Quickbar tweaks (ctrl+enter)? x Card-mod preload - - Video player? + x Video player? x Media_seek - Screensavers x IMPORTANT: FIX DEFAULT HIDING OF ENTITIES diff --git a/js/plugin/mediaPlayer.ts b/js/plugin/mediaPlayer.ts index 01b7e81..2e3e273 100644 --- a/js/plugin/mediaPlayer.ts +++ b/js/plugin/mediaPlayer.ts @@ -1,20 +1,34 @@ +import { selectTree } from "../helpers"; + export const MediaPlayerMixin = (SuperClass) => { return class MediaPlayerMixinClass extends SuperClass { public player; + private _audio_player; + private _video_player; private _player_enabled; private _player_update_cooldown; constructor() { super(); - this.player = new Audio(); + this._audio_player = new Audio(); + this._video_player = document.createElement("video"); + this._video_player.controls = true; + this._video_player.style.setProperty("width", "100%"); + this.player = this._audio_player; this._player_enabled = false; for (const ev of ["play", "pause", "ended", "volumechange"]) { - this.player.addEventListener(ev, () => this._player_update()); + this._audio_player.addEventListener(ev, () => this._player_update()); + this._video_player.addEventListener(ev, () => this._player_update()); } for (const ev of ["timeupdate"]) { - this.player.addEventListener(ev, () => this._player_update_choked()); + this._audio_player.addEventListener(ev, () => + this._player_update_choked() + ); + this._video_player.addEventListener(ev, () => + this._player_update_choked() + ); } this.firstInteraction.then(() => { @@ -23,9 +37,15 @@ export const MediaPlayerMixin = (SuperClass) => { }); this.addEventListener("command-player-play", (ev) => { + if (this.player.src) this.player.pause(); + if (ev.detail?.media_type) + if (ev.detail?.media_type.startsWith("video")) + this.player = this._video_player; + else this.player = this._audio_player; if (ev.detail?.media_content_id) this.player.src = ev.detail.media_content_id; this.player.play(); + this._show_video_player(); }); this.addEventListener("command-player-pause", (ev) => this.player.pause() @@ -47,10 +67,38 @@ export const MediaPlayerMixin = (SuperClass) => { this.player.currentTime = ev.detail.position; setTimeout(() => this._player_update(), 10); }); + this.addEventListener("command-player-turn-off", (ev) => { + if ( + this.player === this._video_player && + this._video_player.isConnected + ) + this.closePopup(); + else if (this.player.src) this.player.pause(); + this.player.src = ""; + this._player_update(); + }); this.connectionPromise.then(() => this._player_update()); } + private _show_video_player() { + if (this.player === this._video_player && this.player.src) { + selectTree( + document, + "home-assistant $ dialog-media-player-browse" + ).then((el) => el?.closeDialog()); + this.showPopup(undefined, this._video_player, { + dismiss_action: () => this._video_player.pause(), + size: "wide", + }); + } else if ( + this.player !== this._video_player && + this._video_player.isConnected + ) { + this.closePopup(); + } + } + private _player_update_choked() { if (this._player_update_cooldown) return; this._player_update_cooldown = window.setTimeout( @@ -62,13 +110,13 @@ export const MediaPlayerMixin = (SuperClass) => { private _player_update() { const state = this._player_enabled - ? this.player.src - ? this.player.ended - ? "stopped" - : this.player.paused - ? "paused" - : "playing" - : "stopped" + ? !this.player.src || this.player.src === window.location.href + ? "off" + : this.player.ended + ? "stopped" + : this.player.paused + ? "paused" + : "playing" : "unavailable"; this.sendUpdate({ player: { diff --git a/js/plugin/popups.ts b/js/plugin/popups.ts index 8fa007f..b1fdfce 100644 --- a/js/plugin/popups.ts +++ b/js/plugin/popups.ts @@ -114,21 +114,21 @@ class BrowserModPopup extends LitElement { async _primary() { if (this._actions?.dismiss_action) this._actions.dismiss_action = undefined; - this.dialog.close(); + this.dialog?.close(); this._actions?.right_button_action?.(); } async _secondary() { if (this._actions?.dismiss_action) this._actions.dismiss_action = undefined; - this.dialog.close(); + this.dialog?.close(); this._actions?.left_button_action?.(); } async _dismiss(ev?) { - this.dialog.close(); + this.dialog?.close(); this._actions?.dismiss_action?.(); } async _timeout() { if (this._actions?.dismiss_action) this._actions.dismiss_action = undefined; - this.dialog.close(); + this.dialog?.close(); this._actions?.timeout_action?.(); } @@ -195,6 +195,7 @@ class BrowserModPopup extends LitElement { static get styles() { return css` ha-dialog { + z-index: 10; --mdc-dialog-min-width: var(--popup-min-width, 400px); --mdc-dialog-max-width: var(--popup-max-width, 600px); --mdc-dialog-heading-ink-color: var(--primary-text-color);