diff --git a/README.md b/README.md index 16ba220..fe8c3f9 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,12 @@ +# Fork details + +This is a fork of original project made/edited/updated by [lukx](https://github.com/lukx/home-assistant-jukebox). I added couple of new functions like: + +* Displaying station logo image +* Adding option to display custom sensor text (song author and title) +* Fix (workaround) for long playback start for specific radio stations +* Fix for icons not showing up after HA update to version 2021.11.X + # Jukebox Card for Home-Assistant This is a media player UI for Home-Assistant leveraging the potential of the excellent new @@ -10,7 +19,7 @@ You can send different media to different players, which makes it usable for mul to some *Frozen*, while you're Jazzing in the Kitchen. Volume-Level is handled separately, too. ## Screenshot -![alt text](https://github.com/lukx/home-assistant-jukebox/blob/master/screenshot.png?raw=true "See the jukebox in action") +![alt text](https://github.com/thehijacker/home-assistant-jukebox/blob/master/screenshot.png?raw=true "See the jukebox in action") ## Acknowledgement Apart from the home-assistant project, I need to say thanks to User [Bob_NL](https://community.home-assistant.io/u/Bob_NL) @@ -26,7 +35,7 @@ I recommend using [HACS](https://hacs.xyz/) to install and update this integrati * In your Home Assistant, open the HACS panel * Click on "Frontend" to see the list of Frontend (or "Lovelace") integrations * On the top right of your screen, click on the three dots to see "Custom Repositories" -* in the "Custom Repositories" dialogue, paste `https://github.com/lukx/home-assistant-jukebox.git` in the "custom repository URL" box, and select "Lovelace" as the Category. +* in the "Custom Repositories" dialogue, paste `https://github.com/thehijacker/home-assistant-jukebox.git` in the "custom repository URL" box, and select "Lovelace" as the Category. * Now, in the Frontend Category, search for "Jukebox" and install this module like you would install any other module. @@ -48,18 +57,14 @@ views: links: - url: http://streams.greenhost.nl:8080/jazz name: Concertzender Jazz + logo: https://raw.githubusercontent.com/home-assistant/assets/master/logo/logo-small.png + song: sensor.radio_jazz - url: http://fs-insidejazz.fast-serv.com:8282/;stream.nsv name: Inside Jazz + logo: https://raw.githubusercontent.com/home-assistant/assets/master/logo/logo-small.png + song: sensor.radio_inside_jazz - url: http://stream.srg-ssr.ch/m/rsj/mp3_128 name: Radio Swiss Jazz - - url: http://stream.beachlatinoradio.com:8030/;?d= - name: Beach Latino Radio - - url: http://streams.calmradio.com/api/43/128/stream/;?d= - name: Calm Radio - - url: http://swr-swr1-bw.cast.addradio.de/swr/swr1/bw/mp3/128/stream.mp3 - name: SWR 1 - - url: http://94.23.252.14:8067/stream - name: Nature Sounds entities: - media_player.wuerfel_wohnzimmer - media_player.wuerfel_kueche @@ -75,21 +80,16 @@ Example config (note the differances from the above example): type: "custom:jukebox-card" links: - url: http://streams.greenhost.nl:8080/jazz - name: Concertzender Jazz + name: Concertzender Jazz + logo: https://raw.githubusercontent.com/home-assistant/assets/master/logo/logo-small.png + song: sensor.radio_jazz - url: http://fs-insidejazz.fast-serv.com:8282/;stream.nsv - name: Inside Jazz + name: Inside Jazz + logo: https://raw.githubusercontent.com/home-assistant/assets/master/logo/logo-small.png + song: sensor.radio_inside_jazz - url: http://stream.srg-ssr.ch/m/rsj/mp3_128 - name: Radio Swiss Jazz - - url: http://stream.beachlatinoradio.com:8030/;?d= - name: Beach Latino Radio - - url: http://streams.calmradio.com/api/43/128/stream/;?d= - name: Calm Radio - - url: http://swr-swr1-bw.cast.addradio.de/swr/swr1/bw/mp3/128/stream.mp3 - name: SWR 1 - - url: http://94.23.252.14:8067/stream - name: Nature Sounds + name: Radio Swiss Jazz entities: - media_player.wuerfel_wohnzimmer - media_player.wuerfel_kueche - ``` diff --git a/jukebox-card.js b/jukebox-card.js index bec206c..b416d34 100644 --- a/jukebox-card.js +++ b/jukebox-card.js @@ -1,3 +1,4 @@ +console.info(`%c HOME-ASSISTANT-JUKEBOX %c v2021_11_09_v1`, 'color: orange; font-weight: bold; background: black', 'color: white; font-weight: bold; background: dimgray'); class JukeboxCard extends HTMLElement { set hass(hass) { if (!this.content) { @@ -10,6 +11,7 @@ class JukeboxCard extends HTMLElement { this.content.appendChild(this.buildSpeakerSwitches(hass)); this.content.appendChild(this.buildVolumeSlider()); + this.content.appendChild(this.buildSongDisplay()); this.content.appendChild(this.buildStationList()); } @@ -49,7 +51,7 @@ class JukeboxCard extends HTMLElement { stationList.classList.add('station-list'); this.config.links.forEach(linkCfg => { - const stationButton = this.buildStationSwitch(linkCfg.name, linkCfg.url) + const stationButton = this.buildStationSwitch(linkCfg.name, linkCfg.url, linkCfg.logo, linkCfg.song) this._stationButtons.push(stationButton); stationList.appendChild(stationButton); }); @@ -60,6 +62,13 @@ class JukeboxCard extends HTMLElement { return stationList; } + buildSongDisplay() { + this._song = document.createElement('div'); + this._song.className = 'song-content'; + this._hassObservers.push(this.updateSongState.bind(this)); + return this._song; + } + buildVolumeSlider() { const volumeContainer = document.createElement('div'); volumeContainer.className = 'volume center horizontal layout'; @@ -68,6 +77,9 @@ class JukeboxCard extends HTMLElement { muteButton.icon = 'hass:volume-high'; muteButton.isMute = false; muteButton.addEventListener('click', this.onMuteUnmute.bind(this)); + const muteButtonIcon = document.createElement('ha-icon'); + muteButtonIcon.icon = 'hass:volume-high'; + muteButton.appendChild(muteButtonIcon); const slider = document.createElement('ha-slider'); slider.min = 0; @@ -79,7 +91,17 @@ class JukeboxCard extends HTMLElement { stopButton.icon = 'hass:stop'; stopButton.setAttribute('disabled', true); stopButton.addEventListener('click', this.onStop.bind(this)); - + const stopButtonIcon = document.createElement('ha-icon') + stopButtonIcon.icon = 'hass:stop'; + stopButton.appendChild(stopButtonIcon); + + const powerButton = document.createElement('ha-icon-button') + powerButton.icon = 'hass:power'; + powerButton.setAttribute('disabled', true); + powerButton.addEventListener('click', this.onPower.bind(this)); + const powerButtonIcon = document.createElement('ha-icon') + powerButtonIcon.icon = 'hass:power'; + powerButton.appendChild(powerButtonIcon); this._hassObservers.push(hass => { if (!this._selectedSpeaker || !hass.states[this._selectedSpeaker]) { @@ -90,10 +112,14 @@ class JukeboxCard extends HTMLElement { // no speaker level? then hide mute button and volume if (!speakerState.hasOwnProperty('volume_level')) { slider.setAttribute('hidden', true); - stopButton.setAttribute('hidden', true) + stopButton.setAttribute('hidden', true); + powerButton.setAttribute('hidden', true); + this._song.setAttribute('hidden', true); } else { slider.removeAttribute('hidden'); - stopButton.removeAttribute('hidden') + stopButton.removeAttribute('hidden'); + powerButton.removeAttribute('hidden'); + this._song.removeAttribute('hidden'); } if (!speakerState.hasOwnProperty('is_volume_muted')) { @@ -104,6 +130,7 @@ class JukeboxCard extends HTMLElement { if (hass.states[this._selectedSpeaker].state === 'playing') { stopButton.removeAttribute('disabled'); + powerButton.removeAttribute('disabled'); } else { stopButton.setAttribute('disabled', true); } @@ -124,6 +151,7 @@ class JukeboxCard extends HTMLElement { volumeContainer.appendChild(muteButton); volumeContainer.appendChild(slider); volumeContainer.appendChild(stopButton); + volumeContainer.appendChild(powerButton); return volumeContainer; } @@ -149,42 +177,117 @@ class JukeboxCard extends HTMLElement { this.hass.callService('media_player', 'media_stop', { entity_id: this._selectedSpeaker }); + + delete this._songEntity; + this._song.innerHTML = ''; + } + + onPower(e) { + this.hass.callService('media_player', 'turn_off', { + entity_id: this._selectedSpeaker + }); + + delete this._songEntity; + this._song.innerHTML = ''; } updateStationSwitchStates(hass) { let playingUrl = null; const selectedSpeaker = this._selectedSpeaker; - if (hass.states[selectedSpeaker] && hass.states[selectedSpeaker].state === 'playing') { + if (hass.states[selectedSpeaker] && hass.states[selectedSpeaker].state === 'playing'){ playingUrl = hass.states[selectedSpeaker].attributes.media_content_id; } - this._stationButtons.forEach(stationSwitch => { - if (stationSwitch.hasAttribute('raised') && stationSwitch.stationUrl !== playingUrl) { + this._stationButtons.forEach(stationSwitch => + { + if (stationSwitch.hasAttribute('raised') && stationSwitch.stationUrl !== playingUrl) + { stationSwitch.removeAttribute('raised'); return; } - if (!stationSwitch.hasAttribute('raised') && stationSwitch.stationUrl === playingUrl) { + if (!stationSwitch.hasAttribute('raised') && stationSwitch.stationUrl === playingUrl) + { stationSwitch.setAttribute('raised', true); + + // Show song entity state for selected station if available + if (stationSwitch.stationSong) + this._songEntity = stationSwitch.stationSong; } }) } - buildStationSwitch(name, url) { + updateSongState(hass) + { + if (this._songEntity) + { + if (this._song.innerHTML != hass.states[this._songEntity].state) + { + this._song.innerHTML = hass.states[this._songEntity].state; + } + } else + { + this._song.innerHTML = ''; + } + } + + buildStationSwitch(name, url, logo, song) { const btn = document.createElement('mwc-button'); btn.stationUrl = url; + btn.stationName = name; + btn.stationLogo = logo; + btn.stationSong = song; btn.className = 'juke-toggle'; btn.innerText = name; btn.addEventListener('click', this.onStationSelect.bind(this)); return btn; } - onStationSelect(e) { + onStationSelect(e) { + delete this._songEntity; + this._song.innerHTML = ''; + + /* + // If there is a song entity, display it, otherwise clear it + if (e.currentTarget.stationSong) + { + this._song.innerHTML = this.hass.states[e.currentTarget.stationSong].state; + this._songEntity = e.currentTarget.stationSong; + } else + { + delete this._songEntity; + this._song.innerHTML = ''; + } + */ + this.hass.callService('media_player', 'play_media', { entity_id: this._selectedSpeaker, media_content_id: e.currentTarget.stationUrl, - media_content_type: 'audio/mp4' + media_content_type: 'music', + extra: { + metadata: { + stream_type: "LIVE", + metadataType: 3, + title: e.currentTarget.stationName, + artist: "Live Radio", + images: [ + { url: e.currentTarget.stationLogo } + ] + } + } }); + + // Force play for 10 seconds every half a second + var that = this; + var i = 0; + for (i = 500; i <= 5000; i=i+500) + { + setTimeout(function() { + that.hass.callService('media_player', 'media_play', { + entity_id: that._selectedSpeaker + }); }, i); + } + } setVolume(value) { @@ -228,6 +331,15 @@ class JukeboxCard extends HTMLElement { } } +function pauseBrowser(millis) +{ + var date = Date.now(); + var curDate = null; + do { + curDate = Date.now(); + } while (curDate-date < millis); +} + function getStyle() { const frag = document.createDocumentFragment(); @@ -269,6 +381,12 @@ function getStyle() { .volume { padding: 10px 20px; } + + .song-content { + text-align: center; + color: green; + font-weight: bold; + } mwc-button.juke-toggle { --mdc-theme-primary: var(--primary-text-color); diff --git a/screenshot.png b/screenshot.png index 3d25249..867a358 100644 Binary files a/screenshot.png and b/screenshot.png differ